From 3508019cd2b46dc105d5374988dc337e2ce2ff66 Mon Sep 17 00:00:00 2001 From: Arthur Meyre Date: Tue, 6 Jun 2023 10:28:58 +0200 Subject: [PATCH] feat(core): Add Compact Public Key - Based on "TFHE Public-Key Encryption Revisited " https://eprint.iacr.org/2023/603.pdf Co-authored-by: tmontaigu --- .gitignore | 1 + Makefile | 18 +- tfhe/Cargo.toml | 11 +- tfhe/c_api_tests/test_high_level_128_bits.c | 44 +- tfhe/c_api_tests/test_high_level_256_bits.c | 78 +- .../test_high_level_custom_integers.c | 213 +++++ tfhe/c_api_tests/test_micro_bench_and.c | 2 + tfhe/c_api_tests/test_shortint_keygen.c | 45 +- tfhe/c_api_tests/test_shortint_pbs.c | 12 +- tfhe/c_api_tests/test_shortint_server_key.c | 6 +- tfhe/examples/integer_compact_pk_ct_sizes.rs | 92 ++ tfhe/js_on_wasm_tests/test-hlapi.js | 247 ++++++ tfhe/src/boolean/engine/bootstrapping.rs | 4 +- tfhe/src/c_api/high_level_api/config.rs | 14 + tfhe/src/c_api/high_level_api/integers.rs | 189 ++++- tfhe/src/c_api/high_level_api/keys.rs | 46 + tfhe/src/c_api/high_level_api/mod.rs | 2 + tfhe/src/c_api/high_level_api/u128.rs | 20 + tfhe/src/c_api/high_level_api/u256.rs | 81 +- tfhe/src/c_api/high_level_api/utils.rs | 43 + tfhe/src/c_api/shortint/client_key.rs | 7 +- tfhe/src/c_api/shortint/destroy.rs | 12 - tfhe/src/c_api/shortint/mod.rs | 7 +- tfhe/src/c_api/shortint/parameters.rs | 245 ++++-- .../lwe_compact_ciphertext_list_expansion.rs | 110 +++ .../lwe_compact_public_key_generation.rs | 149 ++++ .../core_crypto/algorithms/lwe_encryption.rs | 788 ++++++++++++++++++ tfhe/src/core_crypto/algorithms/mod.rs | 6 + ...ed_lwe_compact_public_key_decompression.rs | 49 ++ .../algorithms/slice_algorithms.rs | 53 ++ .../test/lwe_compact_public_key_generation.rs | 83 ++ .../algorithms/test/lwe_encryption.rs | 64 ++ tfhe/src/core_crypto/algorithms/test/mod.rs | 1 + .../commons/generators/encryption.rs | 33 +- tfhe/src/core_crypto/commons/parameters.rs | 8 + .../traits/contiguous_entity_container.rs | 182 +++- .../core_crypto/entities/glwe_ciphertext.rs | 2 +- .../core_crypto/entities/lwe_ciphertext.rs | 387 +++++++-- .../entities/lwe_compact_ciphertext_list.rs | 334 ++++++++ .../entities/lwe_compact_public_key.rs | 187 +++++ .../core_crypto/entities/lwe_public_key.rs | 2 +- tfhe/src/core_crypto/entities/mod.rs | 6 + .../entities/seeded_glwe_ciphertext.rs | 2 +- .../entities/seeded_lwe_ciphertext_list.rs | 4 +- .../entities/seeded_lwe_compact_public_key.rs | 214 +++++ .../src/high_level_api/booleans/client_key.rs | 20 +- tfhe/src/high_level_api/config.rs | 2 +- tfhe/src/high_level_api/details.rs | 18 + tfhe/src/high_level_api/integers/keys.rs | 159 ++++ tfhe/src/high_level_api/integers/mod.rs | 15 +- tfhe/src/high_level_api/integers/tests.rs | 99 ++- .../src/high_level_api/integers/types/base.rs | 21 + .../high_level_api/integers/types/compact.rs | 119 +++ tfhe/src/high_level_api/integers/types/mod.rs | 14 +- .../high_level_api/integers/types/static_.rs | 8 + tfhe/src/high_level_api/keys/client.rs | 15 + tfhe/src/high_level_api/keys/mod.rs | 3 +- tfhe/src/high_level_api/keys/public.rs | 75 ++ tfhe/src/high_level_api/mod.rs | 37 +- .../high_level_api/shortints/client_key.rs | 21 + tfhe/src/high_level_api/tests.rs | 39 +- tfhe/src/integer/ciphertext/mod.rs | 52 ++ tfhe/src/integer/encryption.rs | 46 +- tfhe/src/integer/mod.rs | 4 +- tfhe/src/integer/public_key/compact.rs | 154 ++++ tfhe/src/integer/public_key/mod.rs | 5 + tfhe/src/integer/public_key/tests.rs | 53 +- tfhe/src/js_on_wasm_api/boolean.rs | 16 +- .../js_on_wasm_api/high_level_api/config.rs | 8 + .../js_on_wasm_api/high_level_api/integers.rs | 251 +++++- .../src/js_on_wasm_api/high_level_api/keys.rs | 68 ++ tfhe/src/js_on_wasm_api/mod.rs | 29 - tfhe/src/js_on_wasm_api/shortint.rs | 93 ++- tfhe/src/shortint/ciphertext/mod.rs | 60 ++ tfhe/src/shortint/engine/mod.rs | 6 +- tfhe/src/shortint/keycache.rs | 2 + tfhe/src/shortint/mod.rs | 2 + tfhe/src/shortint/parameters/mod.rs | 21 + tfhe/src/shortint/prelude.rs | 5 +- tfhe/src/shortint/public_key/compact.rs | 255 ++++++ tfhe/src/shortint/public_key/mod.rs | 5 + tfhe/src/shortint/server_key/mod.rs | 2 +- tfhe/src/shortint/server_key/tests.rs | 130 +++ tfhe/web_wasm_parallel_tests/favicon.ico | Bin 0 -> 258062 bytes tfhe/web_wasm_parallel_tests/index.html | 9 + tfhe/web_wasm_parallel_tests/index.js | 15 +- tfhe/web_wasm_parallel_tests/package.json | 4 +- tfhe/web_wasm_parallel_tests/test/common.mjs | 4 +- .../test/compact-public-key.test.js | 26 + .../test/public-key-small.test.js | 7 - tfhe/web_wasm_parallel_tests/worker.js | 223 ++++- 91 files changed, 5849 insertions(+), 474 deletions(-) create mode 100644 tfhe/c_api_tests/test_high_level_custom_integers.c create mode 100644 tfhe/examples/integer_compact_pk_ct_sizes.rs create mode 100644 tfhe/src/c_api/high_level_api/u128.rs create mode 100644 tfhe/src/core_crypto/algorithms/lwe_compact_ciphertext_list_expansion.rs create mode 100644 tfhe/src/core_crypto/algorithms/lwe_compact_public_key_generation.rs create mode 100644 tfhe/src/core_crypto/algorithms/seeded_lwe_compact_public_key_decompression.rs create mode 100644 tfhe/src/core_crypto/algorithms/test/lwe_compact_public_key_generation.rs create mode 100644 tfhe/src/core_crypto/entities/lwe_compact_ciphertext_list.rs create mode 100644 tfhe/src/core_crypto/entities/lwe_compact_public_key.rs create mode 100644 tfhe/src/core_crypto/entities/seeded_lwe_compact_public_key.rs create mode 100644 tfhe/src/high_level_api/integers/types/compact.rs create mode 100644 tfhe/src/integer/public_key/compact.rs create mode 100644 tfhe/src/shortint/public_key/compact.rs create mode 100644 tfhe/web_wasm_parallel_tests/favicon.ico create mode 100644 tfhe/web_wasm_parallel_tests/test/compact-public-key.test.js delete mode 100644 tfhe/web_wasm_parallel_tests/test/public-key-small.test.js diff --git a/.gitignore b/.gitignore index d71c878fd..d11c01282 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ target/ # Some of our bench outputs /tfhe/benchmarks_parameters +/tfhe/shortint_key_sizes.csv diff --git a/Makefile b/Makefile index 36e59ddf5..a02b8f141 100644 --- a/Makefile +++ b/Makefile @@ -232,7 +232,13 @@ test_boolean: install_rs_build_toolchain --features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean:: .PHONY: test_c_api # Run the tests for the C API -test_c_api: build_c_api +test_c_api: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ + --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api \ + -p tfhe \ + c_api + + "$(MAKE)" build_c_api ./scripts/c_api_tests.sh .PHONY: test_shortint_ci # Run the tests for shortint ci @@ -307,13 +313,15 @@ format_doc_latex: @printf "\n===============================\n" .PHONY: check_compile_tests # Build tests in debug without running them -check_compile_tests: build_c_api +check_compile_tests: RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \ --features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache \ -p tfhe - @if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \ - ./scripts/c_api_tests.sh --build-only; \ - fi + + @if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \ + "$(MAKE)" build_c_api; \ + ./scripts/c_api_tests.sh --build-only; \ + fi .PHONY: build_nodejs_test_docker # Build a docker image with tools to run nodejs tests for wasm API build_nodejs_test_docker: diff --git a/tfhe/Cargo.toml b/tfhe/Cargo.toml index dfd9df1ed..8eb8b1755 100644 --- a/tfhe/Cargo.toml +++ b/tfhe/Cargo.toml @@ -83,7 +83,12 @@ experimental-force_fft_algo_dif4 = [] __c_api = ["cbindgen", "bincode"] boolean-c-api = ["boolean", "__c_api"] shortint-c-api = ["shortint", "__c_api"] -high-level-c-api = ["boolean", "shortint", "integer", "__c_api"] +high-level-c-api = [ + "boolean-c-api", + "shortint-c-api", + "integer", + "__c_api" +] __wasm_api = [ "wasm-bindgen", @@ -200,6 +205,10 @@ required-features = ["integer", "internal-keycache"] name = "shortint_key_sizes" required-features = ["shortint", "internal-keycache"] +[[example]] +name = "integer_compact_pk_ct_sizes" +required-features = ["integer", "internal-keycache"] + [[example]] name = "micro_bench_and" required-features = ["boolean"] diff --git a/tfhe/c_api_tests/test_high_level_128_bits.c b/tfhe/c_api_tests/test_high_level_128_bits.c index 3d2d00448..27598afef 100644 --- a/tfhe/c_api_tests/test_high_level_128_bits.c +++ b/tfhe/c_api_tests/test_high_level_128_bits.c @@ -9,22 +9,24 @@ int uint128_client_key(const ClientKey *client_key) { FheUint128 *lhs = NULL; FheUint128 *rhs = NULL; FheUint128 *result = NULL; + U128 lhs_clear = {10, 20}; + U128 rhs_clear = {1, 2}; + U128 result_clear = {0}; - ok = fhe_uint128_try_encrypt_with_client_key_u128(10, 20, client_key, &lhs); + ok = fhe_uint128_try_encrypt_with_client_key_u128(lhs_clear, client_key, &lhs); assert(ok == 0); - ok = fhe_uint128_try_encrypt_with_client_key_u128(1, 2, client_key, &rhs); + ok = fhe_uint128_try_encrypt_with_client_key_u128(rhs_clear, client_key, &rhs); assert(ok == 0); ok = fhe_uint128_sub(lhs, rhs, &result); assert(ok == 0); - uint64_t w0, w1; - ok = fhe_uint128_decrypt(result, client_key, &w0, &w1); + ok = fhe_uint128_decrypt(result, client_key, &result_clear); assert(ok == 0); - assert(w0 == 9); - assert(w1 == 18); + assert(result_clear.w0 == 9); + assert(result_clear.w1 == 18); fhe_uint128_destroy(lhs); fhe_uint128_destroy(rhs); @@ -37,22 +39,24 @@ int uint128_encrypt_trivial(const ClientKey *client_key) { FheUint128 *lhs = NULL; FheUint128 *rhs = NULL; FheUint128 *result = NULL; - - ok = fhe_uint128_try_encrypt_trivial_u128(10, 20, &lhs); + U128 lhs_clear = {10, 20}; + U128 rhs_clear = {1, 2}; + U128 result_clear = {0}; + + ok = fhe_uint128_try_encrypt_trivial_u128(lhs_clear, &lhs); assert(ok == 0); - ok = fhe_uint128_try_encrypt_trivial_u128(1, 2, &rhs); + ok = fhe_uint128_try_encrypt_trivial_u128(rhs_clear, &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); + ok = fhe_uint128_decrypt(result, client_key, &result_clear); assert(ok == 0); - assert(w0 == 9); - assert(w1 == 18); + assert(result_clear.w0 == 9); + assert(result_clear.w1 == 18); fhe_uint128_destroy(lhs); fhe_uint128_destroy(rhs); @@ -65,22 +69,24 @@ int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key) FheUint128 *lhs = NULL; FheUint128 *rhs = NULL; FheUint128 *result = NULL; + U128 lhs_clear = {10, 20}; + U128 rhs_clear = {1, 2}; + U128 result_clear = {0}; - ok = fhe_uint128_try_encrypt_with_public_key_u128(1, 2, public_key, &lhs); + ok = fhe_uint128_try_encrypt_with_public_key_u128(lhs_clear, public_key, &lhs); assert(ok == 0); - ok = fhe_uint128_try_encrypt_with_public_key_u128(10, 20, public_key, &rhs); + ok = fhe_uint128_try_encrypt_with_public_key_u128(rhs_clear, public_key, &rhs); assert(ok == 0); ok = fhe_uint128_add(lhs, rhs, &result); assert(ok == 0); - uint64_t w0, w1; - ok = fhe_uint128_decrypt(result, client_key, &w0, &w1); + ok = fhe_uint128_decrypt(result, client_key, &result_clear); assert(ok == 0); - assert(w0 == 11); - assert(w1 == 22); + assert(result_clear.w0 == 11); + assert(result_clear.w1 == 22); fhe_uint128_destroy(lhs); fhe_uint128_destroy(rhs); diff --git a/tfhe/c_api_tests/test_high_level_256_bits.c b/tfhe/c_api_tests/test_high_level_256_bits.c index 9eebd2bb5..e475ada9a 100644 --- a/tfhe/c_api_tests/test_high_level_256_bits.c +++ b/tfhe/c_api_tests/test_high_level_256_bits.c @@ -10,14 +10,9 @@ int uint256_client_key(const ClientKey *client_key) { FheUint256 *rhs = NULL; FheUint256 *result = NULL; FheUint64 *cast_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); + U256 lhs_clear = {1 , 2, 3, 4}; + U256 rhs_clear = {5, 6, 7, 8}; + U256 result_clear = { 0 }; ok = fhe_uint256_try_encrypt_with_client_key_u256(lhs_clear, client_key, &lhs); assert(ok == 0); @@ -31,14 +26,10 @@ int uint256_client_key(const ClientKey *client_key) { 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); + assert(result_clear.w0 == 6); + assert(result_clear.w1 == 8); + assert(result_clear.w2 == 10); + assert(result_clear.w3 == 12); // try some casting ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result); @@ -48,9 +39,6 @@ int uint256_client_key(const ClientKey *client_key) { assert(ok == 0); assert(u64_clear == 6); - u256_destroy(lhs_clear); - u256_destroy(rhs_clear); - u256_destroy(result_clear); fhe_uint256_destroy(lhs); fhe_uint256_destroy(rhs); fhe_uint256_destroy(result); @@ -63,14 +51,9 @@ int uint256_encrypt_trivial(const ClientKey *client_key) { 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); + U256 lhs_clear = {1 , 2, 3, 4}; + U256 rhs_clear = {5, 6, 7, 8}; + U256 result_clear = { 0 }; ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs); assert(ok == 0); @@ -84,18 +67,11 @@ int uint256_encrypt_trivial(const ClientKey *client_key) { 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(result_clear.w0 == 6); + assert(result_clear.w1 == 8); + assert(result_clear.w2 == 10); + assert(result_clear.w3 == 12); - 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); @@ -107,14 +83,9 @@ int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key) FheUint256 *lhs = NULL; FheUint256 *rhs = NULL; FheUint256 *result = NULL; - U256 *lhs_clear = NULL; - U256 *rhs_clear = NULL; - U256 *result_clear = NULL; - - ok = u256_from_u64_words(5, 6, 7, 8, &lhs_clear); - assert(ok == 0); - ok = u256_from_u64_words(1, 2, 3, 4, &rhs_clear); - assert(ok == 0); + U256 lhs_clear = {5, 6, 7, 8}; + U256 rhs_clear = {1 , 2, 3, 4}; + U256 result_clear = { 0 }; ok = fhe_uint256_try_encrypt_with_public_key_u256(lhs_clear, public_key, &lhs); assert(ok == 0); @@ -128,18 +99,11 @@ int uint256_public_key(const ClientKey *client_key, const PublicKey *public_key) 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(result_clear.w0 == 4); + assert(result_clear.w1 == 4); + assert(result_clear.w2 == 4); + assert(result_clear.w3 == 4); - assert(w0 == 4); - assert(w1 == 4); - assert(w2 == 4); - assert(w3 == 4); - - u256_destroy(lhs_clear); - u256_destroy(rhs_clear); - u256_destroy(result_clear); fhe_uint256_destroy(lhs); fhe_uint256_destroy(rhs); fhe_uint256_destroy(result); diff --git a/tfhe/c_api_tests/test_high_level_custom_integers.c b/tfhe/c_api_tests/test_high_level_custom_integers.c new file mode 100644 index 000000000..79a50f91b --- /dev/null +++ b/tfhe/c_api_tests/test_high_level_custom_integers.c @@ -0,0 +1,213 @@ +#include + +#include +#include +#include + +int uint256_client_key(const ClientKey *client_key) { + int ok; + FheUint256 *lhs = NULL; + FheUint256 *rhs = NULL; + FheUint256 *result = NULL; + FheUint64 *cast_result = NULL; + U256 lhs_clear = {1, 2, 3, 4}; + U256 rhs_clear = {5, 6, 7, 8}; + U256 result_clear = {0}; + + ok = fhe_uint256_try_encrypt_with_client_key_u256(lhs_clear, client_key, &lhs); + assert(ok == 0); + + ok = fhe_uint256_try_encrypt_with_client_key_u256(rhs_clear, client_key, &rhs); + assert(ok == 0); + + ok = fhe_uint256_add(lhs, rhs, &result); + assert(ok == 0); + + ok = fhe_uint256_decrypt(result, client_key, &result_clear); + assert(ok == 0); + + assert(result_clear.w0 == 6); + assert(result_clear.w1 == 8); + assert(result_clear.w2 == 10); + assert(result_clear.w3 == 12); + + // try some casting + ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result); + assert(ok == 0); + uint64_t u64_clear; + ok = fhe_uint64_decrypt(cast_result, client_key, &u64_clear); + assert(ok == 0); + assert(u64_clear == 6); + + fhe_uint256_destroy(lhs); + fhe_uint256_destroy(rhs); + fhe_uint256_destroy(result); + fhe_uint64_destroy(cast_result); + return ok; +} + +int uint256_encrypt_trivial(const ClientKey *client_key) { + int ok; + FheUint256 *lhs = NULL; + FheUint256 *rhs = NULL; + FheUint256 *result = NULL; + U256 lhs_clear = {1, 2, 3, 4}; + U256 rhs_clear = {5, 6, 7, 8}; + U256 result_clear = {0}; + + ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs); + assert(ok == 0); + + ok = fhe_uint256_try_encrypt_trivial_u256(rhs_clear, &rhs); + assert(ok == 0); + + ok = fhe_uint256_add(lhs, rhs, &result); + assert(ok == 0); + + ok = fhe_uint256_decrypt(result, client_key, &result_clear); + assert(ok == 0); + + assert(result_clear.w0 == 6); + assert(result_clear.w1 == 8); + assert(result_clear.w2 == 10); + assert(result_clear.w3 == 12); + + fhe_uint256_destroy(lhs); + fhe_uint256_destroy(rhs); + fhe_uint256_destroy(result); + return ok; +} + +int uint256_public_key(const ClientKey *client_key, + const CompressedCompactPublicKey *compressed_public_key) { + int ok; + CompactPublicKey *public_key = NULL; + FheUint256 *lhs = NULL; + FheUint256 *rhs = NULL; + FheUint256 *result = NULL; + CompactFheUint256List *list = NULL; + + U256 result_clear = {0}; + U256 clears[2] = {{5, 6, 7, 8}, {1, 2, 3, 4}}; + + ok = compressed_compact_public_key_decompress(compressed_public_key, &public_key); + assert(ok == 0); + + // Compact list example + { + ok = compact_fhe_uint256_list_try_encrypt_with_compact_public_key_u256( + &clears[0], 2, public_key, &list); + assert(ok == 0); + + size_t len = 0; + ok = compact_fhe_uint256_list_len(list, &len); + assert(ok == 0); + assert(len == 2); + + FheUint256 *expand_output[2] = {NULL}; + ok = compact_fhe_uint256_list_expand(list, &expand_output[0], 2); + assert(ok == 0); + + // transfer ownership + lhs = expand_output[0]; + rhs = expand_output[1]; + + ok = fhe_uint256_sub(lhs, rhs, &result); + assert(ok == 0); + + ok = fhe_uint256_decrypt(result, client_key, &result_clear); + assert(ok == 0); + + assert(result_clear.w0 == 4); + assert(result_clear.w1 == 4); + assert(result_clear.w2 == 4); + assert(result_clear.w3 == 4); + + fhe_uint256_destroy(lhs); + fhe_uint256_destroy(rhs); + fhe_uint256_destroy(result); + } + + { + ok = fhe_uint256_try_encrypt_with_compact_public_key_u256(clears[0], public_key, &lhs); + assert(ok == 0); + + ok = fhe_uint256_try_encrypt_with_compact_public_key_u256(clears[1], public_key, &rhs); + assert(ok == 0); + + ok = fhe_uint256_sub(lhs, rhs, &result); + assert(ok == 0); + + ok = fhe_uint256_decrypt(result, client_key, &result_clear); + assert(ok == 0); + + assert(result_clear.w0 == 4); + assert(result_clear.w1 == 4); + assert(result_clear.w2 == 4); + assert(result_clear.w3 == 4); + + fhe_uint256_destroy(lhs); + fhe_uint256_destroy(rhs); + fhe_uint256_destroy(result); + } + + compact_public_key_destroy(public_key); + return ok; +} + +int main(void) { + int ok = 0; + { + ConfigBuilder *builder; + Config *config; + + config_builder_all_disabled(&builder); + config_builder_enable_custom_integers(&builder, SHORTINT_PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + config_builder_build(builder, &config); + + ClientKey *client_key = NULL; + ServerKey *server_key = NULL; + CompressedCompactPublicKey *compressed_public_key = NULL; + + generate_keys(config, &client_key, &server_key); + compressed_compact_public_key_new(client_key, &compressed_public_key); + + set_server_key(server_key); + + uint256_client_key(client_key); + uint256_encrypt_trivial(client_key); + uint256_public_key(client_key, compressed_public_key); + + client_key_destroy(client_key); + compressed_compact_public_key_destroy(compressed_public_key); + server_key_destroy(server_key); + } + + { + ConfigBuilder *builder; + Config *config; + + config_builder_all_disabled(&builder); + config_builder_enable_custom_integers(&builder, + SHORTINT_PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + config_builder_build(builder, &config); + + ClientKey *client_key = NULL; + ServerKey *server_key = NULL; + CompressedCompactPublicKey *compressed_public_key = NULL; + + generate_keys(config, &client_key, &server_key); + compressed_compact_public_key_new(client_key, &compressed_public_key); + + set_server_key(server_key); + + uint256_client_key(client_key); + uint256_encrypt_trivial(client_key); + uint256_public_key(client_key, compressed_public_key); + + client_key_destroy(client_key); + compressed_compact_public_key_destroy(compressed_public_key); + server_key_destroy(server_key); + } + return ok; +} diff --git a/tfhe/c_api_tests/test_micro_bench_and.c b/tfhe/c_api_tests/test_micro_bench_and.c index 898d6f23f..c077313e5 100644 --- a/tfhe/c_api_tests/test_micro_bench_and.c +++ b/tfhe/c_api_tests/test_micro_bench_and.c @@ -43,6 +43,8 @@ void micro_bench_and() { destroy_boolean_client_key(cks); destroy_boolean_server_key(sks); + destroy_boolean_ciphertext(ct_left); + destroy_boolean_ciphertext(ct_right); } int main(void) { diff --git a/tfhe/c_api_tests/test_shortint_keygen.c b/tfhe/c_api_tests/test_shortint_keygen.c index 9b6b2bbfc..fb57e3125 100644 --- a/tfhe/c_api_tests/test_shortint_keygen.c +++ b/tfhe/c_api_tests/test_shortint_keygen.c @@ -8,16 +8,13 @@ void test_predefined_keygen_w_serde(void) { ShortintClientKey *cks = NULL; ShortintServerKey *sks = NULL; - ShortintParameters *params = NULL; ShortintCiphertext *ct = NULL; Buffer ct_ser_buffer = {.pointer = NULL, .length = 0}; ShortintCiphertext *deser_ct = NULL; ShortintCompressedCiphertext *cct = NULL; ShortintCompressedCiphertext *deser_cct = NULL; ShortintCiphertext *decompressed_ct = NULL; - - int get_params_ok = shortint_get_parameters(2, 2, ¶ms); - assert(get_params_ok == 0); + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2; int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks); assert(gen_keys_ok == 0); @@ -67,7 +64,6 @@ void test_predefined_keygen_w_serde(void) { destroy_shortint_client_key(cks); destroy_shortint_server_key(sks); - destroy_shortint_parameters(params); destroy_shortint_ciphertext(ct); destroy_shortint_ciphertext(deser_ct); destroy_shortint_compressed_ciphertext(cct); @@ -79,11 +75,8 @@ void test_predefined_keygen_w_serde(void) { void test_server_key_trivial_encrypt(void) { ShortintClientKey *cks = NULL; ShortintServerKey *sks = NULL; - ShortintParameters *params = NULL; ShortintCiphertext *ct = NULL; - - int get_params_ok = shortint_get_parameters(2, 2, ¶ms); - assert(get_params_ok == 0); + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2; int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks); assert(gen_keys_ok == 0); @@ -99,24 +92,32 @@ void test_server_key_trivial_encrypt(void) { destroy_shortint_client_key(cks); destroy_shortint_server_key(sks); - destroy_shortint_parameters(params); destroy_shortint_ciphertext(ct); } void test_custom_keygen(void) { ShortintClientKey *cks = NULL; ShortintServerKey *sks = NULL; - ShortintParameters *params = NULL; - - int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 2, 64, - ShortintEncryptionKeyChoiceBig, ¶ms); - assert(params_ok == 0); + ShortintPBSParameters params = { + .lwe_dimension = 10, + .glwe_dimension = 1, + .polynomial_size = 1024, + .lwe_modular_std_dev = 10e-100, + .glwe_modular_std_dev = 10e-100, + .pbs_base_log = 2, + .pbs_level = 3, + .ks_base_log = 2, + .ks_level = 3, + .message_modulus = 2, + .carry_modulus = 2, + .modulus_power_of_2_exponent = 64, + .encryption_key_choice = ShortintEncryptionKeyChoiceBig, + }; int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks); assert(gen_keys_ok == 0); - destroy_shortint_parameters(params); destroy_shortint_client_key(cks); destroy_shortint_server_key(sks); } @@ -125,12 +126,9 @@ void test_public_keygen(ShortintPublicKeyKind pk_kind) { ShortintClientKey *cks = NULL; ShortintPublicKey *pks = NULL; ShortintPublicKey *pks_deser = NULL; - ShortintParameters *params = NULL; ShortintCiphertext *ct = NULL; Buffer pks_ser_buff = {.pointer = NULL, .length = 0}; - - int get_params_ok = shortint_get_parameters(2, 2, ¶ms); - assert(get_params_ok == 0); + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2; int gen_keys_ok = shortint_gen_client_key(params, &cks); assert(gen_keys_ok == 0); @@ -156,7 +154,6 @@ void test_public_keygen(ShortintPublicKeyKind pk_kind) { assert(result == 2); - destroy_shortint_parameters(params); destroy_shortint_client_key(cks); destroy_shortint_public_key(pks); destroy_shortint_public_key(pks_deser); @@ -168,11 +165,8 @@ void test_compressed_public_keygen(ShortintPublicKeyKind pk_kind) { ShortintClientKey *cks = NULL; ShortintCompressedPublicKey *cpks = NULL; ShortintPublicKey *pks = NULL; - ShortintParameters *params = NULL; ShortintCiphertext *ct = NULL; - - int get_params_ok = shortint_get_parameters(2, 2, ¶ms); - assert(get_params_ok == 0); + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2; int gen_keys_ok = shortint_gen_client_key(params, &cks); assert(gen_keys_ok == 0); @@ -203,7 +197,6 @@ void test_compressed_public_keygen(ShortintPublicKeyKind pk_kind) { assert(result == 2); - destroy_shortint_parameters(params); destroy_shortint_client_key(cks); destroy_shortint_compressed_public_key(cpks); destroy_shortint_public_key(pks); diff --git a/tfhe/c_api_tests/test_shortint_pbs.c b/tfhe/c_api_tests/test_shortint_pbs.c index 478a01346..93e12ca8a 100644 --- a/tfhe/c_api_tests/test_shortint_pbs.c +++ b/tfhe/c_api_tests/test_shortint_pbs.c @@ -41,10 +41,7 @@ void test_shortint_pbs_2_bits_message(void) { ShortintPBSLookupTable *accumulator = NULL; ShortintClientKey *cks = NULL; ShortintServerKey *sks = NULL; - ShortintParameters *params = NULL; - - int get_params_ok = shortint_get_parameters(2, 2, ¶ms); - assert(get_params_ok == 0); + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2; int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks); assert(gen_keys_ok == 0); @@ -111,17 +108,13 @@ void test_shortint_pbs_2_bits_message(void) { destroy_shortint_pbs_accumulator(accumulator); destroy_shortint_client_key(cks); destroy_shortint_server_key(sks); - destroy_shortint_parameters(params); } void test_shortint_bivariate_pbs_2_bits_message(void) { ShortintBivariatePBSLookupTable *accumulator = NULL; ShortintClientKey *cks = NULL; ShortintServerKey *sks = NULL; - ShortintParameters *params = NULL; - - int get_params_ok = shortint_get_parameters(2, 2, ¶ms); - assert(get_params_ok == 0); + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2; int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks); assert(gen_keys_ok == 0); @@ -187,7 +180,6 @@ void test_shortint_bivariate_pbs_2_bits_message(void) { destroy_shortint_bivariate_pbs_accumulator(accumulator); destroy_shortint_client_key(cks); destroy_shortint_server_key(sks); - destroy_shortint_parameters(params); } int main(void) { diff --git a/tfhe/c_api_tests/test_shortint_server_key.c b/tfhe/c_api_tests/test_shortint_server_key.c index 987b0d196..2fea5f101 100644 --- a/tfhe/c_api_tests/test_shortint_server_key.c +++ b/tfhe/c_api_tests/test_shortint_server_key.c @@ -419,10 +419,10 @@ void test_server_key(void) { ShortintCompressedServerKey *deser_csks = NULL; Buffer sks_ser_buffer = {.pointer = NULL, .length = 0}; ShortintServerKey *deser_sks = NULL; - ShortintParameters *params = NULL; ShortintClientKey *cks_small = NULL; ShortintServerKey *sks_small = NULL; - ShortintParameters *params_small = NULL; + ShortintPBSParameters params = { 0 }; + ShortintPBSParameters params_small = { 0 }; const uint32_t message_bits = 2; const uint32_t carry_bits = 2; @@ -736,8 +736,6 @@ void test_server_key(void) { destroy_shortint_client_key(deser_cks); destroy_shortint_compressed_server_key(deser_csks); destroy_shortint_server_key(deser_sks); - destroy_shortint_parameters(params); - destroy_shortint_parameters(params_small); destroy_buffer(&cks_ser_buffer); destroy_buffer(&csks_ser_buffer); destroy_buffer(&sks_ser_buffer); diff --git a/tfhe/examples/integer_compact_pk_ct_sizes.rs b/tfhe/examples/integer_compact_pk_ct_sizes.rs new file mode 100644 index 000000000..c9db6293e --- /dev/null +++ b/tfhe/examples/integer_compact_pk_ct_sizes.rs @@ -0,0 +1,92 @@ +use rand::Rng; + +use tfhe::core_crypto::commons::numeric::Numeric; +use tfhe::integer::block_decomposition::{DecomposableInto, RecomposableFrom}; +use tfhe::integer::public_key::{CompactPublicKeyBig, CompactPublicKeySmall}; +use tfhe::integer::{gen_keys, U256}; +use tfhe::shortint::keycache::NamedParam; +use tfhe::shortint::parameters::*; + +pub fn main() { + fn size_func + RecomposableFrom + From>() { + let mut rng = rand::thread_rng(); + let num_bits = Scalar::BITS; + + let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK; + { + println!("Sizes for: {} and {num_bits} bits", params.name()); + let (cks, _) = gen_keys(params); + let pk = CompactPublicKeyBig::new(&cks); + + println!("PK size: {} bytes", bincode::serialize(&pk).unwrap().len()); + + let num_block = + (num_bits as f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize; + + const MAX_CT: usize = 20; + + let mut clear_vec = Vec::with_capacity(MAX_CT); + // 5 inputs to a smart contract + let num_ct_for_this_iter = 5; + clear_vec.truncate(0); + for _ in 0..num_ct_for_this_iter { + let clear = rng.gen::(); + clear_vec.push(Scalar::from(clear)); + } + + let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block); + + println!( + "Compact CT list for {num_ct_for_this_iter} CTs: {} bytes", + bincode::serialize(&compact_encrypted_list).unwrap().len() + ); + + let ciphertext_vec = compact_encrypted_list.expand(); + + for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) { + let decrypted: Scalar = cks.decrypt_radix(ciphertext); + assert_eq!(decrypted, clear); + } + } + + let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK; + { + println!("Sizes for: {} and {num_bits} bits", params.name()); + let (cks, _) = gen_keys(params); + let pk = CompactPublicKeySmall::new(&cks); + + println!("PK size: {} bytes", bincode::serialize(&pk).unwrap().len()); + + let num_block = + (num_bits as f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize; + + const MAX_CT: usize = 20; + + let mut clear_vec = Vec::with_capacity(MAX_CT); + // 5 inputs to a smart contract + let num_ct_for_this_iter = 5; + clear_vec.truncate(0); + for _ in 0..num_ct_for_this_iter { + let clear = rng.gen::(); + clear_vec.push(Scalar::from(clear)); + } + + let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block); + + println!( + "Compact CT list for {num_ct_for_this_iter} CTs: {} bytes", + bincode::serialize(&compact_encrypted_list).unwrap().len() + ); + + let ciphertext_vec = compact_encrypted_list.expand(); + + for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) { + let decrypted: Scalar = cks.decrypt_radix(ciphertext); + assert_eq!(decrypted, clear); + } + } + } + + size_func::(); + size_func::(); +} diff --git a/tfhe/js_on_wasm_tests/test-hlapi.js b/tfhe/js_on_wasm_tests/test-hlapi.js index d5fd0c58c..7c0ebb2b7 100644 --- a/tfhe/js_on_wasm_tests/test-hlapi.js +++ b/tfhe/js_on_wasm_tests/test-hlapi.js @@ -2,17 +2,24 @@ const test = require('node:test'); const assert = require('node:assert').strict; const { performance } = require('perf_hooks'); const { + ShortintParametersName, + ShortintParameters, TfheClientKey, TfhePublicKey, TfheCompressedPublicKey, + TfheCompactPublicKey, TfheCompressedServerKey, TfheConfigBuilder, CompressedFheUint8, FheUint8, FheUint32, + CompactFheUint32, + CompactFheUint32List, CompressedFheUint128, FheUint128, CompressedFheUint256, + CompactFheUint256, + CompactFheUint256List, FheUint256 } = require("../pkg/tfhe.js"); @@ -306,3 +313,243 @@ test('hlapi_public_key_encrypt_decrypt_uint256_small', (t) => { assert.deepStrictEqual(deserialized_decrypted, U256_MAX); }); + + +////////////////////////////////////////////////////////////////////////////// +/// 32 bits compact +////////////////////////////////////////////////////////////////////////////// + +function hlapi_compact_public_key_encrypt_decrypt_uint32_single(config) { + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let encrypted = FheUint32.encrypt_with_compact_public_key(U32_MAX, publicKey); + let decrypted = encrypted.decrypt(clientKey); + assert.deepStrictEqual(decrypted, U32_MAX); + + let serialized = encrypted.serialize(); + let deserialized = FheUint32.deserialize(serialized); + let deserialized_decrypted = deserialized.decrypt(clientKey); + assert.deepStrictEqual(deserialized_decrypted, U32_MAX); +} + +test('hlapi_compact_public_key_encrypt_decrypt_uint32_big_single', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint32_single(config); +}); + +test('hlapi_compact_public_key_encrypt_decrypt_uint32_small_single', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint32_single(config); +}); + +function hlapi_compact_public_key_encrypt_decrypt_uint32_single_compact(config) { + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let compact_encrypted = CompactFheUint32.encrypt_with_compact_public_key(U32_MAX, publicKey); + let encrypted = compact_encrypted.expand(); + let decrypted = encrypted.decrypt(clientKey); + assert.deepStrictEqual(decrypted, U32_MAX); + + let serialized = compact_encrypted.serialize(); + let deserialized = CompactFheUint32.deserialize(serialized); + let deserialized_decrypted = deserialized.expand().decrypt(clientKey); + assert.deepStrictEqual(deserialized_decrypted, U32_MAX); +} + +test('hlapi_compact_public_key_encrypt_decrypt_uint32_small_single_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint32_single_compact(config); +}); + +test('hlapi_compact_public_key_encrypt_decrypt_uint32_big_single_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint32_single_compact(config); +}); + +function hlapi_compact_public_key_encrypt_decrypt_uint32_list_compact(config) { + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let values = [0, 1, 2394, U32_MAX]; + + let compact_list = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey); + + { + let encrypted_list = compact_list.expand(); + + assert.deepStrictEqual(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert.deepStrictEqual(decrypted, values[i]); + } + } + + let serialized_list = compact_list.serialize(); + let deserialized_list = CompactFheUint32List.deserialize(serialized_list); + let encrypted_list = deserialized_list.expand(); + assert.deepStrictEqual(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert.deepStrictEqual(decrypted, values[i]); + } +} + +test('hlapi_compact_public_key_encrypt_decrypt_uint32_small_list_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint32_list_compact(config); +}); + +test('hlapi_compact_public_key_encrypt_decrypt_uint32_big_list_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint32_list_compact(config); +}); + + +////////////////////////////////////////////////////////////////////////////// +/// 256 bits compact +////////////////////////////////////////////////////////////////////////////// + +function hlapi_compact_public_key_encrypt_decrypt_uint256_single(config) { + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let encrypted = FheUint256.encrypt_with_compact_public_key(U256_MAX, publicKey); + let decrypted = encrypted.decrypt(clientKey); + assert.deepStrictEqual(decrypted, U256_MAX); + + let serialized = encrypted.serialize(); + let deserialized = FheUint256.deserialize(serialized); + let deserialized_decrypted = deserialized.decrypt(clientKey); + assert.deepStrictEqual(deserialized_decrypted, U256_MAX); +} + +test('hlapi_compact_public_key_encrypt_decrypt_uint256_big_single', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint256_single(config); +}); + +test('hlapi_compact_public_key_encrypt_decrypt_uint256_small_single', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint256_single(config); +}); + +function hlapi_compact_public_key_encrypt_decrypt_uint256_single_compact(config) { + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let compact_encrypted = CompactFheUint256.encrypt_with_compact_public_key(U256_MAX, publicKey); + let encrypted = compact_encrypted.expand(); + let decrypted = encrypted.decrypt(clientKey); + assert.deepStrictEqual(decrypted, U256_MAX); + + let serialized = compact_encrypted.serialize(); + let deserialized = CompactFheUint256.deserialize(serialized); + let deserialized_decrypted = deserialized.expand().decrypt(clientKey); + assert.deepStrictEqual(deserialized_decrypted, U256_MAX); +} + +test('hlapi_compact_public_key_encrypt_decrypt_uint256_small_single_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint256_single_compact(config); +}); + +test('hlapi_compact_public_key_encrypt_decrypt_uint256_big_single_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint256_single_compact(config); +}); + +function hlapi_compact_public_key_encrypt_decrypt_uint256_list_compact(config) { + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let values = [BigInt(0), BigInt(1), BigInt(2394), BigInt(2309840239), BigInt(U32_MAX), U256_MAX, U128_MAX]; + + let compact_list = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey); + + { + let encrypted_list = compact_list.expand(); + + assert.deepStrictEqual(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert.deepStrictEqual(decrypted, values[i]); + } + } + + let serialized_list = compact_list.serialize(); + let deserialized_list = CompactFheUint256List.deserialize(serialized_list); + let encrypted_list = deserialized_list.expand(); + assert.deepStrictEqual(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert.deepStrictEqual(decrypted, values[i]); + } +} + +test('hlapi_compact_public_key_encrypt_decrypt_uint256_small_list_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint256_list_compact(config); +}); + +test('hlapi_compact_public_key_encrypt_decrypt_uint256_big_list_compact', (t) => { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + + hlapi_compact_public_key_encrypt_decrypt_uint256_list_compact(config); +}); diff --git a/tfhe/src/boolean/engine/bootstrapping.rs b/tfhe/src/boolean/engine/bootstrapping.rs index 717c0b8fa..08c1a4b0c 100644 --- a/tfhe/src/boolean/engine/bootstrapping.rs +++ b/tfhe/src/boolean/engine/bootstrapping.rs @@ -94,7 +94,7 @@ impl ServerKey { } pub fn bootstrapping_key_size_bytes(&self) -> usize { - self.bootstrapping_key_size_elements() * std::mem::size_of::() + std::mem::size_of_val(self.bootstrapping_key.as_view().data()) } pub fn key_switching_key_size_elements(&self) -> usize { @@ -102,7 +102,7 @@ impl ServerKey { } pub fn key_switching_key_size_bytes(&self) -> usize { - self.key_switching_key_size_elements() * std::mem::size_of::() + std::mem::size_of_val(self.key_switching_key.as_ref()) } } diff --git a/tfhe/src/c_api/high_level_api/config.rs b/tfhe/src/c_api/high_level_api/config.rs index b4654e96d..8c47005b5 100644 --- a/tfhe/src/c_api/high_level_api/config.rs +++ b/tfhe/src/c_api/high_level_api/config.rs @@ -72,6 +72,20 @@ define_enable_default_fn!(integers); #[cfg(feature = "integer")] define_enable_default_fn!(integers @small); +#[no_mangle] +pub unsafe extern "C" fn config_builder_enable_custom_integers( + builder: *mut *mut ConfigBuilder, + shortint_block_parameters: crate::c_api::shortint::parameters::ShortintPBSParameters, +) -> ::std::os::raw::c_int { + catch_panic(|| { + check_ptr_is_non_null_and_aligned(builder).unwrap(); + + let inner = Box::from_raw(*builder) + .0 + .enable_custom_integers(shortint_block_parameters.into(), None); + *builder = Box::into_raw(Box::new(ConfigBuilder(inner))); + }) +} /// Takes ownership of the builder #[no_mangle] pub unsafe extern "C" fn config_builder_build( diff --git a/tfhe/src/c_api/high_level_api/integers.rs b/tfhe/src/c_api/high_level_api/integers.rs index 20dc00052..b6c64af49 100644 --- a/tfhe/src/c_api/high_level_api/integers.rs +++ b/tfhe/src/c_api/high_level_api/integers.rs @@ -1,10 +1,11 @@ -use crate::c_api::high_level_api::keys::{ClientKey, PublicKey}; +use crate::c_api::high_level_api::keys::{ClientKey, CompactPublicKey, PublicKey}; use crate::high_level_api::prelude::*; use std::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, MulAssign, Neg, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, }; +use crate::c_api::high_level_api::u128::U128; use crate::c_api::high_level_api::u256::U256; use crate::c_api::utils::*; use std::os::raw::c_int; @@ -67,6 +68,49 @@ macro_rules! create_integer_wrapper_type { }) } } + + // The compact list version of the ciphertext type + ::paste::paste! { + pub struct []($crate::high_level_api::[]); + + impl_destroy_on_type!([]); + + impl_clone_on_type!([]); + + impl_serialize_deserialize_on_type!([]); + + #[no_mangle] + pub unsafe extern "C" fn []( + sself: *const [], + result: *mut usize, + ) -> ::std::os::raw::c_int { + $crate::c_api::utils::catch_panic(|| { + let list = $crate::c_api::utils::get_ref_checked(sself).unwrap(); + + *result = list.0.len(); + }) + } + + #[no_mangle] + pub unsafe extern "C" fn []( + sself: *const [], + output: *mut *mut $name, + output_len: usize + ) -> ::std::os::raw::c_int { + $crate::c_api::utils::catch_panic(|| { + check_ptr_is_non_null_and_aligned(output).unwrap(); + let list = $crate::c_api::utils::get_ref_checked(sself).unwrap(); + let expanded = list.0.expand(); + + let num_to_take = output_len.max(list.0.len()); + let iter = expanded.into_iter().take(num_to_take).enumerate(); + for (i, fhe_uint) in iter { + let ptr = output.wrapping_add(i); + *ptr = Box::into_raw(Box::new($name(fhe_uint))); + } + }) + } + } }; } @@ -84,52 +128,65 @@ 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_compact_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_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint8List{crate::high_level_api::CompactFheUint8List}, 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_compact_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_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint10List{crate::high_level_api::CompactFheUint10List}, 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_compact_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_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint12List{crate::high_level_api::CompactFheUint12List}, 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_compact_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_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint14List{crate::high_level_api::CompactFheUint14List}, 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_compact_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_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint16List{crate::high_level_api::CompactFheUint16List}, 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_compact_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_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint32List{crate::high_level_api::CompactFheUint32List}, 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_compact_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); +impl_try_encrypt_list_with_compact_public_key_on_type!(CompactFheUint64List{crate::high_level_api::CompactFheUint64List}, u64); #[no_mangle] pub unsafe extern "C" fn fhe_uint128_try_encrypt_trivial_u128( - low_word: u64, - high_word: u64, + value: U128, result: *mut *mut FheUint128, ) -> c_int { catch_panic(|| { - let value = ((high_word as u128) << 64u128) | low_word as u128; + let value = u128::from(value); let inner = ::try_encrypt_trivial(value).unwrap(); @@ -139,15 +196,14 @@ pub unsafe extern "C" fn fhe_uint128_try_encrypt_trivial_u128( #[no_mangle] pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_client_key_u128( - low_word: u64, - high_word: u64, + value: U128, client_key: *const ClientKey, result: *mut *mut FheUint128, ) -> c_int { catch_panic(|| { let client_key = get_ref_checked(client_key).unwrap(); - let value = ((high_word as u128) << 64u128) | low_word as u128; + let value = u128::from(value); let inner = ::try_encrypt(value, &client_key.0).unwrap(); @@ -157,15 +213,14 @@ pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_client_key_u128( #[no_mangle] pub unsafe extern "C" fn compressed_fhe_uint128_try_encrypt_with_client_key_u128( - low_word: u64, - high_word: u64, + value: U128, client_key: *const ClientKey, result: *mut *mut CompressedFheUint128, ) -> c_int { catch_panic(|| { let client_key = get_ref_checked(client_key).unwrap(); - let value = ((high_word as u128) << 64u128) | low_word as u128; + let value = u128::from(value); let inner = ::try_encrypt(value, &client_key.0) @@ -177,15 +232,14 @@ pub unsafe extern "C" fn compressed_fhe_uint128_try_encrypt_with_client_key_u128 #[no_mangle] pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_public_key_u128( - low_word: u64, - high_word: u64, + value: U128, public_key: *const PublicKey, result: *mut *mut FheUint128, ) -> c_int { catch_panic(|| { let public_key = get_ref_checked(public_key).unwrap(); - let value = ((high_word as u128) << 64u128) | low_word as u128; + let value = u128::from(value); let inner = ::try_encrypt(value, &public_key.0).unwrap(); @@ -193,12 +247,48 @@ pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_public_key_u128( }) } +#[no_mangle] +pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_compact_public_key_u128( + value: U128, + public_key: *const CompactPublicKey, + result: *mut *mut FheUint128, +) -> c_int { + catch_panic(|| { + let public_key = get_ref_checked(public_key).unwrap(); + + let value = u128::from(value); + + let inner = ::try_encrypt(value, &public_key.0).unwrap(); + + *result = Box::into_raw(Box::new(FheUint128(inner))); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn compact_fhe_uint256_list_try_encrypt_with_compact_public_key_u128( + input: *const U128, + input_len: usize, + public_key: *const CompactPublicKey, + result: *mut *mut CompactFheUint256List, +) -> c_int { + catch_panic(|| { + let public_key = get_ref_checked(public_key).unwrap(); + + let slc = ::std::slice::from_raw_parts(input, input_len); + let values = slc.iter().copied().map(u128::from).collect::>(); + let inner = + ::try_encrypt(&values, &public_key.0) + .unwrap(); + + *result = Box::into_raw(Box::new(CompactFheUint256List(inner))); + }) +} + #[no_mangle] pub unsafe extern "C" fn fhe_uint128_decrypt( encrypted_value: *const FheUint128, client_key: *const ClientKey, - low_word: *mut u64, - high_word: *mut u64, + result: *mut U128, ) -> c_int { catch_panic(|| { let client_key = get_ref_checked(client_key).unwrap(); @@ -206,18 +296,18 @@ pub unsafe extern "C" fn fhe_uint128_decrypt( let inner: u128 = encrypted_value.0.decrypt(&client_key.0); - *low_word = (inner & (u64::MAX as u128)) as u64; - *high_word = (inner >> 64) as u64; + *result = U128::from(inner); }) } #[no_mangle] pub unsafe extern "C" fn fhe_uint256_try_encrypt_trivial_u256( - value: *const U256, + value: U256, result: *mut *mut FheUint256, ) -> c_int { catch_panic(|| { - let inner = ::try_encrypt_trivial((*value).0).unwrap(); + let value = crate::integer::U256::from(value); + let inner = ::try_encrypt_trivial(value).unwrap(); *result = Box::into_raw(Box::new(FheUint256(inner))); }) @@ -225,15 +315,15 @@ pub unsafe extern "C" fn fhe_uint256_try_encrypt_trivial_u256( #[no_mangle] pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_client_key_u256( - value: *const U256, + value: U256, client_key: *const ClientKey, result: *mut *mut FheUint256, ) -> c_int { catch_panic(|| { let client_key = get_ref_checked(client_key).unwrap(); - let inner = - ::try_encrypt((*value).0, &client_key.0).unwrap(); + let value = crate::integer::U256::from(value); + let inner = ::try_encrypt(value, &client_key.0).unwrap(); *result = Box::into_raw(Box::new(FheUint256(inner))); }) @@ -241,15 +331,16 @@ pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_client_key_u256( #[no_mangle] pub unsafe extern "C" fn compressed_fhe_uint256_try_encrypt_with_client_key_u256( - value: *const U256, + value: U256, client_key: *const ClientKey, result: *mut *mut CompressedFheUint256, ) -> c_int { catch_panic(|| { let client_key = get_ref_checked(client_key).unwrap(); + let value = crate::integer::U256::from(value); let inner = - ::try_encrypt((*value).0, &client_key.0) + ::try_encrypt(value, &client_key.0) .unwrap(); *result = Box::into_raw(Box::new(CompressedFheUint256(inner))); @@ -258,32 +349,72 @@ pub unsafe extern "C" fn compressed_fhe_uint256_try_encrypt_with_client_key_u256 #[no_mangle] pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_public_key_u256( - value: *const U256, + value: U256, public_key: *const PublicKey, result: *mut *mut FheUint256, ) -> c_int { catch_panic(|| { let public_key = get_ref_checked(public_key).unwrap(); - let inner = - ::try_encrypt((*value).0, &public_key.0).unwrap(); + let value = crate::integer::U256::from(value); + let inner = ::try_encrypt(value, &public_key.0).unwrap(); *result = Box::into_raw(Box::new(FheUint256(inner))); }) } +#[no_mangle] +pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_compact_public_key_u256( + value: U256, + public_key: *const CompactPublicKey, + result: *mut *mut FheUint256, +) -> c_int { + catch_panic(|| { + let public_key = get_ref_checked(public_key).unwrap(); + + let value = crate::integer::U256::from(value); + let inner = ::try_encrypt(value, &public_key.0).unwrap(); + + *result = Box::into_raw(Box::new(FheUint256(inner))); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn compact_fhe_uint256_list_try_encrypt_with_compact_public_key_u256( + input: *const U256, + input_len: usize, + public_key: *const CompactPublicKey, + result: *mut *mut CompactFheUint256List, +) -> c_int { + catch_panic(|| { + let public_key = get_ref_checked(public_key).unwrap(); + + let slc = ::std::slice::from_raw_parts(input, input_len); + let values = slc + .iter() + .copied() + .map(crate::integer::U256::from) + .collect::>(); + let inner = + ::try_encrypt(&values, &public_key.0) + .unwrap(); + + *result = Box::into_raw(Box::new(CompactFheUint256List(inner))); + }) +} + #[no_mangle] pub unsafe extern "C" fn fhe_uint256_decrypt( encrypted_value: *const FheUint256, client_key: *const ClientKey, - result: *mut *mut U256, + result: *mut U256, ) -> c_int { catch_panic(|| { let client_key = get_ref_checked(client_key).unwrap(); let encrypted_value = get_ref_checked(encrypted_value).unwrap(); let inner: crate::integer::U256 = encrypted_value.0.decrypt(&client_key.0); - *result = Box::into_raw(Box::new(U256(inner))); + *result = U256::from(inner); }) } diff --git a/tfhe/src/c_api/high_level_api/keys.rs b/tfhe/src/c_api/high_level_api/keys.rs index 5101d8841..d829b1e14 100644 --- a/tfhe/src/c_api/high_level_api/keys.rs +++ b/tfhe/src/c_api/high_level_api/keys.rs @@ -3,14 +3,20 @@ use std::os::raw::c_int; pub struct ClientKey(pub(crate) crate::high_level_api::ClientKey); pub struct PublicKey(pub(crate) crate::high_level_api::PublicKey); +pub struct CompactPublicKey(pub(crate) crate::high_level_api::CompactPublicKey); +pub struct CompressedCompactPublicKey(pub(crate) crate::high_level_api::CompressedCompactPublicKey); pub struct ServerKey(pub(crate) crate::high_level_api::ServerKey); impl_destroy_on_type!(ClientKey); impl_destroy_on_type!(PublicKey); +impl_destroy_on_type!(CompactPublicKey); +impl_destroy_on_type!(CompressedCompactPublicKey); impl_destroy_on_type!(ServerKey); impl_serialize_deserialize_on_type!(ClientKey); impl_serialize_deserialize_on_type!(PublicKey); +impl_serialize_deserialize_on_type!(CompactPublicKey); +impl_serialize_deserialize_on_type!(CompressedCompactPublicKey); impl_serialize_deserialize_on_type!(ServerKey); #[no_mangle] @@ -69,3 +75,43 @@ pub unsafe extern "C" fn public_key_new( *result_public_key = Box::into_raw(Box::new(PublicKey(inner))); }) } + +#[no_mangle] +pub unsafe extern "C" fn compact_public_key_new( + client_key: *const ClientKey, + result_public_key: *mut *mut CompactPublicKey, +) -> c_int { + catch_panic(|| { + let client_key = get_ref_checked(client_key).unwrap(); + let inner = crate::high_level_api::CompactPublicKey::new(&client_key.0); + + *result_public_key = Box::into_raw(Box::new(CompactPublicKey(inner))); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn compressed_compact_public_key_new( + client_key: *const ClientKey, + result_public_key: *mut *mut CompressedCompactPublicKey, +) -> c_int { + catch_panic(|| { + let client_key = get_ref_checked(client_key).unwrap(); + let inner = crate::high_level_api::CompressedCompactPublicKey::new(&client_key.0); + + *result_public_key = Box::into_raw(Box::new(CompressedCompactPublicKey(inner))); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn compressed_compact_public_key_decompress( + public_key: *const CompressedCompactPublicKey, + result_public_key: *mut *mut CompactPublicKey, +) -> c_int { + catch_panic(|| { + let public_key = get_ref_checked(public_key).unwrap(); + + *result_public_key = Box::into_raw(Box::new(CompactPublicKey( + public_key.0.clone().decompress(), + ))); + }) +} diff --git a/tfhe/src/c_api/high_level_api/mod.rs b/tfhe/src/c_api/high_level_api/mod.rs index b904ea466..9fd909563 100644 --- a/tfhe/src/c_api/high_level_api/mod.rs +++ b/tfhe/src/c_api/high_level_api/mod.rs @@ -7,4 +7,6 @@ pub mod config; pub mod integers; pub mod keys; #[cfg(feature = "integer")] +pub mod u128; +#[cfg(feature = "integer")] pub mod u256; diff --git a/tfhe/src/c_api/high_level_api/u128.rs b/tfhe/src/c_api/high_level_api/u128.rs new file mode 100644 index 000000000..32a73139a --- /dev/null +++ b/tfhe/src/c_api/high_level_api/u128.rs @@ -0,0 +1,20 @@ +#[repr(C)] +#[derive(Copy, Clone)] +pub struct U128 { + pub w0: u64, + pub w1: u64, +} + +impl From for U128 { + fn from(value: u128) -> Self { + let w0 = (value & (u64::MAX as u128)) as u64; + let w1 = (value >> 64) as u64; + Self { w0, w1 } + } +} + +impl From for u128 { + fn from(value: U128) -> Self { + ((value.w1 as u128) << 64u128) | value.w0 as u128 + } +} diff --git a/tfhe/src/c_api/high_level_api/u256.rs b/tfhe/src/c_api/high_level_api/u256.rs index 9bd66b7f8..f891cb8e8 100644 --- a/tfhe/src/c_api/high_level_api/u256.rs +++ b/tfhe/src/c_api/high_level_api/u256.rs @@ -1,47 +1,30 @@ use crate::c_api::utils::*; use std::os::raw::c_int; -pub struct U256(pub(in crate::c_api) crate::integer::U256); - -impl_destroy_on_type!(U256); - -/// w0 is the least significant, w4 is the most significant -#[no_mangle] -pub unsafe extern "C" fn u256_from_u64_words( - w0: u64, - w1: u64, - w2: u64, - w3: u64, - result: *mut *mut U256, -) -> c_int { - catch_panic(|| { - let inner = crate::integer::U256::from((w0, w1, w2, w3)); - *result = Box::into_raw(Box::new(U256(inner))); - }) +#[repr(C)] +#[derive(Copy, Clone)] +pub struct U256 { + pub w0: u64, + pub w1: u64, + pub w2: u64, + pub w3: u64, } -/// w0 is the least significant, w4 is the most significant -#[no_mangle] -pub unsafe extern "C" fn u256_to_u64_words( - input: *const U256, - w0: *mut u64, - w1: *mut u64, - w2: *mut u64, - w3: *mut u64, -) -> c_int { - catch_panic(|| { - let input = get_ref_checked(input).unwrap(); +impl From for U256 { + fn from(value: crate::integer::U256) -> Self { + Self { + w0: value.0[0], + w1: value.0[1], + w2: value.0[2], + w3: value.0[3], + } + } +} - check_ptr_is_non_null_and_aligned(w0).unwrap(); - check_ptr_is_non_null_and_aligned(w1).unwrap(); - check_ptr_is_non_null_and_aligned(w2).unwrap(); - check_ptr_is_non_null_and_aligned(w3).unwrap(); - - *w0 = input.0 .0[0]; - *w1 = input.0 .0[1]; - *w2 = input.0 .0[2]; - *w3 = input.0 .0[3]; - }) +impl From for crate::integer::U256 { + fn from(value: U256) -> Self { + Self([value.w0, value.w1, value.w2, value.w3]) + } } /// Creates a U256 from little endian bytes @@ -51,7 +34,7 @@ pub unsafe extern "C" fn u256_to_u64_words( pub unsafe extern "C" fn u256_from_little_endian_bytes( input: *const u8, len: usize, - result: *mut *mut U256, + result: *mut U256, ) -> c_int { catch_panic(|| { let mut inner = crate::integer::U256::default(); @@ -59,7 +42,7 @@ pub unsafe extern "C" fn u256_from_little_endian_bytes( let input = std::slice::from_raw_parts(input, len); inner.copy_from_le_byte_slice(input); - *result = Box::into_raw(Box::new(U256(inner))); + *result = U256::from(inner) }) } @@ -70,7 +53,7 @@ pub unsafe extern "C" fn u256_from_little_endian_bytes( pub unsafe extern "C" fn u256_from_big_endian_bytes( input: *const u8, len: usize, - result: *mut *mut U256, + result: *mut U256, ) -> c_int { catch_panic(|| { let mut inner = crate::integer::U256::default(); @@ -78,38 +61,32 @@ pub unsafe extern "C" fn u256_from_big_endian_bytes( let input = std::slice::from_raw_parts(input, len); inner.copy_from_be_byte_slice(input); - *result = Box::into_raw(Box::new(U256(inner))); + *result = U256::from(inner) }) } /// len must be 32 #[no_mangle] pub unsafe extern "C" fn u256_little_endian_bytes( - input: *const U256, + input: U256, result: *mut u8, len: usize, ) -> c_int { catch_panic(|| { check_ptr_is_non_null_and_aligned(result).unwrap(); - let input = get_ref_checked(input).unwrap(); let bytes = std::slice::from_raw_parts_mut(result, len); - input.0.copy_to_le_byte_slice(bytes); + crate::integer::U256::from(input).copy_to_le_byte_slice(bytes); }) } /// len must be 32 #[no_mangle] -pub unsafe extern "C" fn u256_big_endian_bytes( - input: *const U256, - result: *mut u8, - len: usize, -) -> c_int { +pub unsafe extern "C" fn u256_big_endian_bytes(input: U256, result: *mut u8, len: usize) -> c_int { catch_panic(|| { check_ptr_is_non_null_and_aligned(result).unwrap(); - let input = get_ref_checked(input).unwrap(); let bytes = std::slice::from_raw_parts_mut(result, len); - input.0.copy_to_be_byte_slice(bytes); + crate::integer::U256::from(input).copy_to_be_byte_slice(bytes); }) } diff --git a/tfhe/src/c_api/high_level_api/utils.rs b/tfhe/src/c_api/high_level_api/utils.rs index 06914271a..01750ddba 100644 --- a/tfhe/src/c_api/high_level_api/utils.rs +++ b/tfhe/src/c_api/high_level_api/utils.rs @@ -59,6 +59,49 @@ macro_rules! impl_try_encrypt_with_public_key_on_type { }; } +macro_rules! impl_try_encrypt_with_compact_public_key_on_type { + ($wrapper_type:ty{$wrapped_type:ty}, $input_type:ty) => { + ::paste::paste! { + #[no_mangle] + pub unsafe extern "C" fn [<$wrapper_type:snake _try_encrypt_with_compact_public_key_ $input_type:snake>]( + value: $input_type, + public_key: *const $crate::c_api::high_level_api::keys::CompactPublicKey, + result: *mut *mut $wrapper_type, + ) -> ::std::os::raw::c_int { + $crate::c_api::utils::catch_panic(|| { + let public_key = $crate::c_api::utils::get_ref_checked(public_key).unwrap(); + + let inner = <$wrapped_type>::try_encrypt(value, &public_key.0).unwrap(); + + *result = Box::into_raw(Box::new($wrapper_type(inner))); + }) + } + } + }; +} + +macro_rules! impl_try_encrypt_list_with_compact_public_key_on_type { + ($wrapper_type:ty{$wrapped_type:ty}, $input_type:ty) => { + ::paste::paste! { + #[no_mangle] + pub unsafe extern "C" fn [<$wrapper_type:snake _try_encrypt_with_compact_public_key_ $input_type:snake>]( + input: *const $input_type, + input_len: usize, + public_key: *const $crate::c_api::high_level_api::keys::CompactPublicKey, + result: *mut *mut $wrapper_type, + ) -> ::std::os::raw::c_int { + $crate::c_api::utils::catch_panic(|| { + let public_key = $crate::c_api::utils::get_ref_checked(public_key).unwrap(); + let slc = ::std::slice::from_raw_parts(input, input_len); + let inner = <$wrapped_type>::try_encrypt(slc, &public_key.0).unwrap(); + + *result = Box::into_raw(Box::new($wrapper_type(inner))); + }) + } + } + }; +} + macro_rules! impl_try_encrypt_trivial_on_type { ($wrapper_type:ty{$wrapped_type:ty}, $input_type:ty) => { ::paste::paste! { diff --git a/tfhe/src/c_api/shortint/client_key.rs b/tfhe/src/c_api/shortint/client_key.rs index 8272ae493..e7726c7f3 100644 --- a/tfhe/src/c_api/shortint/client_key.rs +++ b/tfhe/src/c_api/shortint/client_key.rs @@ -10,7 +10,7 @@ pub struct ShortintClientKey(pub(in crate::c_api) shortint::client_key::ClientKe #[no_mangle] pub unsafe extern "C" fn shortint_gen_client_key( - shortint_parameters: *const super::parameters::ShortintParameters, + shortint_parameters: super::parameters::ShortintPBSParameters, result_client_key: *mut *mut ShortintClientKey, ) -> c_int { catch_panic(|| { @@ -20,9 +20,10 @@ pub unsafe extern "C" fn shortint_gen_client_key( // checked, then any access to the result pointer will segfault (mimics malloc on failure) *result_client_key = std::ptr::null_mut(); - let shortint_parameters = get_ref_checked(shortint_parameters).unwrap(); + let shortint_parameters: crate::shortint::parameters::ClassicPBSParameters = + shortint_parameters.into(); - let client_key = shortint::client_key::ClientKey::new(shortint_parameters.0.to_owned()); + let client_key = shortint::client_key::ClientKey::new(shortint_parameters); let heap_allocated_client_key = Box::new(ShortintClientKey(client_key)); diff --git a/tfhe/src/c_api/shortint/destroy.rs b/tfhe/src/c_api/shortint/destroy.rs index a5861aecf..fcb86abba 100644 --- a/tfhe/src/c_api/shortint/destroy.rs +++ b/tfhe/src/c_api/shortint/destroy.rs @@ -1,7 +1,6 @@ use crate::c_api::utils::*; use std::os::raw::c_int; -use super::parameters::ShortintParameters; use super::{ ShortintBivariatePBSLookupTable, ShortintCiphertext, ShortintClientKey, ShortintCompressedCiphertext, ShortintCompressedPublicKey, ShortintCompressedServerKey, @@ -57,17 +56,6 @@ pub unsafe extern "C" fn destroy_shortint_compressed_public_key( }) } -#[no_mangle] -pub unsafe extern "C" fn destroy_shortint_parameters( - shortint_parameters: *mut ShortintParameters, -) -> c_int { - catch_panic(|| { - check_ptr_is_non_null_and_aligned(shortint_parameters).unwrap(); - - drop(Box::from_raw(shortint_parameters)); - }) -} - #[no_mangle] pub unsafe extern "C" fn destroy_shortint_ciphertext( shortint_ciphertext: *mut ShortintCiphertext, diff --git a/tfhe/src/c_api/shortint/mod.rs b/tfhe/src/c_api/shortint/mod.rs index 67ac77890..185f26bb7 100644 --- a/tfhe/src/c_api/shortint/mod.rs +++ b/tfhe/src/c_api/shortint/mod.rs @@ -19,7 +19,7 @@ pub use server_key::{ShortintCompressedServerKey, ShortintServerKey}; #[no_mangle] pub unsafe extern "C" fn shortint_gen_keys_with_parameters( - shortint_parameters: *const parameters::ShortintParameters, + shortint_parameters: parameters::ShortintPBSParameters, result_client_key: *mut *mut ShortintClientKey, result_server_key: *mut *mut ShortintServerKey, ) -> c_int { @@ -32,9 +32,10 @@ pub unsafe extern "C" fn shortint_gen_keys_with_parameters( *result_client_key = std::ptr::null_mut(); *result_server_key = std::ptr::null_mut(); - let shortint_parameters = get_ref_checked(shortint_parameters).unwrap(); + let shortint_parameters: crate::shortint::parameters::ClassicPBSParameters = + shortint_parameters.into(); - let client_key = shortint::client_key::ClientKey::new(shortint_parameters.0); + let client_key = shortint::client_key::ClientKey::new(shortint_parameters); let server_key = shortint::server_key::ServerKey::new(&client_key); let heap_allocated_client_key = Box::new(ShortintClientKey(client_key)); diff --git a/tfhe/src/c_api/shortint/parameters.rs b/tfhe/src/c_api/shortint/parameters.rs index cbd5b3b8c..3cc6ffddb 100644 --- a/tfhe/src/c_api/shortint/parameters.rs +++ b/tfhe/src/c_api/shortint/parameters.rs @@ -8,6 +8,7 @@ use std::os::raw::c_int; use crate::shortint; #[repr(C)] +#[derive(Copy, Clone)] pub enum ShortintEncryptionKeyChoice { ShortintEncryptionKeyChoiceBig, ShortintEncryptionKeyChoiceSmall, @@ -26,13 +27,185 @@ impl From for crate::shortint::parameters::Encrypti } } -pub struct ShortintParameters(pub(in crate::c_api) shortint::parameters::ClassicPBSParameters); +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ShortintPBSParameters { + pub lwe_dimension: usize, + pub glwe_dimension: usize, + pub polynomial_size: usize, + pub lwe_modular_std_dev: f64, + pub glwe_modular_std_dev: f64, + pub pbs_base_log: usize, + pub pbs_level: usize, + pub ks_base_log: usize, + pub ks_level: usize, + pub message_modulus: usize, + pub carry_modulus: usize, + pub modulus_power_of_2_exponent: usize, + pub encryption_key_choice: ShortintEncryptionKeyChoice, +} + +impl From for crate::shortint::ClassicPBSParameters { + fn from(c_params: ShortintPBSParameters) -> Self { + Self { + lwe_dimension: LweDimension(c_params.lwe_dimension), + glwe_dimension: GlweDimension(c_params.glwe_dimension), + polynomial_size: PolynomialSize(c_params.polynomial_size), + lwe_modular_std_dev: StandardDev(c_params.lwe_modular_std_dev), + glwe_modular_std_dev: StandardDev(c_params.glwe_modular_std_dev), + pbs_base_log: DecompositionBaseLog(c_params.pbs_base_log), + pbs_level: DecompositionLevelCount(c_params.pbs_level), + ks_base_log: DecompositionBaseLog(c_params.ks_base_log), + ks_level: DecompositionLevelCount(c_params.ks_level), + message_modulus: crate::shortint::parameters::MessageModulus(c_params.message_modulus), + carry_modulus: crate::shortint::parameters::CarryModulus(c_params.carry_modulus), + ciphertext_modulus: crate::shortint::parameters::CiphertextModulus::try_new_power_of_2( + c_params.modulus_power_of_2_exponent, + ) + .unwrap(), + encryption_key_choice: c_params.encryption_key_choice.into(), + } + } +} + +impl From for ShortintPBSParameters { + fn from(rust_params: crate::shortint::ClassicPBSParameters) -> Self { + Self::convert(rust_params) + } +} + +impl ShortintEncryptionKeyChoice { + // From::from cannot be marked as const, so we have to have + // our own function + const fn convert(rust_choice: crate::shortint::EncryptionKeyChoice) -> Self { + match rust_choice { + crate::shortint::EncryptionKeyChoice::Big => Self::ShortintEncryptionKeyChoiceBig, + crate::shortint::EncryptionKeyChoice::Small => Self::ShortintEncryptionKeyChoiceSmall, + } + } +} + +const fn convert_modulus(rust_modulus: crate::shortint::CiphertextModulus) -> usize { + if rust_modulus.is_native_modulus() { + 64 // shortints are on 64 bits + } else { + assert!(rust_modulus.is_power_of_two()); + let modulus = rust_modulus.get_custom_modulus(); + let exponent = modulus.ilog2() as usize; + assert!(exponent <= 64); + exponent + } +} + +impl ShortintPBSParameters { + const fn convert(rust_params: crate::shortint::ClassicPBSParameters) -> Self { + Self { + lwe_dimension: rust_params.lwe_dimension.0, + glwe_dimension: rust_params.glwe_dimension.0, + polynomial_size: rust_params.polynomial_size.0, + lwe_modular_std_dev: rust_params.lwe_modular_std_dev.0, + glwe_modular_std_dev: rust_params.glwe_modular_std_dev.0, + pbs_base_log: rust_params.pbs_base_log.0, + pbs_level: rust_params.pbs_level.0, + ks_base_log: rust_params.ks_base_log.0, + ks_level: rust_params.ks_level.0, + message_modulus: rust_params.message_modulus.0, + carry_modulus: rust_params.carry_modulus.0, + modulus_power_of_2_exponent: convert_modulus(rust_params.ciphertext_modulus), + encryption_key_choice: ShortintEncryptionKeyChoice::convert( + rust_params.encryption_key_choice, + ), + } + } +} + +macro_rules! expose_as_shortint_pbs_parameters( + ( + $( + $param_name:ident + ),* + $(,)? + ) => { + ::paste::paste!{ + $( + #[no_mangle] + pub static []: ShortintPBSParameters = + ShortintPBSParameters::convert(crate::shortint::parameters::$param_name); + + )* + } + + // Test that converting a param from its rust struct + // to the c struct and then to the rust struct again + // yields the same values as the original struct + // + // This is what will essentially happen in the real code + #[test] + fn test_shortint_pbs_parameters_roundtrip_c_rust() { + $( + // 1 scope for each parameters + { + let rust_params = crate::shortint::parameters::$param_name; + let c_params = ShortintPBSParameters::from(rust_params); + let rust_params_from_c = crate::shortint::parameters::ClassicPBSParameters::from(c_params); + assert_eq!(rust_params, rust_params_from_c); + } + )* + } + } +); + +expose_as_shortint_pbs_parameters!( + PARAM_MESSAGE_1_CARRY_0, + PARAM_MESSAGE_1_CARRY_1, + PARAM_MESSAGE_2_CARRY_0, + PARAM_MESSAGE_1_CARRY_2, + PARAM_MESSAGE_2_CARRY_1, + PARAM_MESSAGE_3_CARRY_0, + PARAM_MESSAGE_1_CARRY_3, + PARAM_MESSAGE_2_CARRY_2, + PARAM_MESSAGE_3_CARRY_1, + PARAM_MESSAGE_4_CARRY_0, + PARAM_MESSAGE_1_CARRY_4, + PARAM_MESSAGE_2_CARRY_3, + PARAM_MESSAGE_3_CARRY_2, + PARAM_MESSAGE_4_CARRY_1, + PARAM_MESSAGE_5_CARRY_0, + PARAM_MESSAGE_1_CARRY_5, + PARAM_MESSAGE_2_CARRY_4, + PARAM_MESSAGE_3_CARRY_3, + PARAM_MESSAGE_4_CARRY_2, + PARAM_MESSAGE_5_CARRY_1, + PARAM_MESSAGE_6_CARRY_0, + PARAM_MESSAGE_1_CARRY_6, + PARAM_MESSAGE_2_CARRY_5, + PARAM_MESSAGE_3_CARRY_4, + PARAM_MESSAGE_4_CARRY_3, + PARAM_MESSAGE_5_CARRY_2, + PARAM_MESSAGE_6_CARRY_1, + PARAM_MESSAGE_7_CARRY_0, + PARAM_MESSAGE_1_CARRY_7, + PARAM_MESSAGE_2_CARRY_6, + PARAM_MESSAGE_3_CARRY_5, + PARAM_MESSAGE_4_CARRY_4, + PARAM_MESSAGE_5_CARRY_3, + PARAM_MESSAGE_6_CARRY_2, + PARAM_MESSAGE_7_CARRY_1, + PARAM_MESSAGE_8_CARRY_0, + PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, + // Small params + PARAM_SMALL_MESSAGE_1_CARRY_1, + PARAM_SMALL_MESSAGE_2_CARRY_2, + PARAM_SMALL_MESSAGE_3_CARRY_3, + PARAM_SMALL_MESSAGE_4_CARRY_4, + PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, +); #[no_mangle] pub unsafe extern "C" fn shortint_get_parameters( message_bits: u32, carry_bits: u32, - result: *mut *mut ShortintParameters, + result: *mut ShortintPBSParameters, ) -> c_int { catch_panic(|| { check_ptr_is_non_null_and_aligned(result).unwrap(); @@ -76,12 +249,8 @@ pub unsafe extern "C" fn shortint_get_parameters( _ => None, }; - match params { - Some(params) => { - let params = Box::new(ShortintParameters(params)); - *result = Box::into_raw(params); - } - None => *result = std::ptr::null_mut(), + if let Some(params) = params { + *result = params.into(); } }) } @@ -90,7 +259,7 @@ pub unsafe extern "C" fn shortint_get_parameters( pub unsafe extern "C" fn shortint_get_parameters_small( message_bits: u32, carry_bits: u32, - result: *mut *mut ShortintParameters, + result: *mut ShortintPBSParameters, ) -> c_int { catch_panic(|| { check_ptr_is_non_null_and_aligned(result).unwrap(); @@ -102,62 +271,8 @@ pub unsafe extern "C" fn shortint_get_parameters_small( _ => None, }; - match params { - Some(params) => { - let params = Box::new(ShortintParameters(params)); - *result = Box::into_raw(params); - } - None => *result = std::ptr::null_mut(), + if let Some(params) = params { + *result = params.into(); } }) } - -#[no_mangle] -pub unsafe extern "C" fn shortint_create_parameters( - lwe_dimension: usize, - glwe_dimension: usize, - polynomial_size: usize, - lwe_modular_std_dev: f64, - glwe_modular_std_dev: f64, - pbs_base_log: usize, - pbs_level: usize, - ks_base_log: usize, - ks_level: usize, - message_modulus: usize, - carry_modulus: usize, - modulus_power_of_2_exponent: usize, - encryption_key_choice: ShortintEncryptionKeyChoice, - result: *mut *mut ShortintParameters, -) -> c_int { - catch_panic(|| { - check_ptr_is_non_null_and_aligned(result).unwrap(); - - // First fill the result with a null ptr so that if we fail and the return code is not - // checked, then any access to the result pointer will segfault (mimics malloc on failure) - *result = std::ptr::null_mut(); - - let heap_allocated_parameters = Box::new(ShortintParameters( - shortint::parameters::ClassicPBSParameters { - lwe_dimension: LweDimension(lwe_dimension), - glwe_dimension: GlweDimension(glwe_dimension), - polynomial_size: PolynomialSize(polynomial_size), - lwe_modular_std_dev: StandardDev(lwe_modular_std_dev), - glwe_modular_std_dev: StandardDev(glwe_modular_std_dev), - pbs_base_log: DecompositionBaseLog(pbs_base_log), - pbs_level: DecompositionLevelCount(pbs_level), - ks_base_log: DecompositionBaseLog(ks_base_log), - ks_level: DecompositionLevelCount(ks_level), - message_modulus: crate::shortint::parameters::MessageModulus(message_modulus), - carry_modulus: crate::shortint::parameters::CarryModulus(carry_modulus), - ciphertext_modulus: - crate::shortint::parameters::CiphertextModulus::try_new_power_of_2( - modulus_power_of_2_exponent, - ) - .unwrap(), - encryption_key_choice: encryption_key_choice.into(), - }, - )); - - *result = Box::into_raw(heap_allocated_parameters); - }) -} diff --git a/tfhe/src/core_crypto/algorithms/lwe_compact_ciphertext_list_expansion.rs b/tfhe/src/core_crypto/algorithms/lwe_compact_ciphertext_list_expansion.rs new file mode 100644 index 000000000..b3309432d --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/lwe_compact_ciphertext_list_expansion.rs @@ -0,0 +1,110 @@ +//! Module with primitives pertaining to [`LweCompactCiphertextList`] expansion. + +use crate::core_crypto::algorithms::polynomial_algorithms::polynomial_wrapping_monic_monomial_mul_assign; +use crate::core_crypto::commons::parameters::MonomialDegree; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; +use rayon::prelude::*; + +/// Expand an [`LweCompactCiphertextList`] into an [`LweCiphertextList`]. +/// +/// Consider using [`par_expand_lwe_compact_ciphertext_list`] for better performance. +pub fn expand_lwe_compact_ciphertext_list( + output_lwe_ciphertext_list: &mut LweCiphertextList, + input_lwe_compact_ciphertext_list: &LweCompactCiphertextList, +) where + Scalar: UnsignedInteger, + InputCont: Container, + OutputCont: ContainerMut, +{ + assert!( + output_lwe_ciphertext_list.entity_count() + == input_lwe_compact_ciphertext_list.lwe_ciphertext_count().0 + ); + + assert!(output_lwe_ciphertext_list.lwe_size() == input_lwe_compact_ciphertext_list.lwe_size()); + + let (input_mask_list, input_body_list) = + input_lwe_compact_ciphertext_list.get_mask_and_body_list(); + + let lwe_dimension = input_mask_list.lwe_dimension(); + let max_ciphertext_per_bin = lwe_dimension.0; + + for (input_mask, (mut output_ct_chunk, input_body_chunk)) in input_mask_list.iter().zip( + output_lwe_ciphertext_list + .chunks_mut(max_ciphertext_per_bin) + .zip(input_body_list.chunks(max_ciphertext_per_bin)), + ) { + for (ct_idx, (mut out_ct, input_body)) in output_ct_chunk + .iter_mut() + .zip(input_body_chunk.iter()) + .enumerate() + { + let (mut out_mask, out_body) = out_ct.get_mut_mask_and_body(); + out_mask.as_mut().copy_from_slice(input_mask.as_ref()); + + let mut out_mask_as_polynomial = Polynomial::from_container(out_mask.as_mut()); + + // This the Psi_jl from the paper, it's equivalent to a multiplication in the X^N + 1 + // ring for our choice of i == n + polynomial_wrapping_monic_monomial_mul_assign( + &mut out_mask_as_polynomial, + MonomialDegree(lwe_dimension.0 - (ct_idx + 1)), + ); + + *out_body.data = *input_body.data; + } + } +} + +/// Parallel version of [`expand_lwe_compact_ciphertext_list`]. +pub fn par_expand_lwe_compact_ciphertext_list( + output_lwe_ciphertext_list: &mut LweCiphertextList, + input_lwe_compact_ciphertext_list: &LweCompactCiphertextList, +) where + Scalar: UnsignedInteger + Send + Sync, + InputCont: Container, + OutputCont: ContainerMut, +{ + assert!( + output_lwe_ciphertext_list.entity_count() + == input_lwe_compact_ciphertext_list.lwe_ciphertext_count().0 + ); + + assert!(output_lwe_ciphertext_list.lwe_size() == input_lwe_compact_ciphertext_list.lwe_size()); + + let (input_mask_list, input_body_list) = + input_lwe_compact_ciphertext_list.get_mask_and_body_list(); + + let lwe_dimension = input_mask_list.lwe_dimension(); + let max_ciphertext_per_bin = lwe_dimension.0; + + input_mask_list + .par_iter() + .zip( + output_lwe_ciphertext_list + .par_chunks_mut(max_ciphertext_per_bin) + .zip(input_body_list.par_chunks(max_ciphertext_per_bin)), + ) + .for_each(|(input_mask, (mut output_ct_chunk, input_body_chunk))| { + output_ct_chunk + .par_iter_mut() + .zip(input_body_chunk.par_iter()) + .enumerate() + .for_each(|(ct_idx, (mut out_ct, input_body))| { + let (mut out_mask, out_body) = out_ct.get_mut_mask_and_body(); + out_mask.as_mut().copy_from_slice(input_mask.as_ref()); + + let mut out_mask_as_polynomial = Polynomial::from_container(out_mask.as_mut()); + + // This is the Psi_jl from the paper, it's equivalent to a multiplication in the + // X^N + 1 ring for our choice of i == n + polynomial_wrapping_monic_monomial_mul_assign( + &mut out_mask_as_polynomial, + MonomialDegree(lwe_dimension.0 - (ct_idx + 1)), + ); + + *out_body.data = *input_body.data; + }); + }); +} diff --git a/tfhe/src/core_crypto/algorithms/lwe_compact_public_key_generation.rs b/tfhe/src/core_crypto/algorithms/lwe_compact_public_key_generation.rs new file mode 100644 index 000000000..ed2f35042 --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/lwe_compact_public_key_generation.rs @@ -0,0 +1,149 @@ +//! Module containing primitives pertaining to [`LWE compact public key +//! generation`](`LweCompactPublicKey`). + +use crate::core_crypto::algorithms::*; +use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus; +use crate::core_crypto::commons::dispersion::DispersionParameter; +use crate::core_crypto::commons::generators::EncryptionRandomGenerator; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; +use crate::core_crypto::prelude::ActivatedRandomGenerator; +use slice_algorithms::*; + +/// Fill an [`LWE compact public key`](`LweCompactPublicKey`) with an actual public key constructed +/// from a private [`LWE secret key`](`LweSecretKey`). +pub fn generate_lwe_compact_public_key( + lwe_secret_key: &LweSecretKey, + output: &mut LweCompactPublicKey, + noise_parameters: impl DispersionParameter, + generator: &mut EncryptionRandomGenerator, +) where + Scalar: UnsignedTorus, + InputKeyCont: Container, + OutputKeyCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + assert!( + output.ciphertext_modulus().is_native_modulus(), + "This operation only supports native moduli" + ); + + assert!( + lwe_secret_key.lwe_dimension() == output.lwe_dimension(), + "Mismatched LweDimension between input LweSecretKey {:?} \ + and ouptut LweCompactPublicKey {:?}", + lwe_secret_key.lwe_dimension(), + output.lwe_dimension() + ); + + let (mut mask, mut body) = output.get_mut_mask_and_body(); + generator.fill_slice_with_random_mask(mask.as_mut()); + + slice_semi_reverse_negacyclic_convolution( + body.as_mut(), + mask.as_ref(), + lwe_secret_key.as_ref(), + ); + + generator + .unsigned_torus_slice_wrapping_add_random_noise_assign(body.as_mut(), noise_parameters); +} + +/// Allocate a new [`LWE compact public key`](`LweCompactPublicKey`) and fill it with an actual +/// public key constructed from a private [`LWE secret key`](`LweSecretKey`). +/// +/// See [`encrypt_lwe_ciphertext_with_compact_public_key`] for usage. +pub fn allocate_and_generate_new_lwe_compact_public_key( + lwe_secret_key: &LweSecretKey, + noise_parameters: impl DispersionParameter, + ciphertext_modulus: CiphertextModulus, + generator: &mut EncryptionRandomGenerator, +) -> LweCompactPublicKeyOwned +where + Scalar: UnsignedTorus, + InputKeyCont: Container, + Gen: ByteRandomGenerator, +{ + let mut pk = LweCompactPublicKeyOwned::new( + Scalar::ZERO, + lwe_secret_key.lwe_dimension(), + ciphertext_modulus, + ); + + generate_lwe_compact_public_key(lwe_secret_key, &mut pk, noise_parameters, generator); + + pk +} + +/// Fill a [`seeded LWE compact public key`](`LweCompactPublicKey`) with an actual public key +/// constructed from a private [`LWE secret key`](`LweSecretKey`). +pub fn generate_seeded_lwe_compact_public_key( + lwe_secret_key: &LweSecretKey, + output: &mut SeededLweCompactPublicKey, + noise_parameters: impl DispersionParameter, + noise_seeder: &mut NoiseSeeder, +) where + Scalar: UnsignedTorus, + InputKeyCont: Container, + OutputKeyCont: ContainerMut, + // Maybe Sized allows to pass Box. + NoiseSeeder: Seeder + ?Sized, +{ + assert!( + output.ciphertext_modulus().is_native_modulus(), + "This operation only supports native moduli" + ); + + assert!( + lwe_secret_key.lwe_dimension() == output.lwe_dimension(), + "Mismatched LweDimension between input LweSecretKey {:?} \ + and ouptut LweCompactPublicKey {:?}", + lwe_secret_key.lwe_dimension(), + output.lwe_dimension() + ); + + let mut generator = EncryptionRandomGenerator::::new( + output.compression_seed().seed, + noise_seeder, + ); + + let mut tmp_mask = vec![Scalar::ZERO; output.lwe_dimension().0]; + generator.fill_slice_with_random_mask(tmp_mask.as_mut()); + + let mut body = output.get_mut_body(); + + slice_semi_reverse_negacyclic_convolution( + body.as_mut(), + tmp_mask.as_ref(), + lwe_secret_key.as_ref(), + ); + + generator + .unsigned_torus_slice_wrapping_add_random_noise_assign(body.as_mut(), noise_parameters); +} + +/// Allocate a new [`seeded LWE compact public key`](`SeededLweCompactPublicKey`) and fill it with +/// an actual public key constructed from a private [`LWE secret key`](`LweSecretKey`). +pub fn allocate_and_generate_new_seeded_lwe_compact_public_key( + lwe_secret_key: &LweSecretKey, + noise_parameters: impl DispersionParameter, + ciphertext_modulus: CiphertextModulus, + noise_seeder: &mut NoiseSeeder, +) -> SeededLweCompactPublicKeyOwned +where + Scalar: UnsignedTorus, + InputKeyCont: Container, + // Maybe Sized allows to pass Box. + NoiseSeeder: Seeder + ?Sized, +{ + let mut pk = SeededLweCompactPublicKey::new( + Scalar::ZERO, + lwe_secret_key.lwe_dimension(), + noise_seeder.seed().into(), + ciphertext_modulus, + ); + + generate_seeded_lwe_compact_public_key(lwe_secret_key, &mut pk, noise_parameters, noise_seeder); + + pk +} diff --git a/tfhe/src/core_crypto/algorithms/lwe_encryption.rs b/tfhe/src/core_crypto/algorithms/lwe_encryption.rs index d6840d0e9..f47f37b83 100644 --- a/tfhe/src/core_crypto/algorithms/lwe_encryption.rs +++ b/tfhe/src/core_crypto/algorithms/lwe_encryption.rs @@ -1483,3 +1483,791 @@ where seeded_ct } + +/// Encrypt an input plaintext in an output [`LWE ciphertext`](`LweCiphertext`) using an +/// [`LWE compact public key`](`LweCompactPublicKey`). The ciphertext can be decrypted using the +/// [`LWE secret key`](`LweSecretKey`) that was used to generate the public key. +/// +/// # Example +/// +/// ``` +/// use tfhe::core_crypto::prelude::*; +/// +/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct +/// // computations +/// // Define parameters for LweCiphertext creation +/// let lwe_dimension = LweDimension(2048); +/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// +/// // Create the PRNG +/// let mut seeder = new_seeder(); +/// let seeder = seeder.as_mut(); +/// let mut encryption_generator = +/// EncryptionRandomGenerator::::new(seeder.seed(), seeder); +/// let mut secret_generator = +/// SecretRandomGenerator::::new(seeder.seed()); +/// +/// // Create the LweSecretKey +/// let lwe_secret_key = +/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator); +/// +/// let lwe_compact_public_key = allocate_and_generate_new_lwe_compact_public_key( +/// &lwe_secret_key, +/// glwe_modular_std_dev, +/// ciphertext_modulus, +/// &mut encryption_generator, +/// ); +/// +/// // Create the plaintext +/// let msg = 3u64; +/// let plaintext = Plaintext(msg << 60); +/// +/// // Create a new LweCiphertext +/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus); +/// +/// encrypt_lwe_ciphertext_with_compact_public_key( +/// &lwe_compact_public_key, +/// &mut lwe, +/// plaintext, +/// glwe_modular_std_dev, +/// glwe_modular_std_dev, +/// &mut secret_generator, +/// &mut encryption_generator, +/// ); +/// +/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe); +/// +/// // Round and remove encoding +/// // First create a decomposer working on the high 4 bits corresponding to our encoding. +/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); +/// +/// let rounded = decomposer.closest_representable(decrypted_plaintext.0); +/// +/// // Remove the encoding +/// let cleartext = rounded >> 60; +/// +/// // Check we recovered the original message +/// assert_eq!(cleartext, msg); +/// ``` +pub fn encrypt_lwe_ciphertext_with_compact_public_key< + Scalar, + KeyCont, + OutputCont, + SecretGen, + EncryptionGen, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCiphertext, + encoded: Plaintext, + mask_noise_parameters: impl DispersionParameter, + body_noise_parameters: impl DispersionParameter, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, +) where + Scalar: UnsignedTorus, + KeyCont: Container, + OutputCont: ContainerMut, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, +{ + assert!( + output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(), + "Mismatch between LweDimension of output cipertext and input public key. \ + Got {:?} in output, and {:?} in public key.", + output.lwe_size().to_lwe_dimension(), + lwe_compact_public_key.lwe_dimension() + ); + + assert!( + lwe_compact_public_key.ciphertext_modulus() == output.ciphertext_modulus(), + "Mismatch between CiphertextModulus of output cipertext and input public key. \ + Got {:?} in output, and {:?} in public key.", + output.ciphertext_modulus(), + lwe_compact_public_key.ciphertext_modulus() + ); + + assert!( + output.ciphertext_modulus().is_native_modulus(), + "This operation only supports native moduli" + ); + + let mut binary_random_vector = vec![Scalar::ZERO; lwe_compact_public_key.lwe_dimension().0]; + + secret_generator.fill_slice_with_random_uniform_binary(&mut binary_random_vector); + + let (mut ct_mask, ct_body) = output.get_mut_mask_and_body(); + let (pk_mask, pk_body) = lwe_compact_public_key.get_mask_and_body(); + + slice_semi_reverse_negacyclic_convolution( + ct_mask.as_mut(), + pk_mask.as_ref(), + &binary_random_vector, + ); + + // Noise from Chi_1 for the mask part of the encryption + encryption_generator.unsigned_torus_slice_wrapping_add_random_noise_assign( + ct_mask.as_mut(), + mask_noise_parameters, + ); + + *ct_body.data = slice_wrapping_dot_product(pk_body.as_ref(), &binary_random_vector); + // Noise from Chi_2 for the body part of the encryption + *ct_body.data = + (*ct_body.data).wrapping_add(encryption_generator.random_noise(body_noise_parameters)); + *ct_body.data = (*ct_body.data).wrapping_add(encoded.0); +} + +/// Encrypt an input plaintext list in an output [`LWE compact ciphertext +/// list`](`LweCompactCiphertextList`) using an [`LWE compact public key`](`LweCompactPublicKey`). +/// The expanded ciphertext list can be decrypted using the [`LWE secret key`](`LweSecretKey`) that +/// was used to generate the public key. +/// +/// # Example +/// +/// ``` +/// use tfhe::core_crypto::prelude::*; +/// +/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct +/// // computations +/// // Define parameters for LweCiphertext creation +/// let lwe_dimension = LweDimension(2048); +/// let lwe_ciphertext_count = LweCiphertextCount(lwe_dimension.0 * 4); +/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// +/// // Create the PRNG +/// let mut seeder = new_seeder(); +/// let seeder = seeder.as_mut(); +/// let mut encryption_generator = +/// EncryptionRandomGenerator::::new(seeder.seed(), seeder); +/// let mut secret_generator = +/// SecretRandomGenerator::::new(seeder.seed()); +/// +/// // Create the LweSecretKey +/// let lwe_secret_key = +/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator); +/// +/// let lwe_compact_public_key = allocate_and_generate_new_lwe_compact_public_key( +/// &lwe_secret_key, +/// glwe_modular_std_dev, +/// ciphertext_modulus, +/// &mut encryption_generator, +/// ); +/// +/// let mut input_plaintext_list = PlaintextList::new(0u64, PlaintextCount(lwe_ciphertext_count.0)); +/// input_plaintext_list +/// .iter_mut() +/// .enumerate() +/// .for_each(|(idx, x)| { +/// *x.0 = (idx as u64 % 16) << 60; +/// }); +/// +/// // Create a new LweCompactCiphertextList +/// let mut output_compact_ct_list = LweCompactCiphertextList::new( +/// 0u64, +/// lwe_dimension.to_lwe_size(), +/// lwe_ciphertext_count, +/// ciphertext_modulus, +/// ); +/// +/// encrypt_lwe_compact_ciphertext_list_with_compact_public_key( +/// &lwe_compact_public_key, +/// &mut output_compact_ct_list, +/// &input_plaintext_list, +/// glwe_modular_std_dev, +/// glwe_modular_std_dev, +/// &mut secret_generator, +/// &mut encryption_generator, +/// ); +/// +/// let mut output_plaintext_list = input_plaintext_list.clone(); +/// output_plaintext_list.as_mut().fill(0u64); +/// +/// let lwe_ciphertext_list = output_compact_ct_list.expand_into_lwe_ciphertext_list(); +/// +/// decrypt_lwe_ciphertext_list( +/// &lwe_secret_key, +/// &lwe_ciphertext_list, +/// &mut output_plaintext_list, +/// ); +/// +/// let signed_decomposer = +/// SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); +/// +/// // Round the plaintexts +/// output_plaintext_list +/// .iter_mut() +/// .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0)); +/// +/// // Check we recovered the original messages +/// assert_eq!(input_plaintext_list, output_plaintext_list); +/// ``` +pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< + Scalar, + KeyCont, + InputCont, + OutputCont, + SecretGen, + EncryptionGen, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCompactCiphertextList, + encoded: &PlaintextList, + mask_noise_parameters: impl DispersionParameter, + body_noise_parameters: impl DispersionParameter, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, +) where + Scalar: UnsignedTorus, + KeyCont: Container, + InputCont: Container, + OutputCont: ContainerMut, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, +{ + assert!( + output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(), + "Mismatch between LweDimension of output cipertext and input public key. \ + Got {:?} in output, and {:?} in public key.", + output.lwe_size().to_lwe_dimension(), + lwe_compact_public_key.lwe_dimension() + ); + + assert!( + lwe_compact_public_key.ciphertext_modulus() == output.ciphertext_modulus(), + "Mismatch between CiphertextModulus of output cipertext and input public key. \ + Got {:?} in output, and {:?} in public key.", + output.ciphertext_modulus(), + lwe_compact_public_key.ciphertext_modulus() + ); + + assert!( + output.lwe_ciphertext_count().0 == encoded.plaintext_count().0, + "Mismatch between LweCiphertextCount of output cipertext and \ + PlaintextCount of input list. Got {:?} in output, and {:?} in input plaintext list.", + output.lwe_ciphertext_count(), + encoded.plaintext_count() + ); + + assert!( + output.ciphertext_modulus().is_native_modulus(), + "This operation only supports native moduli" + ); + + let (mut output_mask_list, mut output_body_list) = output.get_mut_mask_and_body_list(); + let (pk_mask, pk_body) = lwe_compact_public_key.get_mask_and_body(); + + let lwe_mask_count = output_mask_list.lwe_mask_count(); + let lwe_dimension = output_mask_list.lwe_dimension(); + + let mut binary_random_vector = vec![Scalar::ZERO; output_mask_list.lwe_mask_list_size()]; + secret_generator.fill_slice_with_random_uniform_binary(&mut binary_random_vector); + + let max_ciphertext_per_bin = lwe_dimension.0; + + let gen_iter = encryption_generator + .fork_lwe_compact_ciphertext_list_to_bin::(lwe_mask_count, lwe_dimension) + .expect("Failed to split generator into lwe compact ciphertext bins"); + + // Loop over the ciphertext "bins" + output_mask_list + .iter_mut() + .zip( + output_body_list + .chunks_mut(max_ciphertext_per_bin) + .zip(encoded.chunks(max_ciphertext_per_bin)) + .zip(binary_random_vector.chunks(max_ciphertext_per_bin)) + .zip(gen_iter), + ) + .for_each( + |( + mut output_mask, + ( + ((mut output_body_chunk, input_plaintext_chunk), binary_random_slice), + mut loop_generator, + ), + )| { + // output_body_chunk may not be able to fit the full convolution result so we create + // a temp buffer to compute the full convolution + let mut pk_body_convolved = vec![Scalar::ZERO; lwe_dimension.0]; + + slice_semi_reverse_negacyclic_convolution( + output_mask.as_mut(), + pk_mask.as_ref(), + binary_random_slice, + ); + + // Fill the temp buffer with b convolved with r + slice_semi_reverse_negacyclic_convolution( + pk_body_convolved.as_mut_slice(), + pk_body.as_ref(), + binary_random_slice, + ); + + // Noise from Chi_1 for the mask part of the encryption + loop_generator.unsigned_torus_slice_wrapping_add_random_noise_assign( + output_mask.as_mut(), + mask_noise_parameters, + ); + + // Fill the body chunk afterwards manually as it most likely will be smaller than + // the full convolution result. b convolved r + Delta * m + e2 + // taking noise from Chi_2 for the body part of the encryption + output_body_chunk + .iter_mut() + .zip(pk_body_convolved.iter().zip(input_plaintext_chunk.iter())) + .for_each(|(dst, (&src, plaintext))| { + *dst.data = src + .wrapping_add(loop_generator.random_noise(body_noise_parameters)) + .wrapping_add(*plaintext.0) + }); + }, + ); +} + +/// Parallel variant of [`encrypt_lwe_compact_ciphertext_list_with_compact_public_key`]. Encrypt an +/// input plaintext list in an output [`LWE compact ciphertext list`](`LweCompactCiphertextList`) +/// using an [`LWE compact public key`](`LweCompactPublicKey`). The expanded ciphertext list can be +/// decrypted using the [`LWE secret key`](`LweSecretKey`) that was used to generate the public key. +/// +/// # Example +/// +/// ``` +/// use tfhe::core_crypto::prelude::*; +/// +/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct +/// // computations +/// // Define parameters for LweCiphertext creation +/// let lwe_dimension = LweDimension(2048); +/// let lwe_ciphertext_count = LweCiphertextCount(lwe_dimension.0 * 4); +/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// +/// // Create the PRNG +/// let mut seeder = new_seeder(); +/// let seeder = seeder.as_mut(); +/// let mut encryption_generator = +/// EncryptionRandomGenerator::::new(seeder.seed(), seeder); +/// let mut secret_generator = +/// SecretRandomGenerator::::new(seeder.seed()); +/// +/// // Create the LweSecretKey +/// let lwe_secret_key = +/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator); +/// +/// let lwe_compact_public_key = allocate_and_generate_new_lwe_compact_public_key( +/// &lwe_secret_key, +/// glwe_modular_std_dev, +/// ciphertext_modulus, +/// &mut encryption_generator, +/// ); +/// +/// let mut input_plaintext_list = PlaintextList::new(0u64, PlaintextCount(lwe_ciphertext_count.0)); +/// input_plaintext_list +/// .iter_mut() +/// .enumerate() +/// .for_each(|(idx, x)| { +/// *x.0 = (idx as u64 % 16) << 60; +/// }); +/// +/// // Create a new LweCompactCiphertextList +/// let mut output_compact_ct_list = LweCompactCiphertextList::new( +/// 0u64, +/// lwe_dimension.to_lwe_size(), +/// lwe_ciphertext_count, +/// ciphertext_modulus, +/// ); +/// +/// par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key( +/// &lwe_compact_public_key, +/// &mut output_compact_ct_list, +/// &input_plaintext_list, +/// glwe_modular_std_dev, +/// glwe_modular_std_dev, +/// &mut secret_generator, +/// &mut encryption_generator, +/// ); +/// +/// let mut output_plaintext_list = input_plaintext_list.clone(); +/// output_plaintext_list.as_mut().fill(0u64); +/// +/// let lwe_ciphertext_list = output_compact_ct_list.par_expand_into_lwe_ciphertext_list(); +/// +/// decrypt_lwe_ciphertext_list( +/// &lwe_secret_key, +/// &lwe_ciphertext_list, +/// &mut output_plaintext_list, +/// ); +/// +/// let signed_decomposer = +/// SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); +/// +/// // Round the plaintexts +/// output_plaintext_list +/// .iter_mut() +/// .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0)); +/// +/// // Check we recovered the original messages +/// assert_eq!(input_plaintext_list, output_plaintext_list); +/// ``` +pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< + Scalar, + KeyCont, + InputCont, + OutputCont, + SecretGen, + EncryptionGen, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCompactCiphertextList, + encoded: &PlaintextList, + mask_noise_parameters: impl DispersionParameter + Sync, + body_noise_parameters: impl DispersionParameter + Sync, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, +) where + Scalar: UnsignedTorus + Sync + Send, + KeyCont: Container, + InputCont: Container, + OutputCont: ContainerMut, + SecretGen: ByteRandomGenerator, + EncryptionGen: ParallelByteRandomGenerator, +{ + assert!( + output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(), + "Mismatch between LweDimension of output cipertext and input public key. \ + Got {:?} in output, and {:?} in public key.", + output.lwe_size().to_lwe_dimension(), + lwe_compact_public_key.lwe_dimension() + ); + + assert!( + lwe_compact_public_key.ciphertext_modulus() == output.ciphertext_modulus(), + "Mismatch between CiphertextModulus of output cipertext and input public key. \ + Got {:?} in output, and {:?} in public key.", + output.ciphertext_modulus(), + lwe_compact_public_key.ciphertext_modulus() + ); + + assert!( + output.lwe_ciphertext_count().0 == encoded.plaintext_count().0, + "Mismatch between LweCiphertextCount of output cipertext and \ + PlaintextCount of input list. Got {:?} in output, and {:?} in input plaintext list.", + output.lwe_ciphertext_count(), + encoded.plaintext_count() + ); + + assert!( + output.ciphertext_modulus().is_native_modulus(), + "This operation only supports native moduli" + ); + + let (mut output_mask_list, mut output_body_list) = output.get_mut_mask_and_body_list(); + let (pk_mask, pk_body) = lwe_compact_public_key.get_mask_and_body(); + + let lwe_mask_count = output_mask_list.lwe_mask_count(); + let lwe_dimension = output_mask_list.lwe_dimension(); + + let mut binary_random_vector = vec![Scalar::ZERO; output_mask_list.lwe_mask_list_size()]; + secret_generator.fill_slice_with_random_uniform_binary(&mut binary_random_vector); + + let max_ciphertext_per_bin = lwe_dimension.0; + + let gen_iter = encryption_generator + .par_fork_lwe_compact_ciphertext_list_to_bin::(lwe_mask_count, lwe_dimension) + .expect("Failed to split generator into lwe compact ciphertext bins"); + + // Loop over the ciphertext "bins" + output_mask_list + .par_iter_mut() + .zip( + output_body_list + .par_chunks_mut(max_ciphertext_per_bin) + .zip(encoded.par_chunks(max_ciphertext_per_bin)) + .zip(binary_random_vector.par_chunks(max_ciphertext_per_bin)) + .zip(gen_iter), + ) + .for_each( + |( + mut output_mask, + ( + ((mut output_body_chunk, input_plaintext_chunk), binary_random_slice), + mut loop_generator, + ), + )| { + // output_body_chunk may not be able to fit the full convolution result so we create + // a temp buffer to compute the full convolution + let mut pk_body_convolved = vec![Scalar::ZERO; lwe_dimension.0]; + + rayon::join( + || { + slice_semi_reverse_negacyclic_convolution( + output_mask.as_mut(), + pk_mask.as_ref(), + binary_random_slice, + ); + }, + || { + // Fill the temp buffer with b convolved with r + slice_semi_reverse_negacyclic_convolution( + pk_body_convolved.as_mut_slice(), + pk_body.as_ref(), + binary_random_slice, + ); + }, + ); + + // Noise from Chi_1 for the mask part of the encryption + loop_generator.unsigned_torus_slice_wrapping_add_random_noise_assign( + output_mask.as_mut(), + mask_noise_parameters, + ); + + // Fill the body chunk afterwards manually as it most likely will be smaller than + // the full convolution result. b convolved r + Delta * m + e2 + // taking noise from Chi_2 for the body part of the encryption + output_body_chunk + .iter_mut() + .zip(pk_body_convolved.iter().zip(input_plaintext_chunk.iter())) + .for_each(|(dst, (&src, plaintext))| { + *dst.data = src + .wrapping_add(loop_generator.random_noise(body_noise_parameters)) + .wrapping_add(*plaintext.0) + }); + }, + ); +} + +#[cfg(test)] +mod test { + use crate::core_crypto::commons::test_tools; + use crate::core_crypto::prelude::*; + + use crate::core_crypto::commons::generators::{ + DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator, + }; + use crate::core_crypto::commons::math::random::ActivatedRandomGenerator; + + #[test] + fn test_compact_public_key_encryption() { + use rand::Rng; + + let lwe_dimension = LweDimension(2048); + let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533); + let ciphertext_modulus = CiphertextModulus::new_native(); + + let mut secret_random_generator = test_tools::new_secret_random_generator(); + let mut encryption_random_generator = test_tools::new_encryption_random_generator(); + + let mut thread_rng = rand::thread_rng(); + + for _ in 0..10_000 { + let lwe_sk = + LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_random_generator); + + let mut compact_lwe_pk = + LweCompactPublicKey::new(0u64, lwe_dimension, ciphertext_modulus); + + generate_lwe_compact_public_key( + &lwe_sk, + &mut compact_lwe_pk, + glwe_modular_std_dev, + &mut encryption_random_generator, + ); + + let msg: u64 = thread_rng.gen(); + let msg = msg % 16; + + let plaintext = Plaintext(msg << 60); + + let mut output_ct = LweCiphertext::new( + 0u64, + lwe_dimension.to_lwe_size(), + CiphertextModulus::new_native(), + ); + + encrypt_lwe_ciphertext_with_compact_public_key( + &compact_lwe_pk, + &mut output_ct, + plaintext, + glwe_modular_std_dev, + glwe_modular_std_dev, + &mut secret_random_generator, + &mut encryption_random_generator, + ); + + let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_sk, &output_ct); + + let signed_decomposer = + SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); + + let cleartext = signed_decomposer.closest_representable(decrypted_plaintext.0) >> 60; + + assert_eq!(cleartext, msg); + } + } + + #[test] + fn test_par_compact_lwe_list_public_key_encryption_equivalence() { + use rand::Rng; + + let lwe_dimension = LweDimension(2048); + let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533); + let ciphertext_modulus = CiphertextModulus::new_native(); + + let mut thread_rng = rand::thread_rng(); + + for _ in 0..100 { + // We'll encrypt between 1 and 4 * lwe_dimension ciphertexts + let ct_count: usize = thread_rng.gen(); + let ct_count = ct_count % (lwe_dimension.0 * 4) + 1; + let lwe_ciphertext_count = LweCiphertextCount(ct_count); + + println!("{lwe_dimension:?} {ct_count:?}"); + + let seed = test_tools::random_seed(); + let mut input_plaintext_list = + PlaintextList::new(0u64, PlaintextCount(lwe_ciphertext_count.0)); + input_plaintext_list.iter_mut().for_each(|x| { + let msg: u64 = thread_rng.gen(); + *x.0 = (msg % 16) << 60; + }); + + let par_lwe_ct_list = { + let mut deterministic_seeder = + DeterministicSeeder::::new(seed); + let mut secret_random_generator = + SecretRandomGenerator::::new( + deterministic_seeder.seed(), + ); + let mut encryption_random_generator = + EncryptionRandomGenerator::::new( + deterministic_seeder.seed(), + &mut deterministic_seeder, + ); + + let lwe_sk = + LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_random_generator); + + let mut compact_lwe_pk = + LweCompactPublicKey::new(0u64, lwe_dimension, ciphertext_modulus); + + generate_lwe_compact_public_key( + &lwe_sk, + &mut compact_lwe_pk, + glwe_modular_std_dev, + &mut encryption_random_generator, + ); + + let mut output_compact_ct_list = LweCompactCiphertextList::new( + 0u64, + lwe_dimension.to_lwe_size(), + lwe_ciphertext_count, + ciphertext_modulus, + ); + + par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key( + &compact_lwe_pk, + &mut output_compact_ct_list, + &input_plaintext_list, + glwe_modular_std_dev, + glwe_modular_std_dev, + &mut secret_random_generator, + &mut encryption_random_generator, + ); + + let mut output_plaintext_list = input_plaintext_list.clone(); + output_plaintext_list.as_mut().fill(0u64); + + let lwe_ciphertext_list = + output_compact_ct_list.par_expand_into_lwe_ciphertext_list(); + + decrypt_lwe_ciphertext_list( + &lwe_sk, + &lwe_ciphertext_list, + &mut output_plaintext_list, + ); + + let signed_decomposer = + SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); + + output_plaintext_list + .iter_mut() + .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0)); + + assert_eq!(input_plaintext_list, output_plaintext_list); + + lwe_ciphertext_list + }; + + let ser_lwe_ct_list = { + let mut deterministic_seeder = + DeterministicSeeder::::new(seed); + let mut secret_random_generator = + SecretRandomGenerator::::new( + deterministic_seeder.seed(), + ); + let mut encryption_random_generator = + EncryptionRandomGenerator::::new( + deterministic_seeder.seed(), + &mut deterministic_seeder, + ); + + let lwe_sk = + LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_random_generator); + + let mut compact_lwe_pk = + LweCompactPublicKey::new(0u64, lwe_dimension, ciphertext_modulus); + + generate_lwe_compact_public_key( + &lwe_sk, + &mut compact_lwe_pk, + glwe_modular_std_dev, + &mut encryption_random_generator, + ); + + let mut output_compact_ct_list = LweCompactCiphertextList::new( + 0u64, + lwe_dimension.to_lwe_size(), + lwe_ciphertext_count, + ciphertext_modulus, + ); + + encrypt_lwe_compact_ciphertext_list_with_compact_public_key( + &compact_lwe_pk, + &mut output_compact_ct_list, + &input_plaintext_list, + glwe_modular_std_dev, + glwe_modular_std_dev, + &mut secret_random_generator, + &mut encryption_random_generator, + ); + + let mut output_plaintext_list = input_plaintext_list.clone(); + output_plaintext_list.as_mut().fill(0u64); + + let lwe_ciphertext_list = output_compact_ct_list.expand_into_lwe_ciphertext_list(); + + decrypt_lwe_ciphertext_list( + &lwe_sk, + &lwe_ciphertext_list, + &mut output_plaintext_list, + ); + + let signed_decomposer = + SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); + + output_plaintext_list + .iter_mut() + .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0)); + + assert_eq!(input_plaintext_list, output_plaintext_list); + + lwe_ciphertext_list + }; + + assert_eq!(ser_lwe_ct_list, par_lwe_ct_list); + } + } +} diff --git a/tfhe/src/core_crypto/algorithms/mod.rs b/tfhe/src/core_crypto/algorithms/mod.rs index 6d81a7f13..515851561 100644 --- a/tfhe/src/core_crypto/algorithms/mod.rs +++ b/tfhe/src/core_crypto/algorithms/mod.rs @@ -9,6 +9,8 @@ pub mod glwe_sample_extraction; pub mod glwe_secret_key_generation; pub mod lwe_bootstrap_key_conversion; pub mod lwe_bootstrap_key_generation; +pub mod lwe_compact_ciphertext_list_expansion; +pub mod lwe_compact_public_key_generation; pub mod lwe_encryption; pub mod lwe_keyswitch; pub mod lwe_keyswitch_key_generation; @@ -31,6 +33,7 @@ pub mod seeded_glwe_ciphertext_list_decompression; pub mod seeded_lwe_bootstrap_key_decompression; pub mod seeded_lwe_ciphertext_decompression; pub mod seeded_lwe_ciphertext_list_decompression; +pub mod seeded_lwe_compact_public_key_decompression; pub mod seeded_lwe_keyswitch_key_decompression; pub mod seeded_lwe_multi_bit_bootstrap_key_decompression; pub mod seeded_lwe_public_key_decompression; @@ -48,6 +51,8 @@ pub use glwe_sample_extraction::*; pub use glwe_secret_key_generation::*; pub use lwe_bootstrap_key_conversion::*; pub use lwe_bootstrap_key_generation::*; +pub use lwe_compact_ciphertext_list_expansion::*; +pub use lwe_compact_public_key_generation::*; pub use lwe_encryption::*; pub use lwe_keyswitch::*; pub use lwe_keyswitch_key_generation::*; @@ -68,6 +73,7 @@ pub use seeded_glwe_ciphertext_list_decompression::*; pub use seeded_lwe_bootstrap_key_decompression::*; pub use seeded_lwe_ciphertext_decompression::*; pub use seeded_lwe_ciphertext_list_decompression::*; +pub use seeded_lwe_compact_public_key_decompression::*; pub use seeded_lwe_keyswitch_key_decompression::*; pub use seeded_lwe_multi_bit_bootstrap_key_decompression::*; pub use seeded_lwe_public_key_decompression::*; diff --git a/tfhe/src/core_crypto/algorithms/seeded_lwe_compact_public_key_decompression.rs b/tfhe/src/core_crypto/algorithms/seeded_lwe_compact_public_key_decompression.rs new file mode 100644 index 000000000..9c88b8096 --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/seeded_lwe_compact_public_key_decompression.rs @@ -0,0 +1,49 @@ +//! Module with primitives pertaining to [`SeededLweCompactPublicKey`] decompression. + +use crate::core_crypto::algorithms::*; +use crate::core_crypto::commons::math::random::RandomGenerator; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// Convenience function to share the core logic of the decompression algorithm for +/// [`SeededLweCompactPublicKey`] between all functions needing it. +pub fn decompress_seeded_lwe_compact_public_key_with_existing_generator< + Scalar, + InputCont, + OutputCont, + Gen, +>( + output_cpk: &mut LweCompactPublicKey, + input_seeded_cpk: &SeededLweCompactPublicKey, + generator: &mut RandomGenerator, +) where + Scalar: UnsignedTorus, + InputCont: Container, + OutputCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + decompress_seeded_glwe_ciphertext_with_existing_generator( + &mut output_cpk.as_mut_glwe_ciphertext(), + &input_seeded_cpk.as_seeded_glwe_ciphertext(), + generator, + ); +} + +/// Decompress a [`SeededLweCompactPublicKey`], without consuming it, into a standard +/// [`LweCompactPublicKey`]. +pub fn decompress_seeded_lwe_compact_public_key( + output_cpk: &mut LweCompactPublicKey, + input_seeded_cpk: &SeededLweCompactPublicKey, +) where + Scalar: UnsignedTorus, + InputCont: Container, + OutputCont: ContainerMut, + Gen: ByteRandomGenerator, +{ + let mut generator = RandomGenerator::::new(input_seeded_cpk.compression_seed().seed); + decompress_seeded_lwe_compact_public_key_with_existing_generator::<_, _, _, Gen>( + output_cpk, + input_seeded_cpk, + &mut generator, + ) +} diff --git a/tfhe/src/core_crypto/algorithms/slice_algorithms.rs b/tfhe/src/core_crypto/algorithms/slice_algorithms.rs index be8f5d9a7..5fcb0aac1 100644 --- a/tfhe/src/core_crypto/algorithms/slice_algorithms.rs +++ b/tfhe/src/core_crypto/algorithms/slice_algorithms.rs @@ -1,6 +1,8 @@ //! Module providing algorithms to perform computations on raw slices. +use crate::core_crypto::algorithms::polynomial_algorithms::polynomial_wrapping_add_mul_assign; use crate::core_crypto::commons::numeric::UnsignedInteger; +use crate::core_crypto::entities::Polynomial; /// Compute a dot product between two slices containing unsigned integers. /// @@ -309,3 +311,54 @@ where lhs.iter_mut() .for_each(|lhs| *lhs = (*lhs).wrapping_div(rhs)); } + +/// Primitive for compact LWE public key +/// +/// Here $i$ from section 3 of is taken equal to $n$. +/// ``` +/// use tfhe::core_crypto::algorithms::slice_algorithms::*; +/// let lhs = vec![1u8, 2u8, 3u8]; +/// let rhs = vec![4u8, 5u8, 6u8]; +/// let mut output = vec![0u8; 3]; +/// slice_semi_reverse_negacyclic_convolution(&mut output, &lhs, &rhs); +/// assert_eq!(&output, &[(-17i8) as u8, 5, 32]); +/// ``` +pub fn slice_semi_reverse_negacyclic_convolution( + output: &mut [Scalar], + lhs: &[Scalar], + rhs: &[Scalar], +) where + Scalar: UnsignedInteger, +{ + assert!( + lhs.len() == rhs.len(), + "lhs (len: {}) and rhs (len: {}) must have the same length", + lhs.len(), + rhs.len() + ); + assert!( + output.len() == lhs.len(), + "output (len: {}) and lhs (len: {}) must have the same length", + output.len(), + lhs.len() + ); + + // Apply phi_1 to the rhs term + let mut phi_1_rhs: Vec<_> = rhs.to_vec(); + phi_1_rhs.reverse(); + + let phi_1_rhs_as_polynomial = Polynomial::from_container(phi_1_rhs.as_slice()); + + // Clear output as we'll add the multiplication result + output.fill(Scalar::ZERO); + let mut output_as_polynomial = Polynomial::from_container(output); + let lhs_as_polynomial = Polynomial::from_container(lhs); + + // Apply the classic negacyclic convolution via polynomial mul in the X^N + 1 ring, with the + // phi_1 rhs it is equivalent to the operator we need + polynomial_wrapping_add_mul_assign( + &mut output_as_polynomial, + &lhs_as_polynomial, + &phi_1_rhs_as_polynomial, + ); +} diff --git a/tfhe/src/core_crypto/algorithms/test/lwe_compact_public_key_generation.rs b/tfhe/src/core_crypto/algorithms/test/lwe_compact_public_key_generation.rs new file mode 100644 index 000000000..6ef1caae3 --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/test/lwe_compact_public_key_generation.rs @@ -0,0 +1,83 @@ +use super::*; + +use crate::core_crypto::commons::generators::{ + DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator, +}; +use crate::core_crypto::commons::math::random::ActivatedRandomGenerator; + +fn test_seeded_lwe_cpk_gen_equivalence( + ciphertext_modulus: CiphertextModulus, +) { + // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct + // computations + // Define parameters for LweCompactPublicKey creation + let lwe_dimension = LweDimension(1024); + let lwe_modular_std_dev = StandardDev(0.00000004990272175010415); + + // Create the PRNG + let mut seeder = new_seeder(); + let seeder = seeder.as_mut(); + let mask_seed = seeder.seed(); + let deterministic_seeder_seed = seeder.seed(); + let mut secret_generator = + SecretRandomGenerator::::new(seeder.seed()); + + const NB_TEST: usize = 10; + + for _ in 0..NB_TEST { + // Create the LweSecretKey + let input_lwe_secret_key = + allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator); + + let mut cpk = LweCompactPublicKey::new(Scalar::ZERO, lwe_dimension, ciphertext_modulus); + + let mut deterministic_seeder = + DeterministicSeeder::::new(deterministic_seeder_seed); + let mut encryption_generator = EncryptionRandomGenerator::::new( + mask_seed, + &mut deterministic_seeder, + ); + + generate_lwe_compact_public_key( + &input_lwe_secret_key, + &mut cpk, + lwe_modular_std_dev, + &mut encryption_generator, + ); + + assert!(check_content_respects_mod(&cpk, ciphertext_modulus)); + + let mut seeded_cpk = SeededLweCompactPublicKey::new( + Scalar::ZERO, + lwe_dimension, + mask_seed.into(), + ciphertext_modulus, + ); + + let mut deterministic_seeder = + DeterministicSeeder::::new(deterministic_seeder_seed); + + generate_seeded_lwe_compact_public_key( + &input_lwe_secret_key, + &mut seeded_cpk, + lwe_modular_std_dev, + &mut deterministic_seeder, + ); + + assert!(check_content_respects_mod(&seeded_cpk, ciphertext_modulus)); + + let decompressed_cpk = seeded_cpk.decompress_into_lwe_compact_public_key(); + + assert_eq!(cpk, decompressed_cpk); + } +} + +#[test] +fn test_seeded_lwe_cpk_gen_equivalence_u32_native_mod() { + test_seeded_lwe_cpk_gen_equivalence::(CiphertextModulus::new_native()) +} + +#[test] +fn test_seeded_lwe_cpk_gen_equivalence_u64_naive_mod() { + test_seeded_lwe_cpk_gen_equivalence::(CiphertextModulus::new_native()) +} diff --git a/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs b/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs index 16833c4a4..4f440c68e 100644 --- a/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs +++ b/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs @@ -827,3 +827,67 @@ fn test_u128_encryption() { } } } + +fn lwe_compact_public_encrypt_decrypt_custom_mod( + params: TestParams, +) { + let lwe_dimension = LweDimension(params.polynomial_size.0); + let glwe_modular_std_dev = params.glwe_modular_std_dev; + let ciphertext_modulus = params.ciphertext_modulus; + let message_modulus_log = params.message_modulus_log; + let encoding_with_padding = get_encoding_with_padding(ciphertext_modulus); + + let mut rsc = TestResources::new(); + + const NB_TESTS: usize = 10; + let msg_modulus = Scalar::ONE.shl(message_modulus_log.0); + let mut msg = msg_modulus; + let delta: Scalar = encoding_with_padding / msg_modulus; + + while msg != Scalar::ZERO { + msg = msg.wrapping_sub(Scalar::ONE); + for _ in 0..NB_TESTS { + let lwe_sk = allocate_and_generate_new_binary_lwe_secret_key( + lwe_dimension, + &mut rsc.secret_random_generator, + ); + + let pk = allocate_and_generate_new_lwe_compact_public_key( + &lwe_sk, + glwe_modular_std_dev, + ciphertext_modulus, + &mut rsc.encryption_random_generator, + ); + + let mut ct = LweCiphertext::new( + Scalar::ZERO, + lwe_dimension.to_lwe_size(), + ciphertext_modulus, + ); + + let plaintext = Plaintext(msg * delta); + + encrypt_lwe_ciphertext_with_compact_public_key( + &pk, + &mut ct, + plaintext, + glwe_modular_std_dev, + glwe_modular_std_dev, + &mut rsc.secret_random_generator, + &mut rsc.encryption_random_generator, + ); + + assert!(check_content_respects_mod(&ct, ciphertext_modulus)); + + let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); + + let decoded = round_decode(decrypted.0, delta) % msg_modulus; + + assert_eq!(msg, decoded); + } + } +} + +create_parametrized_test!(lwe_compact_public_encrypt_decrypt_custom_mod { + TEST_PARAMS_4_BITS_NATIVE_U64 +}); diff --git a/tfhe/src/core_crypto/algorithms/test/mod.rs b/tfhe/src/core_crypto/algorithms/test/mod.rs index 24d31e6fa..1b189a72c 100644 --- a/tfhe/src/core_crypto/algorithms/test/mod.rs +++ b/tfhe/src/core_crypto/algorithms/test/mod.rs @@ -4,6 +4,7 @@ use paste::paste; mod ggsw_encryption; mod glwe_encryption; mod lwe_bootstrap_key_generation; +mod lwe_compact_public_key_generation; mod lwe_encryption; mod lwe_keyswitch; mod lwe_keyswitch_key_generation; diff --git a/tfhe/src/core_crypto/commons/generators/encryption.rs b/tfhe/src/core_crypto/commons/generators/encryption.rs index ca0b06b20..78c19c178 100644 --- a/tfhe/src/core_crypto/commons/generators/encryption.rs +++ b/tfhe/src/core_crypto/commons/generators/encryption.rs @@ -9,7 +9,8 @@ use crate::core_crypto::commons::math::torus::UnsignedTorus; use crate::core_crypto::commons::numeric::{CastInto, UnsignedInteger}; use crate::core_crypto::commons::parameters::{ CiphertextModulus, DecompositionLevelCount, FunctionalPackingKeyswitchKeyCount, GlweDimension, - GlweSize, LweBskGroupingFactor, LweCiphertextCount, LweDimension, LweSize, PolynomialSize, + GlweSize, LweBskGroupingFactor, LweCiphertextCount, LweDimension, LweMaskCount, LweSize, + PolynomialSize, }; use concrete_csprng::generators::ForkError; use rayon::prelude::*; @@ -169,6 +170,16 @@ impl EncryptionRandomGenerator { self.try_fork(lwe_size.0, mask_bytes, noise_bytes) } + pub(crate) fn fork_lwe_compact_ciphertext_list_to_bin( + &mut self, + lwe_mask_count: LweMaskCount, + lwe_dimension: LweDimension, + ) -> Result>, ForkError> { + let mask_bytes = mask_bytes_per_lwe_compact_ciphertext_bin::(lwe_dimension); + let noise_bytes = noise_bytes_per_lwe_compact_ciphertext_bin(lwe_dimension); + self.try_fork(lwe_mask_count.0, mask_bytes, noise_bytes) + } + // Forks both generators into an iterator fn try_fork( &mut self, @@ -431,6 +442,16 @@ impl EncryptionRandomGenerator { self.par_try_fork(lwe_size.0, mask_bytes, noise_bytes) } + pub(crate) fn par_fork_lwe_compact_ciphertext_list_to_bin( + &mut self, + lwe_mask_count: LweMaskCount, + lwe_dimension: LweDimension, + ) -> Result>, ForkError> { + let mask_bytes = mask_bytes_per_lwe_compact_ciphertext_bin::(lwe_dimension); + let noise_bytes = noise_bytes_per_lwe_compact_ciphertext_bin(lwe_dimension); + self.par_try_fork(lwe_mask_count.0, mask_bytes, noise_bytes) + } + // Forks both generators into a parallel iterator. fn par_try_fork( &mut self, @@ -504,6 +525,12 @@ fn mask_bytes_per_pfpksk( lwe_size.0 * mask_bytes_per_pfpksk_chunk::(level, glwe_size, poly_size) } +fn mask_bytes_per_lwe_compact_ciphertext_bin( + lwe_dimension: LweDimension, +) -> usize { + lwe_dimension.0 * mask_bytes_per_coef::() +} + fn noise_bytes_per_coef() -> usize { // We use f64 to sample the noise for every precision, and we need 4/pi inputs to generate // such an output (here we take 32 to keep a safety margin). @@ -553,6 +580,10 @@ fn noise_bytes_per_pfpksk( lwe_size.0 * noise_bytes_per_pfpksk_chunk(level, poly_size) } +fn noise_bytes_per_lwe_compact_ciphertext_bin(lwe_dimension: LweDimension) -> usize { + lwe_dimension.0 * noise_bytes_per_coef() +} + #[cfg(test)] mod test { use crate::core_crypto::algorithms::*; diff --git a/tfhe/src/core_crypto/commons/parameters.rs b/tfhe/src/core_crypto/commons/parameters.rs index 124804e72..1f26f1561 100644 --- a/tfhe/src/core_crypto/commons/parameters.rs +++ b/tfhe/src/core_crypto/commons/parameters.rs @@ -79,6 +79,14 @@ impl LweDimension { #[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] pub struct LwePublicKeyZeroEncryptionCount(pub usize); +/// The number of masks in a collection of LWE masks. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct LweMaskCount(pub usize); + +/// The number of bodues in a collection of LWE bodies. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct LweBodyCount(pub usize); + /// The number of polynomials in a GLWE ciphertext, i.e. the number of polynomials in a GLWE mask /// plus one. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Serialize, Deserialize)] diff --git a/tfhe/src/core_crypto/commons/traits/contiguous_entity_container.rs b/tfhe/src/core_crypto/commons/traits/contiguous_entity_container.rs index 63b47ccab..55ffeb16e 100644 --- a/tfhe/src/core_crypto/commons/traits/contiguous_entity_container.rs +++ b/tfhe/src/core_crypto/commons/traits/contiguous_entity_container.rs @@ -12,7 +12,15 @@ type WrappingFunction<'data, Element, WrappingType> = fn( ), ) -> WrappingType; -type WrappingLendingIterator<'data, Element, WrappingType> = std::iter::Map< +type ChunksWrappingLendingIterator<'data, Element, WrappingType> = std::iter::Map< + std::iter::Zip< + std::slice::Chunks<'data, Element>, + itertools::RepeatN<>::Metadata>, + >, + WrappingFunction<'data, Element, WrappingType>, +>; + +type ChunksExactWrappingLendingIterator<'data, Element, WrappingType> = std::iter::Map< std::iter::Zip< std::slice::ChunksExact<'data, Element>, itertools::RepeatN<>::Metadata>, @@ -20,7 +28,15 @@ type WrappingLendingIterator<'data, Element, WrappingType> = std::iter::Map< WrappingFunction<'data, Element, WrappingType>, >; -type ParallelWrappingLendingIterator<'data, Element, WrappingType> = rayon::iter::Map< +type ParallelChunksWrappingLendingIterator<'data, Element, WrappingType> = rayon::iter::Map< + rayon::iter::Zip< + rayon::slice::Chunks<'data, Element>, + rayon::iter::RepeatN<>::Metadata>, + >, + WrappingFunction<'data, Element, WrappingType>, +>; + +type ParallelChunksExactWrappingLendingIterator<'data, Element, WrappingType> = rayon::iter::Map< rayon::iter::Zip< rayon::slice::ChunksExact<'data, Element>, rayon::iter::RepeatN<>::Metadata>, @@ -35,7 +51,15 @@ type WrappingFunctionMut<'data, Element, WrappingType> = fn( ), ) -> WrappingType; -type WrappingLendingIteratorMut<'data, Element, WrappingType> = std::iter::Map< +type ChunksWrappingLendingIteratorMut<'data, Element, WrappingType> = std::iter::Map< + std::iter::Zip< + std::slice::ChunksMut<'data, Element>, + itertools::RepeatN<>::Metadata>, + >, + WrappingFunctionMut<'data, Element, WrappingType>, +>; + +type ChunksExactWrappingLendingIteratorMut<'data, Element, WrappingType> = std::iter::Map< std::iter::Zip< std::slice::ChunksExactMut<'data, Element>, itertools::RepeatN<>::Metadata>, @@ -43,7 +67,15 @@ type WrappingLendingIteratorMut<'data, Element, WrappingType> = std::iter::Map< WrappingFunctionMut<'data, Element, WrappingType>, >; -type ParallelWrappingLendingIteratorMut<'data, Element, WrappingType> = rayon::iter::Map< +type ParallelChunksWrappingLendingIteratorMut<'data, Element, WrappingType> = rayon::iter::Map< + rayon::iter::Zip< + rayon::slice::ChunksMut<'data, Element>, + rayon::iter::RepeatN<>::Metadata>, + >, + WrappingFunctionMut<'data, Element, WrappingType>, +>; + +type ParallelChunksExactWrappingLendingIteratorMut<'data, Element, WrappingType> = rayon::iter::Map< rayon::iter::Zip< rayon::slice::ChunksExactMut<'data, Element>, rayon::iter::RepeatN<>::Metadata>, @@ -92,7 +124,7 @@ pub trait ContiguousEntityContainer: AsRef<[Self::Element]> { /// Return an iterator borrowing immutably from the current contiguous container which returns /// [`Self::EntityView`] entities. - fn iter(&self) -> WrappingLendingIterator<'_, Self::Element, Self::EntityView<'_>> { + fn iter(&self) -> ChunksExactWrappingLendingIterator<'_, Self::Element, Self::EntityView<'_>> { let meta = self.get_entity_view_creation_metadata(); let entity_count = self.entity_count(); let entity_view_pod_size = self.get_entity_view_pod_size(); @@ -165,10 +197,26 @@ pub trait ContiguousEntityContainer: AsRef<[Self::Element]> { } } + fn chunks( + &self, + chunk_size: usize, + ) -> ChunksWrappingLendingIterator<'_, Self::Element, Self::SelfView<'_>> { + let entity_count = self.entity_count(); + + let entity_view_pod_size = self.get_entity_view_pod_size(); + let pod_chunk_size = entity_view_pod_size * chunk_size; + + let meta = self.get_self_view_creation_metadata(); + self.as_ref() + .chunks(pod_chunk_size) + .zip(itertools::repeat_n(meta, entity_count)) + .map(|(elt, meta)| Self::SelfView::<'_>::create_from(elt, meta)) + } + fn chunks_exact( &self, chunk_size: usize, - ) -> WrappingLendingIterator<'_, Self::Element, Self::SelfView<'_>> { + ) -> ChunksExactWrappingLendingIterator<'_, Self::Element, Self::SelfView<'_>> { let entity_count = self.entity_count(); assert!( entity_count % chunk_size == 0, @@ -188,7 +236,7 @@ pub trait ContiguousEntityContainer: AsRef<[Self::Element]> { fn par_iter<'this>( &'this self, - ) -> ParallelWrappingLendingIterator<'this, Self::Element, Self::EntityView<'this>> + ) -> ParallelChunksExactWrappingLendingIterator<'this, Self::Element, Self::EntityView<'this>> where Self::Element: Sync, Self::EntityView<'this>: Send, @@ -202,6 +250,53 @@ pub trait ContiguousEntityContainer: AsRef<[Self::Element]> { .zip(rayon::iter::repeatn(meta, entity_count)) .map(|(elt, meta)| Self::EntityView::<'this>::create_from(elt, meta)) } + + fn par_chunks<'this>( + &'this self, + chunk_size: usize, + ) -> ParallelChunksWrappingLendingIterator<'_, Self::Element, Self::SelfView<'_>> + where + Self::Element: Sync, + Self::SelfView<'this>: Send, + Self::SelfViewMetadata: Send, + { + let entity_count = self.entity_count(); + + let entity_view_pod_size = self.get_entity_view_pod_size(); + let pod_chunk_size = entity_view_pod_size * chunk_size; + + let meta = self.get_self_view_creation_metadata(); + self.as_ref() + .par_chunks(pod_chunk_size) + .zip(rayon::iter::repeatn(meta, entity_count)) + .map(|(elt, meta)| Self::SelfView::<'_>::create_from(elt, meta)) + } + + fn par_chunks_exact<'this>( + &'this self, + chunk_size: usize, + ) -> ParallelChunksExactWrappingLendingIterator<'_, Self::Element, Self::SelfView<'_>> + where + Self::Element: Sync, + Self::SelfView<'this>: Send, + Self::SelfViewMetadata: Send, + { + let entity_count = self.entity_count(); + assert!( + entity_count % chunk_size == 0, + "The current container has {entity_count} entities, which is not dividable by the \ + requested chunk_size: {chunk_size}, preventing chunks_exact from returning an iterator." + ); + + let entity_view_pod_size = self.get_entity_view_pod_size(); + let pod_chunk_size = entity_view_pod_size * chunk_size; + + let meta = self.get_self_view_creation_metadata(); + self.as_ref() + .par_chunks_exact(pod_chunk_size) + .zip(rayon::iter::repeatn(meta, entity_count)) + .map(|(elt, meta)| Self::SelfView::<'_>::create_from(elt, meta)) + } } pub trait ContiguousEntityContainerMut: ContiguousEntityContainer + AsMut<[Self::Element]> { @@ -230,7 +325,7 @@ pub trait ContiguousEntityContainerMut: ContiguousEntityContainer + AsMut<[Self: /// [`Self::EntityMutView`] entities. fn iter_mut( &mut self, - ) -> WrappingLendingIteratorMut<'_, Self::Element, Self::EntityMutView<'_>> { + ) -> ChunksExactWrappingLendingIteratorMut<'_, Self::Element, Self::EntityMutView<'_>> { let meta = self.get_entity_view_creation_metadata(); let entity_count = self.entity_count(); let entity_view_pod_size = self.get_entity_view_pod_size(); @@ -300,10 +395,26 @@ pub trait ContiguousEntityContainerMut: ContiguousEntityContainer + AsMut<[Self: } } + fn chunks_mut( + &mut self, + chunk_size: usize, + ) -> ChunksWrappingLendingIteratorMut<'_, Self::Element, Self::SelfMutView<'_>> { + let entity_count = self.entity_count(); + + let entity_view_pod_size = self.get_entity_view_pod_size(); + let pod_chunk_size = entity_view_pod_size * chunk_size; + + let meta = self.get_self_view_creation_metadata(); + self.as_mut() + .chunks_mut(pod_chunk_size) + .zip(itertools::repeat_n(meta, entity_count)) + .map(|(elt, meta)| Self::SelfMutView::<'_>::create_from(elt, meta)) + } + fn chunks_exact_mut( &mut self, chunk_size: usize, - ) -> WrappingLendingIteratorMut<'_, Self::Element, Self::SelfMutView<'_>> { + ) -> ChunksExactWrappingLendingIteratorMut<'_, Self::Element, Self::SelfMutView<'_>> { let entity_count = self.entity_count(); assert!( entity_count % chunk_size == 0, @@ -324,7 +435,11 @@ pub trait ContiguousEntityContainerMut: ContiguousEntityContainer + AsMut<[Self: fn par_iter_mut<'this>( &'this mut self, - ) -> ParallelWrappingLendingIteratorMut<'this, Self::Element, Self::EntityMutView<'this>> + ) -> ParallelChunksExactWrappingLendingIteratorMut< + 'this, + Self::Element, + Self::EntityMutView<'this>, + > where Self::Element: Sync + Send, Self::EntityMutView<'this>: Send, @@ -338,4 +453,51 @@ pub trait ContiguousEntityContainerMut: ContiguousEntityContainer + AsMut<[Self: .zip(rayon::iter::repeatn(meta, entity_count)) .map(|(elt, meta)| Self::EntityMutView::<'this>::create_from(elt, meta)) } + + fn par_chunks_mut<'this>( + &'this mut self, + chunk_size: usize, + ) -> ParallelChunksWrappingLendingIteratorMut<'_, Self::Element, Self::SelfMutView<'_>> + where + Self::Element: Sync + Send, + Self::SelfMutView<'this>: Send, + Self::SelfViewMetadata: Send, + { + let entity_count = self.entity_count(); + + let entity_view_pod_size = self.get_entity_view_pod_size(); + let pod_chunk_size = entity_view_pod_size * chunk_size; + + let meta = self.get_self_view_creation_metadata(); + self.as_mut() + .par_chunks_mut(pod_chunk_size) + .zip(rayon::iter::repeatn(meta, entity_count)) + .map(|(elt, meta)| Self::SelfMutView::<'_>::create_from(elt, meta)) + } + + fn par_chunks_exact_mut<'this>( + &'this mut self, + chunk_size: usize, + ) -> ParallelChunksExactWrappingLendingIteratorMut<'_, Self::Element, Self::SelfMutView<'_>> + where + Self::Element: Sync + Send, + Self::SelfMutView<'this>: Send, + Self::SelfViewMetadata: Send, + { + let entity_count = self.entity_count(); + assert!( + entity_count % chunk_size == 0, + "The current container has {entity_count} entities, which is not dividable by the \ + requested chunk_size: {chunk_size}, preventing chunks_exact from returning an iterator." + ); + + let entity_view_pod_size = self.get_entity_view_pod_size(); + let pod_chunk_size = entity_view_pod_size * chunk_size; + + let meta = self.get_self_view_creation_metadata(); + self.as_mut() + .par_chunks_exact_mut(pod_chunk_size) + .zip(rayon::iter::repeatn(meta, entity_count)) + .map(|(elt, meta)| Self::SelfMutView::<'_>::create_from(elt, meta)) + } } diff --git a/tfhe/src/core_crypto/entities/glwe_ciphertext.rs b/tfhe/src/core_crypto/entities/glwe_ciphertext.rs index 1b22a702b..6fe29f976 100644 --- a/tfhe/src/core_crypto/entities/glwe_ciphertext.rs +++ b/tfhe/src/core_crypto/entities/glwe_ciphertext.rs @@ -278,7 +278,7 @@ pub fn glwe_ciphertext_mask_size( /// /// **Remark:** Observe that the decryption is followed by a decoding phase that will contain a /// rounding. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct GlweCiphertext where C::Element: UnsignedInteger, diff --git a/tfhe/src/core_crypto/entities/lwe_ciphertext.rs b/tfhe/src/core_crypto/entities/lwe_ciphertext.rs index eded1548a..46230bbdd 100644 --- a/tfhe/src/core_crypto/entities/lwe_ciphertext.rs +++ b/tfhe/src/core_crypto/entities/lwe_ciphertext.rs @@ -22,6 +22,188 @@ pub struct LweBodyRefMut<'a, Scalar: UnsignedInteger> { ciphertext_modulus: CiphertextModulus, } +impl LweBody { + pub fn new(data: Scalar, ciphertext_modulus: CiphertextModulus) -> Self { + Self { + data, + ciphertext_modulus, + } + } + + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.ciphertext_modulus + } +} + +impl<'outer, T: UnsignedInteger> LweBodyRef<'outer, T> { + pub fn new(data: &'outer T, ciphertext_modulus: CiphertextModulus) -> Self { + Self { + data, + ciphertext_modulus, + } + } + + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.ciphertext_modulus + } +} + +impl<'outer, T: UnsignedInteger> LweBodyRefMut<'outer, T> { + pub fn new(data: &'outer mut T, ciphertext_modulus: CiphertextModulus) -> Self { + Self { + data, + ciphertext_modulus, + } + } + + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.ciphertext_modulus + } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data [T]> for LweBodyRef<'data, T> { + type Metadata = LweBodyCreationMetadata; + + #[inline] + fn create_from(from: &[T], meta: Self::Metadata) -> LweBodyRef { + let LweBodyCreationMetadata(ciphertext_modulus) = meta; + LweBodyRef { + data: &from[0], + ciphertext_modulus, + } + } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data mut [T]> for LweBodyRefMut<'data, T> { + type Metadata = LweBodyCreationMetadata; + + #[inline] + fn create_from(from: &mut [T], meta: Self::Metadata) -> LweBodyRefMut { + let LweBodyCreationMetadata(ciphertext_modulus) = meta; + LweBodyRefMut { + data: &mut from[0], + ciphertext_modulus, + } + } +} + +#[derive(Clone, Debug)] +pub struct LweBodyList +where + C::Element: UnsignedInteger, +{ + data: C, + ciphertext_modulus: CiphertextModulus, +} + +pub type LweBodyListView<'data, Scalar> = LweBodyList<&'data [Scalar]>; +pub type LweBodyListMutView<'data, Scalar> = LweBodyList<&'data mut [Scalar]>; + +impl> AsRef<[T]> for LweBodyList { + fn as_ref(&self) -> &[T] { + self.data.as_ref() + } +} + +impl> AsMut<[T]> for LweBodyList { + fn as_mut(&mut self) -> &mut [T] { + self.data.as_mut() + } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data [T]> for LweBodyListView<'data, T> { + type Metadata = LweBodyListCreationMetadata; + + #[inline] + fn create_from(from: &[T], meta: Self::Metadata) -> LweBodyListView<'_, T> { + let LweBodyListCreationMetadata(ciphertext_modulus) = meta; + LweBodyList { + data: from, + ciphertext_modulus, + } + } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data mut [T]> for LweBodyListMutView<'data, T> { + type Metadata = LweBodyListCreationMetadata; + + #[inline] + fn create_from(from: &mut [T], meta: Self::Metadata) -> LweBodyListMutView<'_, T> { + let LweBodyListCreationMetadata(ciphertext_modulus) = meta; + LweBodyList { + data: from, + ciphertext_modulus, + } + } +} + +impl> LweBodyList { + pub fn from_container(container: C, ciphertext_modulus: CiphertextModulus) -> Self { + Self { + data: container, + ciphertext_modulus, + } + } + + pub fn lwe_body_count(&self) -> LweBodyCount { + LweBodyCount(self.data.container_len()) + } + + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.ciphertext_modulus + } +} + +/// Metadata used in the [`CreateFrom`] implementation to create [`LweBody`] entities. +#[derive(Clone, Copy)] +pub struct LweBodyCreationMetadata(pub CiphertextModulus); + +/// Metadata used in the [`CreateFrom`] implementation to create [`LweBodyList`] entities. +#[derive(Clone, Copy)] +pub struct LweBodyListCreationMetadata(pub CiphertextModulus); + +impl> ContiguousEntityContainer + for LweBodyList +{ + type Element = C::Element; + + type EntityViewMetadata = LweBodyCreationMetadata; + + type EntityView<'this> = LweBodyRef<'this, Self::Element> + where + Self: 'this; + + type SelfViewMetadata = LweBodyListCreationMetadata; + + type SelfView<'this> = LweBodyListView<'this,Self::Element> + where + Self: 'this; + + fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata { + LweBodyCreationMetadata(self.ciphertext_modulus()) + } + + fn get_entity_view_pod_size(&self) -> usize { + 1 + } + + fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata { + LweBodyListCreationMetadata(self.ciphertext_modulus()) + } +} + +impl> ContiguousEntityContainerMut + for LweBodyList +{ + type EntityMutView<'this> = LweBodyRefMut<'this, Self::Element> + where + Self: 'this; + + type SelfMutView<'this> = LweBodyListMutView<'this, Self::Element> + where + Self: 'this; +} + #[derive(Clone, Debug)] pub struct LweMask where @@ -87,69 +269,178 @@ impl> AsMut<[T]> for LweMask } } -impl LweBody { - pub fn new(data: Scalar, ciphertext_modulus: CiphertextModulus) -> Self { - Self { - data, +impl<'data, T: UnsignedInteger> CreateFrom<&'data [T]> for LweMask<&'data [T]> { + type Metadata = LweMaskCreationMetadata; + + #[inline] + fn create_from(from: &[T], meta: Self::Metadata) -> LweMask<&[T]> { + let LweMaskCreationMetadata(ciphertext_modulus) = meta; + LweMask { + data: from, ciphertext_modulus, } } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data mut [T]> for LweMask<&'data mut [T]> { + type Metadata = LweMaskCreationMetadata; + + #[inline] + fn create_from(from: &mut [T], meta: Self::Metadata) -> LweMask<&mut [T]> { + let LweMaskCreationMetadata(ciphertext_modulus) = meta; + LweMask { + data: from, + ciphertext_modulus, + } + } +} + +#[derive(Clone, Debug)] +pub struct LweMaskList +where + C::Element: UnsignedInteger, +{ + data: C, + lwe_dimension: LweDimension, + ciphertext_modulus: CiphertextModulus, +} + +pub type LweMaskListView<'data, Scalar> = LweMaskList<&'data [Scalar]>; +pub type LweMaskListMutView<'data, Scalar> = LweMaskList<&'data mut [Scalar]>; + +pub fn lwe_mask_list_size(lwe_dimension: LweDimension, lwe_mask_count: LweMaskCount) -> usize { + lwe_dimension.0 * lwe_mask_count.0 +} + +impl> AsRef<[T]> for LweMaskList { + fn as_ref(&self) -> &[T] { + self.data.as_ref() + } +} + +impl> AsMut<[T]> for LweMaskList { + fn as_mut(&mut self) -> &mut [T] { + self.data.as_mut() + } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data [T]> for LweMaskListView<'data, T> { + type Metadata = LweMaskListCreationMetadata; + + #[inline] + fn create_from(from: &[T], meta: Self::Metadata) -> LweMaskListView<'_, T> { + let LweMaskListCreationMetadata(lwe_dimension, ciphertext_modulus) = meta; + LweMaskList { + data: from, + lwe_dimension, + ciphertext_modulus, + } + } +} + +impl<'data, T: UnsignedInteger> CreateFrom<&'data mut [T]> for LweMaskListMutView<'data, T> { + type Metadata = LweMaskListCreationMetadata; + + #[inline] + fn create_from(from: &mut [T], meta: Self::Metadata) -> LweMaskListMutView<'_, T> { + let LweMaskListCreationMetadata(lwe_dimension, ciphertext_modulus) = meta; + LweMaskList { + data: from, + lwe_dimension, + ciphertext_modulus, + } + } +} + +impl> LweMaskList { + pub fn from_container( + container: C, + lwe_dimension: LweDimension, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + assert!( + container.container_len() % lwe_dimension.0 == 0, + "The provided container length is not valid. \ + It needs to be dividable by lwe_dimension. \ + Got container length: {} and lwe_dimension: {lwe_dimension:?}.", + container.container_len() + ); + + Self { + data: container, + lwe_dimension, + ciphertext_modulus, + } + } + + pub fn lwe_dimension(&self) -> LweDimension { + self.lwe_dimension + } + + pub fn lwe_mask_count(&self) -> LweMaskCount { + LweMaskCount(self.data.container_len() / self.lwe_dimension.0) + } + + pub fn lwe_mask_list_size(&self) -> usize { + lwe_mask_list_size(self.lwe_dimension(), self.lwe_mask_count()) + } pub fn ciphertext_modulus(&self) -> CiphertextModulus { self.ciphertext_modulus } } -impl<'outer, T: UnsignedInteger> LweBodyRef<'outer, T> { - pub fn new(data: &'outer T, ciphertext_modulus: CiphertextModulus) -> Self { - Self { - data, - ciphertext_modulus, - } +/// Metadata used in the [`CreateFrom`] implementation to create [`LweMask`] entities. +#[derive(Clone, Copy)] +pub struct LweMaskCreationMetadata(pub CiphertextModulus); + +/// Metadata used in the [`CreateFrom`] implementation to create [`LweMaskList`] entities. +#[derive(Clone, Copy)] +pub struct LweMaskListCreationMetadata( + pub LweDimension, + pub CiphertextModulus, +); + +impl> ContiguousEntityContainer + for LweMaskList +{ + type Element = C::Element; + + type EntityViewMetadata = LweMaskCreationMetadata; + + type EntityView<'this> = LweMask<&'this [ Self::Element]> + where + Self: 'this; + + type SelfViewMetadata = LweMaskListCreationMetadata; + + type SelfView<'this> = LweMaskListView<'this, Self::Element> + where + Self: 'this; + + fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata { + LweMaskCreationMetadata(self.ciphertext_modulus()) } - pub fn ciphertext_modulus(&self) -> CiphertextModulus { - self.ciphertext_modulus + fn get_entity_view_pod_size(&self) -> usize { + self.lwe_dimension().0 + } + + fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata { + LweMaskListCreationMetadata(self.lwe_dimension(), self.ciphertext_modulus()) } } -impl<'outer, T: UnsignedInteger> LweBodyRefMut<'outer, T> { - pub fn new(data: &'outer mut T, ciphertext_modulus: CiphertextModulus) -> Self { - Self { - data, - ciphertext_modulus, - } - } +impl> ContiguousEntityContainerMut + for LweMaskList +{ + type EntityMutView<'this> = LweMask<&'this mut [ Self::Element]> + where + Self: 'this; - pub fn ciphertext_modulus(&self) -> CiphertextModulus { - self.ciphertext_modulus - } -} - -impl<'data, T: UnsignedInteger> CreateFrom<&'data [T]> for LweBodyRef<'data, T> { - type Metadata = LweCiphertextCreationMetadata; - - #[inline] - fn create_from(from: &[T], meta: Self::Metadata) -> LweBodyRef { - let LweCiphertextCreationMetadata(ciphertext_modulus) = meta; - LweBodyRef { - data: &from[0], - ciphertext_modulus, - } - } -} - -impl<'data, T: UnsignedInteger> CreateFrom<&'data mut [T]> for LweBodyRefMut<'data, T> { - type Metadata = LweCiphertextCreationMetadata; - - #[inline] - fn create_from(from: &mut [T], meta: Self::Metadata) -> LweBodyRefMut { - let LweCiphertextCreationMetadata(ciphertext_modulus) = meta; - LweBodyRefMut { - data: &mut from[0], - ciphertext_modulus, - } - } + type SelfMutView<'this> = LweMaskListMutView<'this,Self::Element> + where + Self: 'this; } /// An [`LWE ciphertext`](`LweCiphertext`). diff --git a/tfhe/src/core_crypto/entities/lwe_compact_ciphertext_list.rs b/tfhe/src/core_crypto/entities/lwe_compact_ciphertext_list.rs new file mode 100644 index 000000000..199fac76a --- /dev/null +++ b/tfhe/src/core_crypto/entities/lwe_compact_ciphertext_list.rs @@ -0,0 +1,334 @@ +//! Module containing the definition of the [`LweCompactCiphertextList`] a space efficient +//! encryption of a list of LWE ciphertexts. + +use crate::core_crypto::algorithms::{ + expand_lwe_compact_ciphertext_list, par_expand_lwe_compact_ciphertext_list, +}; +use crate::core_crypto::commons::parameters::*; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// A [`compact list of LWE ciphertexts`](`LweCompactCiphertextList`) obtained through encryption +/// with a [`compact LWE public key`](`super::LweCompactPublicKey`). +/// +/// See section 4 of the public key construction described in by +/// M. Joye. +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct LweCompactCiphertextList +where + C::Element: UnsignedInteger, +{ + data: C, + lwe_size: LweSize, + lwe_ciphertext_count: LweCiphertextCount, + ciphertext_modulus: CiphertextModulus, +} + +impl> AsRef<[T]> for LweCompactCiphertextList { + fn as_ref(&self) -> &[T] { + self.data.as_ref() + } +} + +impl> AsMut<[T]> for LweCompactCiphertextList { + fn as_mut(&mut self) -> &mut [T] { + self.data.as_mut() + } +} + +pub fn lwe_compact_ciphertext_list_mask_count( + lwe_dimension: LweDimension, + lwe_ciphertext_count: LweCiphertextCount, +) -> LweMaskCount { + LweMaskCount( + lwe_ciphertext_count.0 / lwe_dimension.0 + + if lwe_ciphertext_count.0 % lwe_dimension.0 != 0 { + 1 + } else { + 0 + }, + ) +} + +pub fn lwe_compact_ciphertext_list_size( + lwe_dimension: LweDimension, + lwe_ciphertext_count: LweCiphertextCount, +) -> usize { + // we expect one mask per "ciphertext bin" plus the bodies, so mask_count * lwe_dimension + + // ciphertext_count + let mask_count = lwe_compact_ciphertext_list_mask_count(lwe_dimension, lwe_ciphertext_count); + mask_count.0 * lwe_dimension.0 + lwe_ciphertext_count.0 +} + +impl> LweCompactCiphertextList { + /// Create an [`LweCompactCiphertextList`] from an existing container. + /// + /// # Note + /// + /// This function only wraps a container in the appropriate type. If you want to encrypt data + /// you need to use + /// [`crate::core_crypto::algorithms::encrypt_lwe_compact_ciphertext_list_with_compact_public_key`] + /// or its parallel variant + /// [`crate::core_crypto::algorithms::par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key`] using this list as + /// output. + /// + /// This docstring exhibits [`LweCompactCiphertextList`] primitives usage. + /// + /// ``` + /// use tfhe::core_crypto::prelude::*; + /// + /// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct + /// // computations + /// // Define parameters for LweCompactCiphertextList creation + /// let lwe_size = LweSize(1025); + /// let lwe_ciphertext_count = LweCiphertextCount(5); + /// let ciphertext_modulus = CiphertextModulus::new_native(); + /// + /// // Create a new LweCiphertextList + /// let lwe_compact_list = + /// LweCompactCiphertextList::new(0u64, lwe_size, lwe_ciphertext_count, ciphertext_modulus); + /// + /// assert_eq!(lwe_compact_list.lwe_size(), lwe_size); + /// assert_eq!( + /// lwe_compact_list.lwe_ciphertext_count(), + /// lwe_ciphertext_count + /// ); + /// assert_eq!(lwe_compact_list.ciphertext_modulus(), ciphertext_modulus); + /// + /// // Demonstrate how to recover the allocated container + /// let underlying_container: Vec = lwe_compact_list.into_container(); + /// + /// // Recreate a list using from_container + /// let lwe_compact_list = LweCompactCiphertextList::from_container( + /// underlying_container, + /// lwe_size, + /// lwe_ciphertext_count, + /// ciphertext_modulus, + /// ); + /// + /// assert_eq!(lwe_compact_list.lwe_size(), lwe_size); + /// assert_eq!( + /// lwe_compact_list.lwe_ciphertext_count(), + /// lwe_ciphertext_count + /// ); + /// assert_eq!(lwe_compact_list.ciphertext_modulus(), ciphertext_modulus); + /// + /// let lwe_list = lwe_compact_list.expand_into_lwe_ciphertext_list(); + /// assert_eq!(lwe_list.lwe_size(), lwe_size); + /// assert_eq!(lwe_list.lwe_ciphertext_count(), lwe_ciphertext_count); + /// assert_eq!(lwe_list.ciphertext_modulus(), ciphertext_modulus); + /// ``` + pub fn from_container( + container: C, + lwe_size: LweSize, + lwe_ciphertext_count: LweCiphertextCount, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + let expected_container_len = + lwe_compact_ciphertext_list_size(lwe_size.to_lwe_dimension(), lwe_ciphertext_count); + assert!( + container.container_len() == expected_container_len, + "Expected container for be of length {}, got length {}", + expected_container_len, + container.container_len() + ); + + Self { + data: container, + lwe_size, + lwe_ciphertext_count, + ciphertext_modulus, + } + } + + /// Consume the entity and return its underlying container. + /// + /// See [`LweCompactCiphertextList::from_container`] for usage. + pub fn into_container(self) -> C { + self.data + } + + /// Return the [`LweSize`] of the [`LweCompactCiphertextList`]. + /// + /// See [`LweCompactCiphertextList::from_container`] for usage. + pub fn lwe_size(&self) -> LweSize { + self.lwe_size + } + + /// Return the [`LweCiphertextCount`] of the [`LweCompactCiphertextList`]. + /// + /// See [`LweCompactCiphertextList::from_container`] for usage. + pub fn lwe_ciphertext_count(&self) -> LweCiphertextCount { + self.lwe_ciphertext_count + } + + /// Return the [`CiphertextModulus`] of the [`LweCompactCiphertextList`]. + /// + /// See [`LweCompactCiphertextList::from_container`] for usage. + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.ciphertext_modulus + } + + /// Return the [`LweMaskCount`] of the [`LweCompactCiphertextList`]. + /// + /// See [`LweCompactCiphertextList::from_container`] for usage. + pub fn lwe_mask_count(&self) -> LweMaskCount { + lwe_compact_ciphertext_list_mask_count( + self.lwe_size().to_lwe_dimension(), + self.lwe_ciphertext_count(), + ) + } + + /// Return an immutable view to the [`LweMaskList`] of a [`LweCompactCiphertextList`]. + pub fn get_mask_list(&self) -> LweMaskListView<'_, Scalar> { + let lwe_mask_list_size = + lwe_mask_list_size(self.lwe_size().to_lwe_dimension(), self.lwe_mask_count()); + LweMaskList::from_container( + &self.data.as_ref()[..lwe_mask_list_size], + self.lwe_size().to_lwe_dimension(), + self.ciphertext_modulus(), + ) + } + + /// Return an immutable view to the [`LweBodyList`] of a [`LweCompactCiphertextList`]. + pub fn get_body_list(&self) -> LweBodyListView<'_, Scalar> { + let lwe_mask_list_size = + lwe_mask_list_size(self.lwe_size().to_lwe_dimension(), self.lwe_mask_count()); + LweBodyList::from_container( + &self.data.as_ref()[lwe_mask_list_size..], + self.ciphertext_modulus(), + ) + } + + /// Return immutable views to the [`LweMaskList`] and [`LweBodyList`] of a + /// [`LweCompactCiphertextList`]. + pub fn get_mask_and_body_list( + &self, + ) -> (LweMaskListView<'_, Scalar>, LweBodyListView<'_, Scalar>) { + (self.get_mask_list(), self.get_body_list()) + } + + /// Consume the [`LweCompactCiphertextList`] and expand it into a standard + /// [`LweCiphertextList`]. + /// + /// See [`LweCompactCiphertextList::from_container`] for usage. + pub fn expand_into_lwe_ciphertext_list(self) -> LweCiphertextListOwned { + let mut lwe_ciphertext_list = LweCiphertextListOwned::new( + Scalar::ZERO, + self.lwe_size(), + self.lwe_ciphertext_count(), + self.ciphertext_modulus(), + ); + + expand_lwe_compact_ciphertext_list(&mut lwe_ciphertext_list, &self); + lwe_ciphertext_list + } + + /// Parallel variant of [`Self::expand_into_lwe_ciphertext_list`] + pub fn par_expand_into_lwe_ciphertext_list(self) -> LweCiphertextListOwned + where + Scalar: UnsignedInteger + Send + Sync, + { + let mut lwe_ciphertext_list = LweCiphertextListOwned::new( + Scalar::ZERO, + self.lwe_size(), + self.lwe_ciphertext_count(), + self.ciphertext_modulus(), + ); + + par_expand_lwe_compact_ciphertext_list(&mut lwe_ciphertext_list, &self); + lwe_ciphertext_list + } + + pub fn size_elements(&self) -> usize { + self.data.container_len() + } + + pub fn size_bytes(&self) -> usize { + std::mem::size_of_val(self.data.as_ref()) + } +} + +impl> LweCompactCiphertextList { + /// Return a mutable view to the [`LweMaskList`] of a [`LweCompactCiphertextList`]. + pub fn get_mut_mask_list(&mut self) -> LweMaskListMutView<'_, Scalar> { + let lwe_dimension = self.lwe_size().to_lwe_dimension(); + let lwe_mask_count = self.lwe_mask_count(); + let ciphertext_modulus = self.ciphertext_modulus(); + let lwe_mask_list_size = lwe_mask_list_size(lwe_dimension, lwe_mask_count); + LweMaskList::from_container( + &mut self.data.as_mut()[..lwe_mask_list_size], + lwe_dimension, + ciphertext_modulus, + ) + } + + /// Return a mutable view to the [`LweBodyList`] of a [`LweCompactCiphertextList`]. + pub fn get_mut_body_list(&mut self) -> LweBodyListMutView<'_, Scalar> { + let lwe_dimension = self.lwe_size().to_lwe_dimension(); + let lwe_mask_count = self.lwe_mask_count(); + let ciphertext_modulus = self.ciphertext_modulus(); + let lwe_mask_list_size = lwe_mask_list_size(lwe_dimension, lwe_mask_count); + LweBodyList::from_container( + &mut self.data.as_mut()[lwe_mask_list_size..], + ciphertext_modulus, + ) + } + + /// Return mutable views to the [`LweMaskList`] and [`LweBodyList`] of a + /// [`LweCompactCiphertextList`]. + pub fn get_mut_mask_and_body_list( + &mut self, + ) -> ( + LweMaskListMutView<'_, Scalar>, + LweBodyListMutView<'_, Scalar>, + ) { + let lwe_dimension = self.lwe_size().to_lwe_dimension(); + let lwe_mask_count = self.lwe_mask_count(); + let ciphertext_modulus = self.ciphertext_modulus(); + let lwe_mask_list_size = lwe_mask_list_size(lwe_dimension, lwe_mask_count); + + let (mask_slice, body_slice) = self.as_mut().split_at_mut(lwe_mask_list_size); + + ( + LweMaskList::from_container(mask_slice, lwe_dimension, ciphertext_modulus), + LweBodyList::from_container(body_slice, ciphertext_modulus), + ) + } +} + +pub type LweCompactCiphertextListOwned = LweCompactCiphertextList>; +pub type LweCompactCiphertextListView<'data, Scalar> = LweCompactCiphertextList<&'data [Scalar]>; +pub type LweCompactCiphertextListMutView<'data, Scalar> = + LweCompactCiphertextList<&'data mut [Scalar]>; + +impl LweCompactCiphertextListOwned { + /// Allocate memory and create a new owned [`LweCompactCiphertextListOwned`]. + /// + /// # Note + /// + /// This function allocates a vector of the appropriate size and wraps it in the appropriate + /// type. If you want to encrypt data you need to use + /// [`crate::core_crypto::algorithms::encrypt_lwe_compact_ciphertext_list_with_compact_public_key`] + /// or its parallel variant + /// [`crate::core_crypto::algorithms::par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key`] + /// using this list as output. + /// + /// See [`LweCompactCiphertextListOwned::from_container`] for usage. + pub fn new( + fill_with: Scalar, + lwe_size: LweSize, + lwe_ciphertext_count: LweCiphertextCount, + ciphertext_modulus: CiphertextModulus, + ) -> Self { + Self::from_container( + vec![ + fill_with; + lwe_compact_ciphertext_list_size(lwe_size.to_lwe_dimension(), lwe_ciphertext_count) + ], + lwe_size, + lwe_ciphertext_count, + ciphertext_modulus, + ) + } +} diff --git a/tfhe/src/core_crypto/entities/lwe_compact_public_key.rs b/tfhe/src/core_crypto/entities/lwe_compact_public_key.rs new file mode 100644 index 000000000..54bc60c01 --- /dev/null +++ b/tfhe/src/core_crypto/entities/lwe_compact_public_key.rs @@ -0,0 +1,187 @@ +//! Module containing the definition of the [`LweCompactPublicKey`]. + +use crate::core_crypto::commons::parameters::*; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// A [`compact public LWE encryption key`](`LweCompactPublicKey`). +/// +/// Implementation of the public key construction described in by +/// M. Joye. +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct LweCompactPublicKey +where + C::Element: UnsignedInteger, +{ + glwe_ciphertext: GlweCiphertext, +} + +impl> AsRef<[T]> for LweCompactPublicKey { + fn as_ref(&self) -> &[T] { + self.glwe_ciphertext.as_ref() + } +} + +impl> AsMut<[T]> for LweCompactPublicKey { + fn as_mut(&mut self) -> &mut [T] { + self.glwe_ciphertext.as_mut() + } +} + +impl> LweCompactPublicKey { + /// Create an [`LweCompactPublicKey`] from an existing container. + /// + /// # Note + /// + /// This function only wraps a container in the appropriate type. If you want to generate an + /// [`LweCompactPublicKey`] you need to call + /// [`crate::core_crypto::algorithms::generate_lwe_compact_public_key`] using this key as + /// output. + /// + /// This docstring exhibits [`LweCompactPublicKey`] primitives usage. + /// + /// ``` + /// use tfhe::core_crypto::prelude::*; + /// + /// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct + /// // computations + /// // Define parameters for LweCompactPublicKey creation + /// let lwe_dimension = LweDimension(1024); + /// let ciphertext_modulus = CiphertextModulus::new_native(); + /// + /// // Create a new LweCompactPublicKey + /// let lwe_compact_public_key = LweCompactPublicKey::new(0u64, lwe_dimension, ciphertext_modulus); + /// + /// assert_eq!(lwe_compact_public_key.lwe_dimension(), lwe_dimension); + /// assert_eq!( + /// lwe_compact_public_key.ciphertext_modulus(), + /// ciphertext_modulus + /// ); + /// + /// // Demonstrate how to recover the allocated container + /// let underlying_container: Vec = lwe_compact_public_key.into_container(); + /// + /// // Recreate a public key using from_container + /// let lwe_compact_public_key = + /// LweCompactPublicKey::from_container(underlying_container, ciphertext_modulus); + /// + /// assert_eq!(lwe_compact_public_key.lwe_dimension(), lwe_dimension); + /// assert_eq!( + /// lwe_compact_public_key.ciphertext_modulus(), + /// ciphertext_modulus + /// ); + /// ``` + pub fn from_container( + container: C, + ciphertext_modulus: CiphertextModulus, + ) -> LweCompactPublicKey { + assert!( + container.container_len().is_power_of_two(), + "LweCompactPublicKey container len must be a power of 2, got len = {}", + container.container_len() + ); + let equivalent_polynomial_size = PolynomialSize(container.container_len() / 2); + LweCompactPublicKey { + glwe_ciphertext: GlweCiphertext::from_container( + container, + equivalent_polynomial_size, + ciphertext_modulus, + ), + } + } + + /// Consume the entity and return its underlying container. + /// + /// See [`LweCompactPublicKey::from_container`] for usage. + pub fn into_container(self) -> C { + self.glwe_ciphertext.into_container() + } + + /// Return the [`LweDimension`] of the [`LweCompactPublicKey`]. + /// + /// See [`LweCompactPublicKey::from_container`] for usage. + pub fn lwe_dimension(&self) -> LweDimension { + LweDimension(self.glwe_ciphertext.polynomial_size().0) + } + + /// Return the [`CiphertextModulus`] of the [`LweCompactPublicKey`]. + /// + /// See [`LweCompactPublicKey::from_container`] for usage. + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.glwe_ciphertext.ciphertext_modulus() + } + + /// Return an immutable view to the [`GlweMask`] of the underlying [`GlweCiphertext`] of the + /// [`LweCompactPublicKey`]. + pub fn get_mask(&self) -> GlweMask<&[Scalar]> { + self.glwe_ciphertext.get_mask() + } + + /// Return an immutable view to the [`GlweBody`] of the underlying [`GlweCiphertext`] of the + /// [`LweCompactPublicKey`]. + pub fn get_body(&self) -> GlweBody<&[Scalar]> { + self.glwe_ciphertext.get_body() + } + + /// Return immutable views to the [`GlweMask`] and [`GlweBody`] of the underlying + /// [`GlweCiphertext`] of the [`LweCompactPublicKey`]. + pub fn get_mask_and_body(&self) -> (GlweMask<&[Scalar]>, GlweBody<&[Scalar]>) { + self.glwe_ciphertext.get_mask_and_body() + } + + pub fn as_glwe_ciphertext(&self) -> GlweCiphertextView<'_, Scalar> { + self.glwe_ciphertext.as_view() + } + + pub fn size_elements(&self) -> usize { + self.glwe_ciphertext.as_ref().len() + } + + pub fn size_bytes(&self) -> usize { + std::mem::size_of_val(self.glwe_ciphertext.as_ref()) + } +} + +impl> LweCompactPublicKey { + /// Return a mutable view to the [`GlweMask`] of the underlying [`GlweCiphertext`] of the + /// [`LweCompactPublicKey`]. + pub fn get_mut_mask(&mut self) -> GlweMask<&mut [Scalar]> { + self.glwe_ciphertext.get_mut_mask() + } + + /// Return a mutable view to the [`GlweBody`] of the underlying [`GlweCiphertext`] of the + /// [`LweCompactPublicKey`]. + pub fn get_mut_body(&mut self) -> GlweBody<&mut [Scalar]> { + self.glwe_ciphertext.get_mut_body() + } + + /// Return mutable views to the [`GlweMask`] and [`GlweBody`] of the underlying + /// [`GlweCiphertext`] of the [`LweCompactPublicKey`]. + pub fn get_mut_mask_and_body(&mut self) -> (GlweMask<&mut [Scalar]>, GlweBody<&mut [Scalar]>) { + self.glwe_ciphertext.get_mut_mask_and_body() + } + + pub fn as_mut_glwe_ciphertext(&mut self) -> GlweCiphertextMutView<'_, Scalar> { + self.glwe_ciphertext.as_mut_view() + } +} + +pub type LweCompactPublicKeyOwned = LweCompactPublicKey>; + +impl LweCompactPublicKeyOwned { + pub fn new( + fill_with: Scalar, + lwe_dimension: LweDimension, + ciphertext_modulus: CiphertextModulus, + ) -> LweCompactPublicKeyOwned { + assert!( + lwe_dimension.0.is_power_of_two(), + "LweCompactPublicKey only supports power of 2 LweDimension. Got lwe_dimension = {}.", + lwe_dimension.0 + ); + LweCompactPublicKeyOwned::from_container( + vec![fill_with; 2 * lwe_dimension.0], + ciphertext_modulus, + ) + } +} diff --git a/tfhe/src/core_crypto/entities/lwe_public_key.rs b/tfhe/src/core_crypto/entities/lwe_public_key.rs index faf7091c8..837eebb0b 100644 --- a/tfhe/src/core_crypto/entities/lwe_public_key.rs +++ b/tfhe/src/core_crypto/entities/lwe_public_key.rs @@ -7,7 +7,7 @@ use crate::core_crypto::entities::*; // An LwePublicKey is literally an LweCiphertextList, so we wrap an LweCiphertextList and use // Deref to have access to all the primitives of the LweCiphertextList easily -/// A [`public LWE bootstrap key`](`LwePublicKey`). +/// A [`public LWE encryption key`](`LwePublicKey`). /// /// This is a wrapper type of [`LweCiphertextList`], [`std::ops::Deref`] and [`std::ops::DerefMut`] /// are implemented to dereference to the underlying [`LweCiphertextList`] for ease of use. See diff --git a/tfhe/src/core_crypto/entities/mod.rs b/tfhe/src/core_crypto/entities/mod.rs index 76f8a0e3f..5be803b14 100644 --- a/tfhe/src/core_crypto/entities/mod.rs +++ b/tfhe/src/core_crypto/entities/mod.rs @@ -13,6 +13,8 @@ pub mod gsw_ciphertext; pub mod lwe_bootstrap_key; pub mod lwe_ciphertext; pub mod lwe_ciphertext_list; +pub mod lwe_compact_ciphertext_list; +pub mod lwe_compact_public_key; pub mod lwe_keyswitch_key; pub mod lwe_multi_bit_bootstrap_key; pub mod lwe_private_functional_packing_keyswitch_key; @@ -30,6 +32,7 @@ pub mod seeded_glwe_ciphertext_list; pub mod seeded_lwe_bootstrap_key; pub mod seeded_lwe_ciphertext; pub mod seeded_lwe_ciphertext_list; +pub mod seeded_lwe_compact_public_key; pub mod seeded_lwe_keyswitch_key; pub mod seeded_lwe_multi_bit_bootstrap_key; pub mod seeded_lwe_public_key; @@ -57,6 +60,8 @@ pub use gsw_ciphertext::*; pub use lwe_bootstrap_key::*; pub use lwe_ciphertext::*; pub use lwe_ciphertext_list::*; +pub use lwe_compact_ciphertext_list::*; +pub use lwe_compact_public_key::*; pub use lwe_keyswitch_key::*; pub use lwe_multi_bit_bootstrap_key::*; pub use lwe_private_functional_packing_keyswitch_key::*; @@ -74,6 +79,7 @@ pub use seeded_glwe_ciphertext_list::*; pub use seeded_lwe_bootstrap_key::*; pub use seeded_lwe_ciphertext::*; pub use seeded_lwe_ciphertext_list::*; +pub use seeded_lwe_compact_public_key::*; pub use seeded_lwe_keyswitch_key::*; pub use seeded_lwe_multi_bit_bootstrap_key::*; pub use seeded_lwe_public_key::*; diff --git a/tfhe/src/core_crypto/entities/seeded_glwe_ciphertext.rs b/tfhe/src/core_crypto/entities/seeded_glwe_ciphertext.rs index 900f48f63..e75b905b4 100644 --- a/tfhe/src/core_crypto/entities/seeded_glwe_ciphertext.rs +++ b/tfhe/src/core_crypto/entities/seeded_glwe_ciphertext.rs @@ -7,7 +7,7 @@ use crate::core_crypto::commons::traits::*; use crate::core_crypto::entities::*; /// A [`seeded GLWE ciphertext`](`SeededGlweCiphertext`). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct SeededGlweCiphertext where C::Element: UnsignedInteger, diff --git a/tfhe/src/core_crypto/entities/seeded_lwe_ciphertext_list.rs b/tfhe/src/core_crypto/entities/seeded_lwe_ciphertext_list.rs index ee62e5d81..d0eaab0e9 100644 --- a/tfhe/src/core_crypto/entities/seeded_lwe_ciphertext_list.rs +++ b/tfhe/src/core_crypto/entities/seeded_lwe_ciphertext_list.rs @@ -250,7 +250,7 @@ impl> ContiguousEntityCo { type Element = C::Element; - type EntityViewMetadata = LweCiphertextCreationMetadata; + type EntityViewMetadata = LweBodyCreationMetadata; type EntityView<'this> = LweBodyRef<'this, Self::Element> where @@ -263,7 +263,7 @@ impl> ContiguousEntityCo Self: 'this; fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata { - LweCiphertextCreationMetadata(self.ciphertext_modulus()) + LweBodyCreationMetadata(self.ciphertext_modulus()) } fn get_entity_view_pod_size(&self) -> usize { diff --git a/tfhe/src/core_crypto/entities/seeded_lwe_compact_public_key.rs b/tfhe/src/core_crypto/entities/seeded_lwe_compact_public_key.rs new file mode 100644 index 000000000..b776d62d0 --- /dev/null +++ b/tfhe/src/core_crypto/entities/seeded_lwe_compact_public_key.rs @@ -0,0 +1,214 @@ +//! Module containing the definition of the [`SeededLweCompactPublicKey`]. + +use crate::core_crypto::algorithms::decompress_seeded_lwe_compact_public_key; +use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, CompressionSeed}; +use crate::core_crypto::commons::parameters::*; +use crate::core_crypto::commons::traits::*; +use crate::core_crypto::entities::*; + +/// A [`seeded compact public LWE encryption key`](`SeededLweCompactPublicKey`). +/// +/// Implementation of the public key construction described in by +/// M. Joye. +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SeededLweCompactPublicKey +where + C::Element: UnsignedInteger, +{ + seeded_glwe_ciphertext: SeededGlweCiphertext, +} + +impl> AsRef<[T]> for SeededLweCompactPublicKey { + fn as_ref(&self) -> &[T] { + self.seeded_glwe_ciphertext.as_ref() + } +} + +impl> AsMut<[T]> for SeededLweCompactPublicKey { + fn as_mut(&mut self) -> &mut [T] { + self.seeded_glwe_ciphertext.as_mut() + } +} + +impl> SeededLweCompactPublicKey { + /// Create an [`SeededLweCompactPublicKey`] from an existing container. + /// + /// # Note + /// + /// This function only wraps a container in the appropriate type. If you want to generate an + /// [`SeededLweCompactPublicKey`] you need to call + /// [`crate::core_crypto::algorithms::generate_seeded_lwe_compact_public_key`] using this key as + /// output. + /// + /// This docstring exhibits [`SeededLweCompactPublicKey`] primitives usage. + /// + /// ``` + /// use tfhe::core_crypto::prelude::*; + /// + /// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct + /// // computations + /// // Define parameters for SeededLweCompactPublicKey creation + /// let lwe_dimension = LweDimension(1024); + /// let ciphertext_modulus = CiphertextModulus::new_native(); + /// + /// // Get a seeder + /// let mut seeder = new_seeder(); + /// let seeder = seeder.as_mut(); + /// + /// // Create a new SeededLweCompactPublicKey + /// let seeded_lwe_compact_public_key = SeededLweCompactPublicKey::new( + /// 0u64, + /// lwe_dimension, + /// seeder.seed().into(), + /// ciphertext_modulus, + /// ); + /// + /// assert_eq!(seeded_lwe_compact_public_key.lwe_dimension(), lwe_dimension); + /// assert_eq!( + /// seeded_lwe_compact_public_key.ciphertext_modulus(), + /// ciphertext_modulus + /// ); + /// + /// let compression_seed = seeded_lwe_compact_public_key.compression_seed(); + /// + /// // Demonstrate how to recover the allocated container + /// let underlying_container: Vec = seeded_lwe_compact_public_key.into_container(); + /// + /// // Recreate a public key using from_container + /// let seeded_lwe_compact_public_key = SeededLweCompactPublicKey::from_container( + /// underlying_container, + /// compression_seed, + /// ciphertext_modulus, + /// ); + /// + /// assert_eq!(seeded_lwe_compact_public_key.lwe_dimension(), lwe_dimension); + /// assert_eq!( + /// seeded_lwe_compact_public_key.ciphertext_modulus(), + /// ciphertext_modulus + /// ); + /// + /// let lwe_compact_public_key = + /// seeded_lwe_compact_public_key.decompress_into_lwe_compact_public_key(); + /// + /// assert_eq!(lwe_compact_public_key.lwe_dimension(), lwe_dimension); + /// assert_eq!( + /// lwe_compact_public_key.ciphertext_modulus(), + /// ciphertext_modulus + /// ); + /// ``` + pub fn from_container( + container: C, + compression_seed: CompressionSeed, + ciphertext_modulus: CiphertextModulus, + ) -> SeededLweCompactPublicKey { + assert!( + container.container_len().is_power_of_two(), + "SeededLweCompactPublicKey container len must be a power of 2, got len = {}", + container.container_len() + ); + SeededLweCompactPublicKey { + seeded_glwe_ciphertext: SeededGlweCiphertext::from_container( + container, + GlweSize(1), + compression_seed, + ciphertext_modulus, + ), + } + } + + /// Consume the entity and return its underlying container. + /// + /// See [`SeededLweCompactPublicKey::from_container`] for usage. + pub fn into_container(self) -> C { + self.seeded_glwe_ciphertext.into_container() + } + + /// Return the [`LweDimension`] of the [`SeededLweCompactPublicKey`]. + /// + /// See [`SeededLweCompactPublicKey::from_container`] for usage. + pub fn lwe_dimension(&self) -> LweDimension { + LweDimension(self.seeded_glwe_ciphertext.polynomial_size().0) + } + + /// Return the [`CompressionSeed`] of the [`SeededLweCompactPublicKey`]. + /// + /// See [`SeededLweCompactPublicKey::from_container`] for usage. + pub fn compression_seed(&self) -> CompressionSeed { + self.seeded_glwe_ciphertext.compression_seed() + } + + /// Return the [`CiphertextModulus`] of the [`SeededLweCompactPublicKey`]. + /// + /// See [`SeededLweCompactPublicKey::from_container`] for usage. + pub fn ciphertext_modulus(&self) -> CiphertextModulus { + self.seeded_glwe_ciphertext.ciphertext_modulus() + } + + /// Return an immutable view to the [`GlweBody`] of the underlying [`GlweCiphertext`] of the + /// [`SeededLweCompactPublicKey`]. + pub fn get_body(&self) -> GlweBody<&[Scalar]> { + self.seeded_glwe_ciphertext.get_body() + } + + /// Return an immutable view to the the underlying [`SeededGlweCiphertext`] of the + /// [`SeededLweCompactPublicKey`]. + pub fn as_seeded_glwe_ciphertext(&self) -> SeededGlweCiphertextView<'_, Scalar> { + self.seeded_glwe_ciphertext.as_view() + } + + /// Consume the [`SeededLweCompactPublicKey`] and decompress it into a standard + /// [`LweCompactPublicKey`]. + /// + /// See [`SeededLweCompactPublicKey::from_container`] for usage. + pub fn decompress_into_lwe_compact_public_key(self) -> LweCompactPublicKeyOwned + where + Scalar: UnsignedTorus, + { + let mut decompressed_cpk = LweCompactPublicKey::new( + Scalar::ZERO, + self.lwe_dimension(), + self.ciphertext_modulus(), + ); + decompress_seeded_lwe_compact_public_key::<_, _, _, ActivatedRandomGenerator>( + &mut decompressed_cpk, + &self, + ); + decompressed_cpk + } +} + +impl> SeededLweCompactPublicKey { + /// Return a mutable view to the [`GlweBody`] of the underlying [`SeededGlweCiphertext`] of the + /// [`SeededLweCompactPublicKey`]. + pub fn get_mut_body(&mut self) -> GlweBody<&mut [Scalar]> { + self.seeded_glwe_ciphertext.get_mut_body() + } + + /// Return a mutable view to the the underlying [`SeededGlweCiphertext`] of the + /// [`SeededLweCompactPublicKey`]. + pub fn as_mut_seeded_glwe_ciphertext(&mut self) -> SeededGlweCiphertextMutView<'_, Scalar> { + self.seeded_glwe_ciphertext.as_mut_view() + } +} + +pub type SeededLweCompactPublicKeyOwned = SeededLweCompactPublicKey>; + +impl SeededLweCompactPublicKeyOwned { + pub fn new( + fill_with: Scalar, + lwe_dimension: LweDimension, + compression_seed: CompressionSeed, + ciphertext_modulus: CiphertextModulus, + ) -> SeededLweCompactPublicKeyOwned { + assert!( + lwe_dimension.0.is_power_of_two(), + "SeededLweCompactPublicKey only supports power of 2 LweDimension. Got lwe_dimension = {}.", + lwe_dimension.0 + ); + SeededLweCompactPublicKeyOwned::from_container( + vec![fill_with; lwe_dimension.0], + compression_seed, + ciphertext_modulus, + ) + } +} diff --git a/tfhe/src/high_level_api/booleans/client_key.rs b/tfhe/src/high_level_api/booleans/client_key.rs index b21813edf..0b3846122 100644 --- a/tfhe/src/high_level_api/booleans/client_key.rs +++ b/tfhe/src/high_level_api/booleans/client_key.rs @@ -1,5 +1,10 @@ -use crate::boolean::client_key::ClientKey; +use std::marker::PhantomData; +use crate::boolean::client_key::ClientKey; +use crate::core_crypto::commons::generators::DeterministicSeeder; +use crate::core_crypto::prelude::ActivatedRandomGenerator; + +use concrete_csprng::seeders::Seed; use serde::{Deserialize, Serialize}; use super::parameters::BooleanParameterSet; @@ -16,6 +21,19 @@ where _marker: std::marker::PhantomData

, } +impl GenericBoolClientKey { + pub(crate) fn with_seed(parameters: FheBoolParameters, seed: Seed) -> Self { + let mut seeder = DeterministicSeeder::::new(seed); + let key = crate::boolean::engine::BooleanEngine::new_from_seeder(&mut seeder) + .create_client_key(parameters.into()); + + Self { + key, + _marker: PhantomData, + } + } +} + impl From for GenericBoolClientKey { fn from(parameters: FheBoolParameters) -> Self { Self { diff --git a/tfhe/src/high_level_api/config.rs b/tfhe/src/high_level_api/config.rs index 7c732f116..400a92e96 100644 --- a/tfhe/src/high_level_api/config.rs +++ b/tfhe/src/high_level_api/config.rs @@ -127,7 +127,7 @@ impl ConfigBuilder { } #[cfg(feature = "integer")] - pub fn enable_default_custom_integers( + pub fn enable_custom_integers( mut self, block_parameters: crate::shortint::ClassicPBSParameters, wopbs_block_parameters: Option, diff --git a/tfhe/src/high_level_api/details.rs b/tfhe/src/high_level_api/details.rs index 10a13812e..16bc14e54 100644 --- a/tfhe/src/high_level_api/details.rs +++ b/tfhe/src/high_level_api/details.rs @@ -60,6 +60,24 @@ macro_rules! define_key_structs { )* } + impl [<$base_struct_name ClientKey>] { + pub(crate) fn with_seed( + config: [<$base_struct_name Config>], + seed: ::concrete_csprng::seeders::Seed + ) -> Self { + Self { + $( + [<$name _params>]: None, + [<$name _key>]: config + .[<$name _params>] + .map(|params| { + <[<$base_ty_name ClientKey>]>::with_seed(params, seed) + }), + )* + } + } + } + impl From<[<$base_struct_name Config>]> for [<$base_struct_name ClientKey>] { fn from(config: [<$base_struct_name Config>]) -> Self { Self { diff --git a/tfhe/src/high_level_api/integers/keys.rs b/tfhe/src/high_level_api/integers/keys.rs index 22eb9b547..6b02b16c0 100644 --- a/tfhe/src/high_level_api/integers/keys.rs +++ b/tfhe/src/high_level_api/integers/keys.rs @@ -1,5 +1,14 @@ +use concrete_csprng::seeders::Seed; +use serde::{Deserialize, Serialize}; + +use crate::core_crypto::commons::generators::DeterministicSeeder; +use crate::core_crypto::prelude::ActivatedRandomGenerator; +use crate::integer::U256; use crate::shortint::EncryptionKeyChoice; +use super::server_key::RadixCiphertextDyn; +use super::types::compact::CompactCiphertextListDyn; + #[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] pub(crate) struct IntegerConfig { pub(crate) block_parameters: Option, @@ -61,6 +70,26 @@ pub(crate) struct IntegerClientKey { } impl IntegerClientKey { + pub(crate) fn with_seed(config: IntegerConfig, seed: Seed) -> Self { + let (key, encryption_type) = match config.block_parameters { + Some(params) => { + let encryption_type = params.encryption_key_choice; + let mut seeder = DeterministicSeeder::::new(seed); + let cks = crate::shortint::engine::ShortintEngine::new_from_seeder(&mut seeder) + .new_client_key(params.into()) + .unwrap(); + let cks = crate::integer::ClientKey::from(cks); + (Some(cks), encryption_type) + } + None => (None, EncryptionKeyChoice::Big), + }; + Self { + key, + wopbs_block_parameters: config.wopbs_block_parameters, + encryption_type, + } + } + pub(crate) fn encryption_type(&self) -> EncryptionKeyChoice { self.encryption_type } @@ -171,3 +200,133 @@ impl IntegerCompressedServerKey { } } } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(in crate::high_level_api) enum CompactPublicKeyDyn { + Big(crate::integer::CompactPublicKeyBig), + Small(crate::integer::CompactPublicKeySmall), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(in crate::high_level_api) struct IntegerCompactPublicKey { + pub(in crate::high_level_api) key: Option, +} + +impl IntegerCompactPublicKey { + pub(in crate::high_level_api) fn new(client_key: &IntegerClientKey) -> Self { + Self::try_new(client_key).expect("Incompatible parameters") + } + + pub(in crate::high_level_api) fn try_new(client_key: &IntegerClientKey) -> Option { + let Some(cks) = client_key.key.as_ref() else { + return Some(Self { + key: None, + }); + }; + + let key = match client_key.encryption_type { + crate::shortint::EncryptionKeyChoice::Big => { + CompactPublicKeyDyn::Big(crate::integer::CompactPublicKeyBig::try_new(cks)?) + } + crate::shortint::EncryptionKeyChoice::Small => { + CompactPublicKeyDyn::Small(crate::integer::CompactPublicKeySmall::try_new(cks)?) + } + }; + + Some(Self { key: Some(key) }) + } + + pub(in crate::high_level_api) fn try_encrypt( + &self, + value: T, + num_blocks: usize, + ) -> Option + where + T: Into, + { + let Some(key) = self.key.as_ref() else { + return None; + }; + let value = value.into(); + let ct = match key { + CompactPublicKeyDyn::Big(pk) => { + RadixCiphertextDyn::Big(pk.encrypt_radix(value, num_blocks)) + } + CompactPublicKeyDyn::Small(pk) => { + RadixCiphertextDyn::Small(pk.encrypt_radix(value, num_blocks)) + } + }; + Some(ct) + } + + pub(in crate::high_level_api::integers) fn try_encrypt_compact( + &self, + values: &[T], + num_blocks: usize, + ) -> Option + where + T: crate::integer::block_decomposition::DecomposableInto, + { + let Some(key) = self.key.as_ref() else { + return None; + }; + let ct = match key { + CompactPublicKeyDyn::Big(pk) => { + CompactCiphertextListDyn::Big(pk.encrypt_slice_radix_compact(values, num_blocks)) + } + CompactPublicKeyDyn::Small(pk) => { + CompactCiphertextListDyn::Small(pk.encrypt_slice_radix_compact(values, num_blocks)) + } + }; + Some(ct) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(in crate::high_level_api) enum CompressedCompactPublicKeyDyn { + Big(crate::integer::CompressedCompactPublicKeyBig), + Small(crate::integer::CompressedCompactPublicKeySmall), +} + +impl CompressedCompactPublicKeyDyn { + fn decompress(self) -> CompactPublicKeyDyn { + match self { + CompressedCompactPublicKeyDyn::Big(big) => CompactPublicKeyDyn::Big(big.decompress()), + CompressedCompactPublicKeyDyn::Small(small) => { + CompactPublicKeyDyn::Small(small.decompress()) + } + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(in crate::high_level_api) struct IntegerCompressedCompactPublicKey { + pub(in crate::high_level_api) key: Option, +} + +impl IntegerCompressedCompactPublicKey { + pub(in crate::high_level_api) fn new(client_key: &IntegerClientKey) -> Self { + let Some(cks) = client_key.key.as_ref() else { + return Self { + key: None, + }; + }; + + let key = match client_key.encryption_type { + crate::shortint::EncryptionKeyChoice::Big => CompressedCompactPublicKeyDyn::Big( + crate::integer::CompressedCompactPublicKeyBig::new(cks), + ), + crate::shortint::EncryptionKeyChoice::Small => CompressedCompactPublicKeyDyn::Small( + crate::integer::CompressedCompactPublicKeySmall::new(cks), + ), + }; + + Self { key: Some(key) } + } + + pub(in crate::high_level_api) fn decompress(self) -> IntegerCompactPublicKey { + IntegerCompactPublicKey { + key: self.key.map(CompressedCompactPublicKeyDyn::decompress), + } + } +} diff --git a/tfhe/src/high_level_api/integers/mod.rs b/tfhe/src/high_level_api/integers/mod.rs index 0ca06ce93..0b2abe926 100644 --- a/tfhe/src/high_level_api/integers/mod.rs +++ b/tfhe/src/high_level_api/integers/mod.rs @@ -1,12 +1,13 @@ -pub use types::{ - CompressedFheUint10, CompressedFheUint12, CompressedFheUint128, CompressedFheUint14, - CompressedFheUint16, CompressedFheUint256, CompressedFheUint32, CompressedFheUint64, - CompressedFheUint8, FheUint10, FheUint12, FheUint128, FheUint14, FheUint16, FheUint256, - FheUint32, FheUint64, FheUint8, GenericInteger, -}; +expand_pub_use_fhe_type!( + pub use types{ + FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, + FheUint256 + }; +); pub(in crate::high_level_api) use keys::{ - IntegerClientKey, IntegerCompressedServerKey, IntegerConfig, IntegerServerKey, + IntegerClientKey, IntegerCompactPublicKey, IntegerCompressedCompactPublicKey, + IntegerCompressedServerKey, IntegerConfig, IntegerServerKey, }; pub(in crate::high_level_api) use public_key::compressed::CompressedPublicKeyDyn; pub(in crate::high_level_api) use public_key::PublicKeyDyn; diff --git a/tfhe/src/high_level_api/integers/tests.rs b/tfhe/src/high_level_api/integers/tests.rs index 12069c146..12ac0adad 100644 --- a/tfhe/src/high_level_api/integers/tests.rs +++ b/tfhe/src/high_level_api/integers/tests.rs @@ -1,9 +1,12 @@ +use rand::Rng; + use crate::high_level_api::prelude::*; use crate::high_level_api::{generate_keys, set_server_key, ConfigBuilder, FheUint8}; use crate::integer::U256; use crate::{ - CompressedFheUint16, CompressedFheUint256, CompressedPublicKey, FheUint128, FheUint16, - FheUint256, FheUint32, FheUint64, + CompactFheUint32, CompactFheUint32List, CompactPublicKey, CompressedFheUint16, + CompressedFheUint256, CompressedPublicKey, Config, FheUint128, FheUint16, FheUint256, + FheUint32, FheUint64, }; #[test] @@ -223,6 +226,98 @@ fn test_decompressed_public_key_encrypt() { assert_eq!(clear, 255u8); } +#[test] +fn test_compact_public_key_big() { + let config = ConfigBuilder::all_disabled() + .enable_custom_integers( + crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, + None, + ) + .build(); + let (client_key, _) = generate_keys(config); + + let public_key = CompactPublicKey::new(&client_key); + + let a = FheUint8::try_encrypt(255u8, &public_key).unwrap(); + let clear: u8 = a.decrypt(&client_key); + assert_eq!(clear, 255u8); +} + +#[test] +fn test_compact_public_key_list_big() { + let config = ConfigBuilder::all_disabled() + .enable_custom_integers( + crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, + None, + ) + .build(); + test_compact_public_key_list(config); +} + +#[test] +fn test_compact_public_key_list_small() { + let config = ConfigBuilder::all_disabled() + .enable_custom_integers( + crate::shortint::parameters::PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, + None, + ) + .build(); + test_compact_public_key_list(config); +} + +fn test_compact_public_key_list(config: Config) { + let (client_key, server_key) = generate_keys(config); + + let public_key = CompactPublicKey::new(&client_key); + + let mut rng = rand::thread_rng(); + + let clear_xs = (0..50).map(|_| rng.gen::()).collect::>(); + let clear_ys = (0..50).map(|_| rng.gen::()).collect::>(); + + let compacted_xs = CompactFheUint32List::encrypt(&clear_xs, &public_key); + let compacted_ys = CompactFheUint32List::encrypt(&clear_ys, &public_key); + + let exs = compacted_xs.expand(); + let eys = compacted_ys.expand(); + + set_server_key(server_key); + + let encrypted_results = exs.iter().zip(eys).map(|(x, y)| x + y).collect::>(); + let clear_results = clear_xs + .iter() + .zip(clear_ys) + .map(|(x, y)| x + y) + .collect::>(); + + for (encrypted, clear) in encrypted_results.iter().zip(clear_results) { + let decrypted: u32 = encrypted.decrypt(&client_key); + assert_eq!(clear, decrypted); + } + + let compact_single = CompactFheUint32::encrypt(clear_xs[0], &public_key); + let a = compact_single.expand(); + let decrypted: u32 = a.decrypt(&client_key); + assert_eq!(clear_xs[0], decrypted); +} + +#[test] +fn test_compact_public_key_small() { + let config = ConfigBuilder::all_disabled() + .enable_custom_integers( + crate::shortint::parameters::PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, + None, + ) + .build(); + let (client_key, _) = generate_keys(config); + + let public_key = CompactPublicKey::new(&client_key); + + let a = FheUint8::try_encrypt(255u8, &public_key).unwrap(); + let clear: u8 = a.decrypt(&client_key); + assert_eq!(clear, 255u8); +} + #[test] fn test_trivial_fhe_uint8() { let config = ConfigBuilder::all_disabled() diff --git a/tfhe/src/high_level_api/integers/types/base.rs b/tfhe/src/high_level_api/integers/types/base.rs index fce0c6679..93be3ccfe 100644 --- a/tfhe/src/high_level_api/integers/types/base.rs +++ b/tfhe/src/high_level_api/integers/types/base.rs @@ -26,6 +26,8 @@ use crate::high_level_api::traits::{ FheBootstrap, FheDecrypt, FheEq, FheOrd, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt, }; use crate::high_level_api::{ClientKey, PublicKey}; +use crate::integer::U256; +use crate::CompactPublicKey; /// A Generic FHE unsigned integer /// @@ -212,6 +214,25 @@ where } } +impl FheTryEncrypt for GenericInteger

+where + T: Into, + P: IntegerParameter, + P::Id: Default + TypeIdentifier, +{ + type Error = crate::high_level_api::errors::Error; + + fn try_encrypt(value: T, key: &CompactPublicKey) -> Result { + let id = P::Id::default(); + let ciphertext = key + .integer_key + .try_encrypt(value, P::num_blocks()) + .ok_or(UninitializedPublicKey(id.type_variant())) + .unwrap_display(); + Ok(Self::new(ciphertext, id)) + } +} + impl FheTryTrivialEncrypt for GenericInteger

where T: crate::integer::block_decomposition::DecomposableInto, diff --git a/tfhe/src/high_level_api/integers/types/compact.rs b/tfhe/src/high_level_api/integers/types/compact.rs new file mode 100644 index 000000000..3e7f1bf42 --- /dev/null +++ b/tfhe/src/high_level_api/integers/types/compact.rs @@ -0,0 +1,119 @@ +use crate::errors::{UninitializedPublicKey, UnwrapResultExt}; +use crate::high_level_api::integers::parameters::IntegerParameter; +use crate::high_level_api::integers::server_key::RadixCiphertextDyn; +use crate::high_level_api::integers::types::base::GenericInteger; +use crate::high_level_api::internal_traits::TypeIdentifier; +use crate::high_level_api::traits::FheTryEncrypt; +use crate::integer::ciphertext::{CompactCiphertextListBig, CompactCiphertextListSmall}; +use crate::CompactPublicKey; + +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub(in crate::high_level_api::integers) enum CompactCiphertextListDyn { + Big(CompactCiphertextListBig), + Small(CompactCiphertextListSmall), +} + +#[cfg_attr(all(doc, not(doctest)), doc(cfg(feature = "integer")))] +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct GenericCompactInteger { + pub(in crate::high_level_api::integers) list: CompactCiphertextListDyn, + pub(in crate::high_level_api::integers) id: P::Id, +} + +#[cfg_attr(all(doc, not(doctest)), doc(cfg(feature = "integer")))] +#[derive(Clone, serde::Deserialize, serde::Serialize)] +pub struct GenericCompactIntegerList { + pub(in crate::high_level_api::integers) list: CompactCiphertextListDyn, + pub(in crate::high_level_api::integers) id: P::Id, +} + +impl

GenericCompactInteger

+where + P: IntegerParameter, +{ + pub fn expand(&self) -> GenericInteger

{ + match &self.list { + CompactCiphertextListDyn::Big(list) => { + let expanded = RadixCiphertextDyn::Big(list.expand_one()); + GenericInteger::new(expanded, self.id) + } + CompactCiphertextListDyn::Small(list) => { + let expanded = RadixCiphertextDyn::Small(list.expand_one()); + GenericInteger::new(expanded, self.id) + } + } + } +} + +impl

GenericCompactIntegerList

+where + P: IntegerParameter, +{ + pub fn len(&self) -> usize { + match &self.list { + CompactCiphertextListDyn::Big(list) => list.ciphertext_count(), + CompactCiphertextListDyn::Small(list) => list.ciphertext_count(), + } + } + + pub fn expand(&self) -> Vec> { + match &self.list { + CompactCiphertextListDyn::Big(list) => list + .expand() + .into_iter() + .map(RadixCiphertextDyn::Big) + .map(|ct| GenericInteger::new(ct, self.id)) + .collect::>(), + CompactCiphertextListDyn::Small(list) => list + .expand() + .into_iter() + .map(RadixCiphertextDyn::Small) + .map(|ct| GenericInteger::new(ct, self.id)) + .collect::>(), + } + } +} + +impl FheTryEncrypt for GenericCompactInteger

+where + T: crate::integer::block_decomposition::DecomposableInto, + P: IntegerParameter, + P::Id: Default + TypeIdentifier, +{ + type Error = crate::high_level_api::errors::Error; + + fn try_encrypt(value: T, key: &CompactPublicKey) -> Result { + let id = P::Id::default(); + let ciphertext = key + .integer_key + .try_encrypt_compact(&[value], P::num_blocks()) + .ok_or(UninitializedPublicKey(id.type_variant())) + .unwrap_display(); + Ok(Self { + list: ciphertext, + id, + }) + } +} + +impl<'a, P, T> FheTryEncrypt<&'a [T], CompactPublicKey> for GenericCompactIntegerList

+where + T: crate::integer::block_decomposition::DecomposableInto, + P: IntegerParameter, + P::Id: Default + TypeIdentifier, +{ + type Error = crate::high_level_api::errors::Error; + + fn try_encrypt(values: &'a [T], key: &CompactPublicKey) -> Result { + let id = P::Id::default(); + let ciphertext = key + .integer_key + .try_encrypt_compact(values, P::num_blocks()) + .ok_or(UninitializedPublicKey(id.type_variant())) + .unwrap_display(); + Ok(Self { + list: ciphertext, + id, + }) + } +} diff --git a/tfhe/src/high_level_api/integers/types/mod.rs b/tfhe/src/high_level_api/integers/types/mod.rs index c6e165318..1e6d3e58a 100644 --- a/tfhe/src/high_level_api/integers/types/mod.rs +++ b/tfhe/src/high_level_api/integers/types/mod.rs @@ -1,11 +1,13 @@ pub use base::GenericInteger; -pub use static_::{ - CompressedFheUint10, CompressedFheUint12, CompressedFheUint128, CompressedFheUint14, - CompressedFheUint16, CompressedFheUint256, CompressedFheUint32, CompressedFheUint64, - CompressedFheUint8, FheUint10, FheUint12, FheUint128, FheUint14, FheUint16, FheUint256, - FheUint32, FheUint64, FheUint8, -}; + +expand_pub_use_fhe_type!( + pub use static_{ + FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, + FheUint256 + }; +); pub(super) mod base; +pub(super) mod compact; pub(super) mod compressed; pub(super) mod static_; diff --git a/tfhe/src/high_level_api/integers/types/static_.rs b/tfhe/src/high_level_api/integers/types/static_.rs index 33679598d..c9d4d9eb7 100644 --- a/tfhe/src/high_level_api/integers/types/static_.rs +++ b/tfhe/src/high_level_api/integers/types/static_.rs @@ -2,6 +2,9 @@ use serde::{Deserialize, Serialize}; use super::base::GenericInteger; use crate::high_level_api::integers::parameters::{EvaluationIntegerKey, IntegerParameter}; +use crate::high_level_api::integers::types::compact::{ + GenericCompactInteger, GenericCompactIntegerList, +}; use crate::high_level_api::integers::types::compressed::CompressedGenericInteger; use crate::high_level_api::internal_traits::{ParameterType, TypeIdentifier}; #[cfg(feature = "internal-keycache")] @@ -67,6 +70,11 @@ macro_rules! static_int_type { #[cfg_attr(all(doc, not(doctest)), cfg(feature = "integer"))] pub type [] = CompressedGenericInteger<[<$name Parameters>]>; + #[cfg_attr(all(doc, not(doctest)), cfg(feature = "integer"))] + pub type [] = GenericCompactInteger<[<$name Parameters>]>; + + #[cfg_attr(all(doc, not(doctest)), cfg(feature = "integer"))] + pub type [] = GenericCompactIntegerList<[<$name Parameters>]>; impl $crate::high_level_api::keys::RefKeyFromKeyChain for [] { type Key = crate::integer::ClientKey; diff --git a/tfhe/src/high_level_api/keys/client.rs b/tfhe/src/high_level_api/keys/client.rs index 1495b104d..c3a889ddf 100644 --- a/tfhe/src/high_level_api/keys/client.rs +++ b/tfhe/src/high_level_api/keys/client.rs @@ -2,6 +2,8 @@ //! //! - [ClientKey] aggregates the keys used to encrypt/decrypt between normal and homomorphic types. +use concrete_csprng::seeders::Seed; + #[cfg(feature = "boolean")] use crate::high_level_api::booleans::BooleanClientKey; use crate::high_level_api::config::Config; @@ -44,6 +46,19 @@ impl ClientKey { } } + pub fn generate_with_seed>(config: C, seed: Seed) -> ClientKey { + #[allow(unused_variables)] + let config: Config = config.into(); + ClientKey { + #[cfg(feature = "boolean")] + boolean_key: BooleanClientKey::with_seed(config.boolean_config, seed), + #[cfg(feature = "shortint")] + shortint_key: ShortIntClientKey::with_seed(config.shortint_config, seed), + #[cfg(feature = "integer")] + integer_key: IntegerClientKey::with_seed(config.integer_config, seed), + } + } + /// Generates a new ServerKey /// /// The `ServerKey` generated is meant to be used to initialize the global state diff --git a/tfhe/src/high_level_api/keys/mod.rs b/tfhe/src/high_level_api/keys/mod.rs index f2c5e7113..48219a936 100644 --- a/tfhe/src/high_level_api/keys/mod.rs +++ b/tfhe/src/high_level_api/keys/mod.rs @@ -7,7 +7,8 @@ mod server; use crate::high_level_api::config::Config; pub use client::{ClientKey, RefKeyFromKeyChain}; pub use public::{ - CompressedPublicKey, PublicKey, RefKeyFromCompressedPublicKeyChain, RefKeyFromPublicKeyChain, + CompactPublicKey, CompressedCompactPublicKey, CompressedPublicKey, PublicKey, + RefKeyFromCompressedPublicKeyChain, RefKeyFromPublicKeyChain, }; pub use server::{CompressedServerKey, ServerKey}; diff --git a/tfhe/src/high_level_api/keys/public.rs b/tfhe/src/high_level_api/keys/public.rs index 641f915b8..2d7db7e67 100644 --- a/tfhe/src/high_level_api/keys/public.rs +++ b/tfhe/src/high_level_api/keys/public.rs @@ -5,6 +5,8 @@ #[cfg(feature = "boolean")] use crate::high_level_api::booleans::{BooleanCompressedPublicKey, BooleanPublicKey}; use crate::high_level_api::errors::{UninitializedPublicKey, UnwrapResultExt}; +#[cfg(feature = "integer")] +use crate::high_level_api::integers::{IntegerCompactPublicKey, IntegerCompressedCompactPublicKey}; #[cfg(feature = "shortint")] use crate::high_level_api::shortints::{ShortIntCompressedPublicKey, ShortIntPublicKey}; @@ -189,3 +191,76 @@ macro_rules! impl_ref_key_from_compressed_public_keychain { } } } + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct CompactPublicKey { + #[cfg(feature = "integer")] + pub(in crate::high_level_api) integer_key: IntegerCompactPublicKey, +} + +impl CompactPublicKey { + /// Creates a CompactPublicKey + /// + /// Compared to the [PublicKey], this one is much smaller + /// however it supports less parameters. + /// + /// # Panic + /// + /// This will panic if parameters are not compatible + pub fn new(client_key: &ClientKey) -> Self { + #[cfg(feature = "integer")] + { + Self { + integer_key: IntegerCompactPublicKey::new(&client_key.integer_key), + } + } + #[cfg(not(feature = "integer"))] + { + let _ = client_key; + Self {} + } + } + + pub fn try_new(client_key: &ClientKey) -> Option { + #[cfg(feature = "integer")] + { + Some(Self { + integer_key: IntegerCompactPublicKey::try_new(&client_key.integer_key)?, + }) + } + #[cfg(not(feature = "integer"))] + { + let _ = client_key; + Some(Self {}) + } + } +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct CompressedCompactPublicKey { + #[cfg(feature = "integer")] + pub(in crate::high_level_api) integer_key: IntegerCompressedCompactPublicKey, +} + +impl CompressedCompactPublicKey { + pub fn new(client_key: &ClientKey) -> Self { + #[cfg(feature = "integer")] + { + Self { + integer_key: IntegerCompressedCompactPublicKey::new(&client_key.integer_key), + } + } + #[cfg(not(feature = "integer"))] + { + let _ = client_key; + Self {} + } + } + + pub fn decompress(self) -> CompactPublicKey { + CompactPublicKey { + #[cfg(feature = "integer")] + integer_key: self.integer_key.decompress(), + } + } +} diff --git a/tfhe/src/high_level_api/mod.rs b/tfhe/src/high_level_api/mod.rs index 7e457908b..b87ce5cce 100644 --- a/tfhe/src/high_level_api/mod.rs +++ b/tfhe/src/high_level_api/mod.rs @@ -1,9 +1,32 @@ #![allow(unused_doc_comments)] + +#[allow(unused)] +macro_rules! expand_pub_use_fhe_type( + ( + pub use $module_path:path { $($fhe_type_name:ident),* $(,)? }; + )=> { + + ::paste::paste! { + pub use $module_path::{ + $( + $fhe_type_name, + [], + [], + [], + )* + }; + + } + } +); + +pub use crate::core_crypto::commons::math::random::Seed; pub use config::{Config, ConfigBuilder}; pub use errors::{Error, OutOfRangeError}; pub use global_state::{set_server_key, unset_server_key, with_server_key_as_context}; pub use keys::{ - generate_keys, ClientKey, CompressedPublicKey, CompressedServerKey, PublicKey, ServerKey, + generate_keys, ClientKey, CompactPublicKey, CompressedCompactPublicKey, CompressedPublicKey, + CompressedServerKey, PublicKey, ServerKey, }; #[cfg(test)] @@ -12,12 +35,12 @@ mod tests; #[cfg(feature = "boolean")] pub use crate::high_level_api::booleans::{CompressedFheBool, FheBool, FheBoolParameters}; #[cfg(feature = "integer")] -pub use crate::high_level_api::integers::{ - CompressedFheUint10, CompressedFheUint12, CompressedFheUint128, CompressedFheUint14, - CompressedFheUint16, CompressedFheUint256, CompressedFheUint32, CompressedFheUint64, - CompressedFheUint8, FheUint10, FheUint12, FheUint128, FheUint14, FheUint16, FheUint256, - FheUint32, FheUint64, FheUint8, GenericInteger, -}; +expand_pub_use_fhe_type!( + pub use crate::high_level_api::integers{ + FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, + FheUint256 + }; +); #[cfg(feature = "shortint")] pub use crate::high_level_api::shortints::{ CompressedFheUint2, CompressedFheUint3, CompressedFheUint4, FheUint2, FheUint2Parameters, diff --git a/tfhe/src/high_level_api/shortints/client_key.rs b/tfhe/src/high_level_api/shortints/client_key.rs index c7496ccaf..350272f27 100644 --- a/tfhe/src/high_level_api/shortints/client_key.rs +++ b/tfhe/src/high_level_api/shortints/client_key.rs @@ -7,6 +7,10 @@ use crate::shortint::keycache::KEY_CACHE; use crate::shortint::ClientKey; use super::parameters::ShortIntegerParameter; +use concrete_csprng::seeders::Seed; + +use crate::core_crypto::commons::generators::DeterministicSeeder; +use crate::core_crypto::prelude::ActivatedRandomGenerator; /// The key associated to a short integer type /// @@ -17,6 +21,23 @@ pub struct GenericShortIntClientKey { _marker: PhantomData

, } +impl

GenericShortIntClientKey

+where + P: ShortIntegerParameter, +{ + pub(crate) fn with_seed(parameters: P, seed: Seed) -> Self { + let mut seeder = DeterministicSeeder::::new(seed); + let key = crate::shortint::engine::ShortintEngine::new_from_seeder(&mut seeder) + .new_client_key(parameters.into().into()) + .unwrap(); + + Self { + key, + _marker: PhantomData, + } + } +} + impl

From

for GenericShortIntClientKey

where P: ShortIntegerParameter, diff --git a/tfhe/src/high_level_api/tests.rs b/tfhe/src/high_level_api/tests.rs index 66bf65e5e..9db6452fa 100644 --- a/tfhe/src/high_level_api/tests.rs +++ b/tfhe/src/high_level_api/tests.rs @@ -9,7 +9,7 @@ use crate::high_level_api::{generate_keys, ClientKey, ConfigBuilder, PublicKey}; use crate::high_level_api::{FheUint256, FheUint8}; #[cfg(feature = "integer")] use crate::integer::U256; -use crate::{CompressedPublicKey, CompressedServerKey}; +use crate::{CompactPublicKey, CompressedPublicKey, CompressedServerKey}; #[cfg(any(feature = "boolean", feature = "shortint", feature = "integer"))] use std::fmt::Debug; @@ -129,6 +129,41 @@ fn test_server_key_decompression() -> Result<(), Box> { Ok(()) } +#[test] +fn test_with_seed() -> Result<(), Box> { + use crate::Seed; + let mut builder = ConfigBuilder::all_disabled(); + #[cfg(feature = "integer")] + { + builder = builder.enable_default_integers(); + } + #[cfg(feature = "shortint")] + { + builder = builder.enable_default_uint2(); + } + #[cfg(feature = "boolean")] + { + builder = builder.enable_default_bool(); + } + let config = builder.build(); + + let cks1 = ClientKey::generate_with_seed(config.clone(), Seed(125)); + let cks2 = ClientKey::generate(config.clone()); + let cks3 = ClientKey::generate_with_seed(config.clone(), Seed(125)); + let cks4 = ClientKey::generate_with_seed(config, Seed(127)); + + let cks1_serialized = bincode::serialize(&cks1).unwrap(); + let cks2_serialized = bincode::serialize(&cks2).unwrap(); + let cks3_serialized = bincode::serialize(&cks3).unwrap(); + let cks4_serialized = bincode::serialize(&cks4).unwrap(); + + assert_eq!(&cks1_serialized, &cks3_serialized); + assert_ne!(&cks1_serialized, &cks2_serialized); + assert_ne!(&cks1_serialized, &cks4_serialized); + + Ok(()) +} + #[cfg(feature = "integer")] #[test] #[should_panic] @@ -172,10 +207,12 @@ fn test_serialize_deserialize_are_implemented() { let pks = PublicKey::new(&cks); let cpks = CompressedPublicKey::new(&cks); let csks = CompressedServerKey::new(&cks); + let pksz = CompactPublicKey::new(&cks); can_be_deserialized(&cks); can_be_deserialized(&sks); can_be_deserialized(&pks); can_be_deserialized(&cpks); can_be_deserialized(&csks); + can_be_deserialized(&pksz); } diff --git a/tfhe/src/integer/ciphertext/mod.rs b/tfhe/src/integer/ciphertext/mod.rs index fd8601e1c..518eadd12 100644 --- a/tfhe/src/integer/ciphertext/mod.rs +++ b/tfhe/src/integer/ciphertext/mod.rs @@ -30,6 +30,58 @@ pub type RadixCiphertextSmall = BaseRadixCiphertext; pub type CompressedRadixCiphertextBig = BaseRadixCiphertext; pub type CompressedRadixCiphertextSmall = BaseRadixCiphertext; +pub type CompactCiphertextListBig = CompactCiphertextList; +pub type CompactCiphertextListSmall = CompactCiphertextList; + +#[derive(Clone, Serialize, Deserialize)] +pub struct CompactCiphertextList { + pub(crate) ct_list: crate::shortint::ciphertext::CompactCiphertextList, + // Keep track of the num_blocks, as we allow + // storing many integer that have the same num_blocks + // into ct_list + pub(crate) num_blocks: usize, +} + +impl CompactCiphertextList { + pub fn expand_one(&self) -> RadixCiphertext { + let mut blocks = self.ct_list.expand(); + blocks.truncate(self.num_blocks); + RadixCiphertext::from(blocks) + } + + pub fn ciphertext_count(&self) -> usize { + self.ct_list.ct_list.lwe_ciphertext_count().0 / self.num_blocks + } + + pub fn expand(&self) -> Vec> { + let mut all_block_iter = self.ct_list.expand().into_iter(); + let num_ct = self.ciphertext_count(); + let mut ciphertexts = Vec::with_capacity(num_ct); + + for _ in 0..num_ct { + let ct_blocks = all_block_iter + .by_ref() + .take(self.num_blocks) + .collect::>(); + if ct_blocks.len() < self.num_blocks { + break; + } + let ct = RadixCiphertext::from(ct_blocks); + ciphertexts.push(ct); + } + + ciphertexts + } + + pub fn size_elements(&self) -> usize { + self.ct_list.size_elements() + } + + pub fn size_bytes(&self) -> usize { + self.ct_list.size_bytes() + } +} + impl RadixCiphertext { pub fn block_carries_are_empty(&self) -> bool { self.blocks.iter().all(|block| block.carry_is_empty()) diff --git a/tfhe/src/integer/encryption.rs b/tfhe/src/integer/encryption.rs index eea545d97..d7e0313e9 100644 --- a/tfhe/src/integer/encryption.rs +++ b/tfhe/src/integer/encryption.rs @@ -27,6 +27,14 @@ impl KnowsMessageModulus } } +impl KnowsMessageModulus + for crate::shortint::CompactPublicKeyBase +{ + fn message_modulus(&self) -> MessageModulus { + self.parameters.message_modulus() + } +} + impl KnowsMessageModulus for crate::shortint::ServerKey { fn message_modulus(&self) -> MessageModulus { self.message_modulus @@ -53,26 +61,34 @@ where F: Fn(&BlockKey, u64) -> Block, RadixCiphertextType: From>, { - let bits_in_block = encrypting_key.message_modulus().0.ilog2(); - let decomposer = BlockDecomposer::new(message, bits_in_block); - let mut blocks = Vec::with_capacity(num_blocks); - for clear_block in decomposer.iter_as::().take(num_blocks) { - let ct = encrypt_block(encrypting_key, clear_block); - blocks.push(ct); - } + let message_modulus = encrypting_key.message_modulus(); + let clear_block_iterator = + create_clear_radix_block_iterator(message, message_modulus, num_blocks); - // This will happen if T has less bits than - // bits_in_block * num_blocks - if blocks.len() < num_blocks { - for _ in 0..num_blocks - blocks.len() { - let ct = encrypt_block(encrypting_key, 0); - blocks.push(ct); - } - } + let blocks = clear_block_iterator + .map(|clear_block| encrypt_block(encrypting_key, clear_block)) + .collect::>(); RadixCiphertextType::from(blocks) } +pub(crate) fn create_clear_radix_block_iterator( + message: T, + message_modulus: MessageModulus, + num_blocks: usize, +) -> impl Iterator +where + T: DecomposableInto, +{ + let bits_in_block = message_modulus.0.ilog2(); + let decomposer = BlockDecomposer::new(message, bits_in_block); + + decomposer + .iter_as::() + .chain(std::iter::repeat(0u64)) + .take(num_blocks) +} + pub(crate) fn encrypt_crt( encrypting_key: &BlockKey, message: u64, diff --git a/tfhe/src/integer/mod.rs b/tfhe/src/integer/mod.rs index 1ead489e0..7ca6d77fb 100755 --- a/tfhe/src/integer/mod.rs +++ b/tfhe/src/integer/mod.rs @@ -69,7 +69,9 @@ pub use ciphertext::{ }; pub use client_key::{ClientKey, CrtClientKey, RadixClientKey}; pub use public_key::{ - CompressedPublicKeyBig, CompressedPublicKeySmall, PublicKeyBig, PublicKeySmall, + CompactPublicKeyBig, CompactPublicKeySmall, CompressedCompactPublicKeyBase, + CompressedCompactPublicKeyBig, CompressedCompactPublicKeySmall, CompressedPublicKeyBig, + CompressedPublicKeySmall, PublicKeyBig, PublicKeySmall, }; pub use server_key::{CheckError, CompressedServerKey, ServerKey}; pub use u256::U256; diff --git a/tfhe/src/integer/public_key/compact.rs b/tfhe/src/integer/public_key/compact.rs new file mode 100644 index 000000000..ffb406b4a --- /dev/null +++ b/tfhe/src/integer/public_key/compact.rs @@ -0,0 +1,154 @@ +use serde::{Deserialize, Serialize}; + +use crate::integer::block_decomposition::DecomposableInto; +use crate::integer::ciphertext::{CompactCiphertextList, RadixCiphertext}; +use crate::integer::encryption::{create_clear_radix_block_iterator, encrypt_words_radix_impl}; +use crate::integer::ClientKey; +use crate::shortint::ciphertext::{BootstrapKeyswitch, KeyswitchBootstrap}; +use crate::shortint::{ + CompactPublicKeyBase as ShortintCompactPublicKeyBase, + CompressedCompactPublicKeyBase as ShortintCompressedCompactPublicKeyBase, PBSOrderMarker, +}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompactPublicKeyBase { + pub(crate) key: ShortintCompactPublicKeyBase, +} + +pub type CompactPublicKeyBig = CompactPublicKeyBase; +pub type CompactPublicKeySmall = CompactPublicKeyBase; + +impl CompactPublicKeyBase { + pub fn new(client_key: &ClientKey) -> Self { + let key = ShortintCompactPublicKeyBase::::new(&client_key.key); + Self { key } + } + + pub fn try_new(client_key: &ClientKey) -> Option { + let key = ShortintCompactPublicKeyBase::::try_new(&client_key.key)?; + Some(Self { key }) + } + + pub fn encrypt_radix>( + &self, + message: T, + num_blocks: usize, + ) -> RadixCiphertext { + encrypt_words_radix_impl( + &self.key, + message, + num_blocks, + ShortintCompactPublicKeyBase::encrypt, + ) + } + + pub fn encrypt_radix_compact>( + &self, + message: T, + num_blocks: usize, + ) -> CompactCiphertextList { + let clear_block_iter = create_clear_radix_block_iterator( + message, + self.key.parameters.message_modulus(), + num_blocks, + ); + + let ct_list = self.key.encrypt_iter(clear_block_iter); + CompactCiphertextList { + ct_list, + num_blocks, + } + } + + pub fn encrypt_slice_radix_compact>( + &self, + messages: &[T], + num_blocks: usize, + ) -> CompactCiphertextList { + self.encrypt_iter_radix_compact(messages.iter().copied(), num_blocks) + } + + pub fn encrypt_iter_radix_compact>( + &self, + mut message_iter: impl Iterator, + num_blocks: usize, + ) -> CompactCiphertextList { + let mut iterator_chain; + match (message_iter.next(), message_iter.next()) { + (None, None) => panic!("At least one message is required"), + (None, Some(_)) => unreachable!(), + (Some(first_message), None) => { + // Cannot form a chain + return self.encrypt_radix_compact(first_message, num_blocks); + } + (Some(first_message), Some(second_message)) => { + let first_iter = create_clear_radix_block_iterator( + first_message, + self.key.parameters.message_modulus(), + num_blocks, + ); + let second_iter = create_clear_radix_block_iterator( + second_message, + self.key.parameters.message_modulus(), + num_blocks, + ); + + iterator_chain = + Box::new(first_iter.chain(second_iter)) as Box>; + } + } + + for message in message_iter { + let other_iter = create_clear_radix_block_iterator( + message, + self.key.parameters.message_modulus(), + num_blocks, + ); + + iterator_chain = Box::new(iterator_chain.chain(other_iter)); + } + + let ct_list = self.key.encrypt_iter(iterator_chain); + CompactCiphertextList { + ct_list, + num_blocks, + } + } + + pub fn size_elements(&self) -> usize { + self.key.size_elements() + } + + pub fn size_bytes(&self) -> usize { + self.key.size_bytes() + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompressedCompactPublicKeyBase { + pub(crate) key: ShortintCompressedCompactPublicKeyBase, +} + +pub type CompressedCompactPublicKeyBig = CompressedCompactPublicKeyBase; +pub type CompressedCompactPublicKeySmall = CompressedCompactPublicKeyBase; + +impl CompressedCompactPublicKeyBase { + pub fn new(client_key: &ClientKey) -> Self { + let key = ShortintCompressedCompactPublicKeyBase::::new(&client_key.key); + Self { key } + } + + pub fn decompress(self) -> CompactPublicKeyBase { + CompactPublicKeyBase { + key: self.key.decompress(), + } + } +} + +impl From> + for CompactPublicKeyBase +{ + fn from(value: CompressedCompactPublicKeyBase) -> Self { + value.decompress() + } +} diff --git a/tfhe/src/integer/public_key/mod.rs b/tfhe/src/integer/public_key/mod.rs index c0dae82c9..6bc952334 100644 --- a/tfhe/src/integer/public_key/mod.rs +++ b/tfhe/src/integer/public_key/mod.rs @@ -1,8 +1,13 @@ //! Module with the definition of the encryption PublicKey. +pub mod compact; pub mod compressed; pub mod standard; +pub use compact::{ + CompactPublicKeyBase, CompactPublicKeyBig, CompactPublicKeySmall, + CompressedCompactPublicKeyBase, CompressedCompactPublicKeyBig, CompressedCompactPublicKeySmall, +}; pub use compressed::{CompressedPublicKeyBase, CompressedPublicKeyBig, CompressedPublicKeySmall}; pub use standard::{PublicKey, PublicKeyBig, PublicKeySmall}; diff --git a/tfhe/src/integer/public_key/tests.rs b/tfhe/src/integer/public_key/tests.rs index c5e360b23..a24225146 100644 --- a/tfhe/src/integer/public_key/tests.rs +++ b/tfhe/src/integer/public_key/tests.rs @@ -1,6 +1,6 @@ use rand::Rng; -use crate::integer::{CompressedPublicKeyBig, PublicKeyBig}; +use crate::integer::{gen_keys, CompressedPublicKeyBig, PublicKeyBig}; use crate::shortint::parameters::*; use crate::shortint::ClassicPBSParameters; @@ -17,6 +17,14 @@ create_parametrized_test!(radix_encrypt_decrypt_compressed_128_bits { * PARAM_MESSAGE_4_CARRY_4, Skipped as its slow */ }); +create_parametrized_test!(big_radix_encrypt_decrypt_compact_128_bits_list { + PARAM_MESSAGE_2_CARRY_2_COMPACT_PK +}); + +create_parametrized_test!(small_radix_encrypt_decrypt_compact_128_bits_list { + PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK +}); + /// Test that the public key can encrypt a 128 bit number /// in radix decomposition, and that the client key can decrypt it fn big_radix_encrypt_decrypt_128_bits(param: ClassicPBSParameters) { @@ -58,3 +66,46 @@ fn radix_encrypt_decrypt_compressed_128_bits(param: ClassicPBSParameters) { // assert assert_eq!(clear, dec); } + +fn big_radix_encrypt_decrypt_compact_128_bits_list(params: ClassicPBSParameters) { + radix_encrypt_decrypt_compact_128_bits_list::( + params, + ); +} + +fn small_radix_encrypt_decrypt_compact_128_bits_list(params: ClassicPBSParameters) { + radix_encrypt_decrypt_compact_128_bits_list::( + params, + ); +} + +fn radix_encrypt_decrypt_compact_128_bits_list( + params: ClassicPBSParameters, +) { + let (cks, _) = gen_keys(params); + let pk = crate::integer::public_key::CompactPublicKeyBase::::new(&cks); + + let mut rng = rand::thread_rng(); + let num_block = (128f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize; + + const MAX_CT: usize = 20; + + let mut clear_vec = Vec::with_capacity(MAX_CT); + for _ in 0..25 { + let num_ct_for_this_iter = rng.gen_range(1..=MAX_CT); + clear_vec.truncate(0); + for _ in 0..num_ct_for_this_iter { + let clear = rng.gen::(); + clear_vec.push(clear); + } + + let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block); + + let ciphertext_vec = compact_encrypted_list.expand(); + + for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) { + let decrypted: u128 = cks.decrypt_radix(ciphertext); + assert_eq!(decrypted, clear); + } + } +} diff --git a/tfhe/src/js_on_wasm_api/boolean.rs b/tfhe/src/js_on_wasm_api/boolean.rs index d68036373..896579904 100644 --- a/tfhe/src/js_on_wasm_api/boolean.rs +++ b/tfhe/src/js_on_wasm_api/boolean.rs @@ -1,7 +1,9 @@ +pub use crate::core_crypto::commons::math::random::Seed; use bincode; use wasm_bindgen::prelude::*; -use super::js_wasm_seeder; +use crate::core_crypto::commons::generators::DeterministicSeeder; +use crate::core_crypto::prelude::ActivatedRandomGenerator; use std::panic::set_hook; @@ -101,14 +103,10 @@ impl Boolean { let seed_low_bytes: u128 = seed_low_bytes.into(); let seed: u128 = (seed_high_bytes << 64) | seed_low_bytes; - let mut constant_seeder = Box::new(js_wasm_seeder::ConstantSeeder::new( - crate::core_crypto::commons::math::random::Seed(seed), - )); - - let mut tmp_boolean_engine = - crate::boolean::engine::BooleanEngine::new_from_seeder(constant_seeder.as_mut()); - - BooleanClientKey(tmp_boolean_engine.create_client_key(parameters.0.to_owned())) + let mut seeder = DeterministicSeeder::::new(Seed(seed)); + let key = crate::boolean::engine::BooleanEngine::new_from_seeder(&mut seeder) + .create_client_key(parameters.0.to_owned()); + BooleanClientKey(key) } #[wasm_bindgen] diff --git a/tfhe/src/js_on_wasm_api/high_level_api/config.rs b/tfhe/src/js_on_wasm_api/high_level_api/config.rs index 8e55e9e78..27690ea69 100644 --- a/tfhe/src/js_on_wasm_api/high_level_api/config.rs +++ b/tfhe/src/js_on_wasm_api/high_level_api/config.rs @@ -25,6 +25,14 @@ impl TfheConfigBuilder { Self(self.0.enable_default_integers_small()) } + #[wasm_bindgen] + pub fn enable_custom_integers( + self, + block_parameters: crate::js_on_wasm_api::shortint::ShortintParameters, + ) -> Self { + Self(self.0.enable_custom_integers(block_parameters.0, None)) + } + #[wasm_bindgen] pub fn build(self) -> TfheConfig { TfheConfig(self.0.build()) diff --git a/tfhe/src/js_on_wasm_api/high_level_api/integers.rs b/tfhe/src/js_on_wasm_api/high_level_api/integers.rs index c42aa7ea9..f0db58778 100644 --- a/tfhe/src/js_on_wasm_api/high_level_api/integers.rs +++ b/tfhe/src/js_on_wasm_api/high_level_api/integers.rs @@ -44,8 +44,9 @@ macro_rules! create_wrapper_type_non_native_type( { type_name: $type_name:ident, compressed_type_name: $compressed_type_name:ident, - rust_type: $rust_type:ty - $(,)? + compact_type_name: $compact_type_name:ident, + compact_list_type_name: $compact_list_type_name:ident, + rust_type: $rust_type:ty $(,)? } ) => { #[wasm_bindgen] @@ -95,6 +96,20 @@ macro_rules! create_wrapper_type_non_native_type( }) } + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + value: JsValue, + compact_public_key: &crate::js_on_wasm_api::high_level_api::keys::TfheCompactPublicKey, + ) -> Result<$type_name, JsError> { + catch_panic_result(|| { + let value = <$rust_type>::try_from(value) + .map_err(|_| JsError::new(&format!("Failed to convert the value to a {}", stringify!($rust_type))))?; + crate::high_level_api::$type_name::try_encrypt(value, &compact_public_key.0) + .map($type_name) + .map_err(into_js_error) + }) + } + #[wasm_bindgen] pub fn decrypt( &self, @@ -122,6 +137,7 @@ macro_rules! create_wrapper_type_non_native_type( }) } } + #[wasm_bindgen] pub struct $compressed_type_name(pub(crate) crate::high_level_api::$compressed_type_name); @@ -164,14 +180,113 @@ macro_rules! create_wrapper_type_non_native_type( }) } } + + #[wasm_bindgen] + pub struct $compact_type_name(pub(crate) crate::high_level_api::$compact_type_name); + + #[wasm_bindgen] + impl $compact_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + value: JsValue, + client_key: &crate::js_on_wasm_api::high_level_api::keys::TfheCompactPublicKey, + ) -> Result<$compact_type_name, JsError> { + catch_panic_result(|| { + let value = <$rust_type>::try_from(value) + .map_err(|_| JsError::new(&format!("Failed to convert the value to a {}", stringify!($rust_type))))?; + crate::high_level_api::$compact_type_name::try_encrypt(value, &client_key.0) + .map($compact_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn expand( + &self, + ) -> Result<$type_name, JsError> { + catch_panic(||{ + $type_name(self.0.expand()) + }) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result, JsError> { + catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) + } + + #[wasm_bindgen] + pub fn deserialize(buffer: &[u8]) -> Result<$compact_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($compact_type_name) + .map_err(into_js_error) + }) + } + } + + #[wasm_bindgen] + pub struct $compact_list_type_name(pub(crate) crate::high_level_api::$compact_list_type_name); + + #[wasm_bindgen] + impl $compact_list_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + values: Vec, + public_key: &crate::js_on_wasm_api::high_level_api::keys::TfheCompactPublicKey, + ) -> Result<$compact_list_type_name, JsError> { + catch_panic_result(|| { + let values = values + .into_iter() + .map(|value| { + <$rust_type>::try_from(value) + .map_err(|_| { + JsError::new(&format!("Failed to convert the value to a {}", stringify!($rust_type))) + }) + }) + .collect::, _>>()?; + crate::high_level_api::$compact_list_type_name::try_encrypt(&values, &public_key.0) + .map($compact_list_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn expand( + &self, + ) -> Result, JsError> { + catch_panic(||{ + self.0.expand() + .into_iter() + .map($type_name) + .map(JsValue::from) + .collect::>() + }) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result, JsError> { + catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) + } + + #[wasm_bindgen] + pub fn deserialize(buffer: &[u8]) -> Result<$compact_list_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($compact_list_type_name) + .map_err(into_js_error) + }) + } + } }; + ( $( { type_name: $type_name:ident, compressed_type_name: $compressed_type_name:ident, - rust_type: $rust_type:ty - $(,)? + compact_type_name: $compact_type_name:ident, + compact_list_type_name: $compact_list_type_name:ident, + rust_type: $rust_type:ty $(,)? } ),* $(,)? @@ -181,6 +296,8 @@ macro_rules! create_wrapper_type_non_native_type( { type_name: $type_name, compressed_type_name: $compressed_type_name, + compact_type_name: $compact_type_name, + compact_list_type_name: $compact_list_type_name, rust_type: $rust_type } ); @@ -192,13 +309,17 @@ create_wrapper_type_non_native_type!( { type_name: FheUint128, compressed_type_name: CompressedFheUint128, + compact_type_name: CompactFheUint128, + compact_list_type_name: CompactFheUint128List, rust_type: u128, }, { type_name: FheUint256, compressed_type_name: CompressedFheUint256, + compact_type_name: CompactFheUint256, + compact_list_type_name: CompactFheUint256List, rust_type: U256, - }, + } ); // We use this macro to define wasm wrapper for @@ -209,8 +330,9 @@ macro_rules! create_wrapper_type_that_has_native_type( { type_name: $type_name:ident, compressed_type_name: $compressed_type_name:ident, - native_type: $native_type:ty - $(,)? + compact_type_name: $compact_type_name:ident, + compact_list_type_name: $compact_list_type_name:ident, + native_type: $native_type:ty $(,)? } ) => { #[wasm_bindgen] @@ -254,6 +376,19 @@ macro_rules! create_wrapper_type_that_has_native_type( }) } + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + value: $native_type, + compact_public_key: &crate::js_on_wasm_api::high_level_api::keys::TfheCompactPublicKey, + ) -> Result<$type_name, JsError> { + catch_panic_result(|| { + crate::high_level_api::$type_name::try_encrypt(value, &compact_public_key.0) + .map($type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] pub fn decrypt( &self, @@ -316,14 +451,102 @@ macro_rules! create_wrapper_type_that_has_native_type( }) } } + + #[wasm_bindgen] + pub struct $compact_type_name(pub(crate) crate::high_level_api::$compact_type_name); + + #[wasm_bindgen] + impl $compact_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + value: $native_type, + client_key: &crate::js_on_wasm_api::high_level_api::keys::TfheCompactPublicKey, + ) -> Result<$compact_type_name, JsError> { + catch_panic_result(|| { + crate::high_level_api::$compact_type_name::try_encrypt(value, &client_key.0) + .map($compact_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn expand( + &self, + ) -> Result<$type_name, JsError> { + catch_panic(||{ + $type_name(self.0.expand()) + }) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result, JsError> { + catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) + } + + #[wasm_bindgen] + pub fn deserialize(buffer: &[u8]) -> Result<$compact_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($compact_type_name) + .map_err(into_js_error) + }) + } + } + + #[wasm_bindgen] + pub struct $compact_list_type_name(pub(crate) crate::high_level_api::$compact_list_type_name); + + #[wasm_bindgen] + impl $compact_list_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + values: Vec<$native_type>, + public_key: &crate::js_on_wasm_api::high_level_api::keys::TfheCompactPublicKey, + ) -> Result<$compact_list_type_name, JsError> { + catch_panic_result(|| { + crate::high_level_api::$compact_list_type_name::try_encrypt(&values, &public_key.0) + .map($compact_list_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn expand( + &self, + ) -> Result, JsError> { + catch_panic(||{ + self.0.expand() + .into_iter() + .map($type_name) + .map(JsValue::from) + .collect::>() + }) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result, JsError> { + catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) + } + + #[wasm_bindgen] + pub fn deserialize(buffer: &[u8]) -> Result<$compact_list_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($compact_list_type_name) + .map_err(into_js_error) + }) + } + } + }; ( $( { type_name: $type_name:ident, compressed_type_name: $compressed_type_name:ident, - native_type: $native_type:ty - $(,)? + compact_type_name: $compact_type_name:ident, + compact_list_type_name: $compact_list_type_name:ident, + native_type: $native_type:ty $(,)? } ),* $(,)? @@ -333,6 +556,8 @@ macro_rules! create_wrapper_type_that_has_native_type( { type_name: $type_name, compressed_type_name: $compressed_type_name, + compact_type_name: $compact_type_name, + compact_list_type_name: $compact_list_type_name, native_type: $native_type } ); @@ -344,21 +569,29 @@ create_wrapper_type_that_has_native_type!( { type_name: FheUint8, compressed_type_name: CompressedFheUint8, + compact_type_name: CompactFheUint8, + compact_list_type_name: CompactFheUint8List, native_type: u8, }, { type_name: FheUint16, compressed_type_name: CompressedFheUint16, + compact_type_name: CompactFheUint16, + compact_list_type_name: CompactFheUint16List, native_type: u16, }, { type_name: FheUint32, compressed_type_name: CompressedFheUint32, + compact_type_name: CompactFheUint32, + compact_list_type_name: CompactFheUint32List, native_type: u32, }, { type_name: FheUint64, compressed_type_name: CompressedFheUint64, + compact_type_name: CompactFheUint64, + compact_list_type_name: CompactFheUint64List, native_type: u64, }, ); diff --git a/tfhe/src/js_on_wasm_api/high_level_api/keys.rs b/tfhe/src/js_on_wasm_api/high_level_api/keys.rs index d612ad82b..a4019aa4e 100644 --- a/tfhe/src/js_on_wasm_api/high_level_api/keys.rs +++ b/tfhe/src/js_on_wasm_api/high_level_api/keys.rs @@ -20,6 +20,19 @@ impl TfheClientKey { catch_panic(|| Self(hlapi::ClientKey::generate(config.0.clone()))) } + #[wasm_bindgen] + pub fn generate_with_seed( + config: &TfheConfig, + seed: JsValue, + ) -> Result { + catch_panic_result(|| { + let seed = + u128::try_from(seed).map_err(|_| JsError::new("Value does not fit in a u128"))?; + let key = hlapi::ClientKey::generate_with_seed(config.0.clone(), crate::Seed(seed)); + Ok(Self(key)) + }) + } + #[wasm_bindgen] pub fn serialize(&self) -> Result, JsError> { catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) @@ -126,3 +139,58 @@ impl TfheCompressedPublicKey { }) } } + +#[wasm_bindgen] +pub struct TfheCompactPublicKey(pub(crate) hlapi::CompactPublicKey); + +#[wasm_bindgen] +impl TfheCompactPublicKey { + #[wasm_bindgen] + pub fn new(client_key: &TfheClientKey) -> Result { + catch_panic(|| Self(hlapi::CompactPublicKey::new(&client_key.0))) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result, JsError> { + catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) + } + + #[wasm_bindgen] + pub fn deserialize(buffer: &[u8]) -> Result { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map(Self) + .map_err(into_js_error) + }) + } +} + +#[wasm_bindgen] +pub struct TfheCompressedCompactPublicKey(pub(crate) hlapi::CompressedCompactPublicKey); + +#[wasm_bindgen] +impl TfheCompressedCompactPublicKey { + #[wasm_bindgen] + pub fn new(client_key: &TfheClientKey) -> Result { + catch_panic(|| Self(hlapi::CompressedCompactPublicKey::new(&client_key.0))) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result, JsError> { + catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) + } + + #[wasm_bindgen] + pub fn deserialize(buffer: &[u8]) -> Result { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map(Self) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn decompress(&self) -> Result { + catch_panic(|| TfheCompactPublicKey(self.0.clone().decompress())) + } +} diff --git a/tfhe/src/js_on_wasm_api/mod.rs b/tfhe/src/js_on_wasm_api/mod.rs index 389fb09ae..201c515d9 100644 --- a/tfhe/src/js_on_wasm_api/mod.rs +++ b/tfhe/src/js_on_wasm_api/mod.rs @@ -13,32 +13,3 @@ pub use wasm_bindgen_rayon::init_thread_pool; pub mod high_level_api; pub use high_level_api::*; - -pub(self) mod js_wasm_seeder { - use crate::core_crypto::commons::math::random::{Seed, Seeder}; - - const SEED_BYTES_COUNT: usize = 16; - - pub struct ConstantSeeder { - seed: Seed, - } - - impl ConstantSeeder { - pub fn new(seed: Seed) -> Self { - Self { seed } - } - } - - impl Seeder for ConstantSeeder { - fn seed(&mut self) -> Seed { - self.seed - } - - fn is_available() -> bool - where - Self: Sized, - { - true - } - } -} diff --git a/tfhe/src/js_on_wasm_api/shortint.rs b/tfhe/src/js_on_wasm_api/shortint.rs index 5896203dd..e350c2791 100644 --- a/tfhe/src/js_on_wasm_api/shortint.rs +++ b/tfhe/src/js_on_wasm_api/shortint.rs @@ -1,8 +1,9 @@ +use crate::core_crypto::commons::generators::DeterministicSeeder; +pub use crate::core_crypto::commons::math::random::Seed; +use crate::core_crypto::prelude::ActivatedRandomGenerator; use bincode; use wasm_bindgen::prelude::*; -use super::js_wasm_seeder; - use std::panic::set_hook; #[derive(serde::Serialize, serde::Deserialize)] @@ -72,6 +73,84 @@ impl From for crate::shortint::parameters::Encrypti } } +macro_rules! expose_predefined_parameters { + ( + $( + $param_name:ident + ),* + $(,)? + ) => { + #[wasm_bindgen] + #[allow(non_camel_case_types)] + pub enum ShortintParametersName { + $( + $param_name, + )* + } + + #[wasm_bindgen] + impl ShortintParameters { + #[wasm_bindgen(constructor)] + pub fn new(name: ShortintParametersName) -> Self { + match name { + $( + ShortintParametersName::$param_name => { + Self(crate::shortint::parameters::$param_name) + } + )* + } + } + } + } +} + +expose_predefined_parameters! { + PARAM_MESSAGE_1_CARRY_0, + PARAM_MESSAGE_1_CARRY_1, + PARAM_MESSAGE_2_CARRY_0, + PARAM_MESSAGE_1_CARRY_2, + PARAM_MESSAGE_2_CARRY_1, + PARAM_MESSAGE_3_CARRY_0, + PARAM_MESSAGE_1_CARRY_3, + PARAM_MESSAGE_2_CARRY_2, + PARAM_MESSAGE_3_CARRY_1, + PARAM_MESSAGE_4_CARRY_0, + PARAM_MESSAGE_1_CARRY_4, + PARAM_MESSAGE_2_CARRY_3, + PARAM_MESSAGE_3_CARRY_2, + PARAM_MESSAGE_4_CARRY_1, + PARAM_MESSAGE_5_CARRY_0, + PARAM_MESSAGE_1_CARRY_5, + PARAM_MESSAGE_2_CARRY_4, + PARAM_MESSAGE_3_CARRY_3, + PARAM_MESSAGE_4_CARRY_2, + PARAM_MESSAGE_5_CARRY_1, + PARAM_MESSAGE_6_CARRY_0, + PARAM_MESSAGE_1_CARRY_6, + PARAM_MESSAGE_2_CARRY_5, + PARAM_MESSAGE_3_CARRY_4, + PARAM_MESSAGE_4_CARRY_3, + PARAM_MESSAGE_5_CARRY_2, + PARAM_MESSAGE_6_CARRY_1, + PARAM_MESSAGE_7_CARRY_0, + PARAM_MESSAGE_1_CARRY_7, + PARAM_MESSAGE_2_CARRY_6, + PARAM_MESSAGE_3_CARRY_5, + PARAM_MESSAGE_4_CARRY_4, + PARAM_MESSAGE_5_CARRY_3, + PARAM_MESSAGE_6_CARRY_2, + PARAM_MESSAGE_7_CARRY_1, + PARAM_MESSAGE_8_CARRY_0, + + PARAM_SMALL_MESSAGE_1_CARRY_1, + PARAM_SMALL_MESSAGE_2_CARRY_2, + PARAM_SMALL_MESSAGE_3_CARRY_3, + PARAM_SMALL_MESSAGE_4_CARRY_4, + + PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, + PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, +} + #[wasm_bindgen] impl Shortint { #[wasm_bindgen] @@ -198,14 +277,8 @@ impl Shortint { let seed_low_bytes: u128 = seed_low_bytes.into(); let seed: u128 = (seed_high_bytes << 64) | seed_low_bytes; - let mut constant_seeder = Box::new(js_wasm_seeder::ConstantSeeder::new( - crate::core_crypto::commons::math::random::Seed(seed), - )); - - let mut tmp_shortint_engine = - crate::shortint::engine::ShortintEngine::new_from_seeder(constant_seeder.as_mut()); - - tmp_shortint_engine + let mut seeder = DeterministicSeeder::::new(Seed(seed)); + crate::shortint::engine::ShortintEngine::new_from_seeder(&mut seeder) .new_client_key(parameters.0.try_into().unwrap()) .map_err(|e| wasm_bindgen::JsError::new(format!("{e:?}").as_str())) .map(ShortintClientKey) diff --git a/tfhe/src/shortint/ciphertext/mod.rs b/tfhe/src/shortint/ciphertext/mod.rs index cb55939a4..48b12d004 100644 --- a/tfhe/src/shortint/ciphertext/mod.rs +++ b/tfhe/src/shortint/ciphertext/mod.rs @@ -359,6 +359,66 @@ impl From> for Cipher } } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompactCiphertextList { + pub ct_list: LweCompactCiphertextListOwned, + pub degree: Degree, + pub message_modulus: MessageModulus, + pub carry_modulus: CarryModulus, + pub _order_marker: PhantomData, +} + +impl CompactCiphertextList { + pub fn expand(&self) -> Vec> { + let mut output_lwe_ciphertext_list = LweCiphertextList::new( + 0u64, + self.ct_list.lwe_size(), + self.ct_list.lwe_ciphertext_count(), + self.ct_list.ciphertext_modulus(), + ); + + // No parallelism allowed + #[cfg(all(feature = "__wasm_api", not(feature = "parallel-wasm-api")))] + { + use crate::core_crypto::prelude::expand_lwe_compact_ciphertext_list; + expand_lwe_compact_ciphertext_list(&mut output_lwe_ciphertext_list, &self.ct_list); + } + + // Parallelism allowed + #[cfg(any(not(feature = "__wasm_api"), feature = "parallel-wasm-api"))] + { + use crate::core_crypto::prelude::par_expand_lwe_compact_ciphertext_list; + par_expand_lwe_compact_ciphertext_list(&mut output_lwe_ciphertext_list, &self.ct_list); + } + + output_lwe_ciphertext_list + .as_ref() + .chunks_exact(self.ct_list.lwe_size().0) + .map(|lwe_data| { + let ct = LweCiphertext::from_container( + lwe_data.to_vec(), + self.ct_list.ciphertext_modulus(), + ); + CiphertextBase { + ct, + degree: self.degree, + message_modulus: self.message_modulus, + carry_modulus: self.carry_modulus, + _order_marker: self._order_marker, + } + }) + .collect::>() + } + + pub fn size_elements(&self) -> usize { + self.ct_list.size_elements() + } + + pub fn size_bytes(&self) -> usize { + self.ct_list.size_bytes() + } +} + #[cfg(test)] mod tests { use super::{BootstrapKeyswitch, KeyswitchBootstrap}; diff --git a/tfhe/src/shortint/engine/mod.rs b/tfhe/src/shortint/engine/mod.rs index 3184180c2..fabd521ef 100644 --- a/tfhe/src/shortint/engine/mod.rs +++ b/tfhe/src/shortint/engine/mod.rs @@ -189,17 +189,17 @@ pub(crate) type EngineResult = Result; /// This structs actually implements the logics into its methods. pub struct ShortintEngine { /// A structure containing a single CSPRNG to generate secret key coefficients. - secret_generator: SecretRandomGenerator, + pub(crate) secret_generator: SecretRandomGenerator, /// A structure containing two CSPRNGs to generate material for encryption like public masks /// and secret errors. /// /// The [`EncryptionRandomGenerator`] contains two CSPRNGs, one publicly seeded used to /// generate mask coefficients and one privately seeded used to generate errors during /// encryption. - encryption_generator: EncryptionRandomGenerator, + pub(crate) encryption_generator: EncryptionRandomGenerator, /// A seeder that can be called to generate 128 bits seeds, useful to create new /// [`EncryptionRandomGenerator`] to encrypt seeded types. - seeder: DeterministicSeeder, + pub(crate) seeder: DeterministicSeeder, computation_buffers: ComputationBuffers, ciphertext_buffers: Memory, } diff --git a/tfhe/src/shortint/keycache.rs b/tfhe/src/shortint/keycache.rs index 1487fcd83..a3f970bb3 100644 --- a/tfhe/src/shortint/keycache.rs +++ b/tfhe/src/shortint/keycache.rs @@ -252,11 +252,13 @@ impl NamedParam for ShortintParameterSet { PARAM_MESSAGE_7_CARRY_0, PARAM_MESSAGE_7_CARRY_1, PARAM_MESSAGE_8_CARRY_0, + PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, // Small PARAM_SMALL_MESSAGE_1_CARRY_1, PARAM_SMALL_MESSAGE_2_CARRY_2, PARAM_SMALL_MESSAGE_3_CARRY_3, PARAM_SMALL_MESSAGE_4_CARRY_4, + PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, // MultiBit Group 2 PARAM_MULTI_BIT_MESSAGE_1_CARRY_1_GROUP_2, PARAM_MULTI_BIT_MESSAGE_2_CARRY_2_GROUP_2, diff --git a/tfhe/src/shortint/mod.rs b/tfhe/src/shortint/mod.rs index 74ed4d6ed..fe4c95257 100755 --- a/tfhe/src/shortint/mod.rs +++ b/tfhe/src/shortint/mod.rs @@ -67,6 +67,8 @@ pub use parameters::{ MultiBitPBSParameters, PBSParameters, ShortintParameterSet, WopbsParameters, }; pub use public_key::{ + CompactPublicKeyBase, CompactPublicKeyBig, CompactPublicKeySmall, + CompressedCompactPublicKeyBase, CompressedCompactPublicKeyBig, CompressedCompactPublicKeySmall, CompressedPublicKeyBase, CompressedPublicKeyBig, CompressedPublicKeySmall, PublicKeyBase, PublicKeyBig, PublicKeySmall, }; diff --git a/tfhe/src/shortint/parameters/mod.rs b/tfhe/src/shortint/parameters/mod.rs index b152210fc..12956e706 100644 --- a/tfhe/src/shortint/parameters/mod.rs +++ b/tfhe/src/shortint/parameters/mod.rs @@ -1297,6 +1297,27 @@ pub const PARAM_MULTI_BIT_MESSAGE_4_CARRY_4_GROUP_3: MultiBitPBSParameters = grouping_factor: LweBskGroupingFactor(3), }; +pub const PARAM_MESSAGE_2_CARRY_2_COMPACT_PK: ClassicPBSParameters = ClassicPBSParameters { + lwe_dimension: LweDimension(1024), + glwe_dimension: GlweDimension(1), + polynomial_size: PolynomialSize(2048), + lwe_modular_std_dev: StandardDev(0.000000049029381729), + glwe_modular_std_dev: StandardDev(0.00000000000000031528), + pbs_base_log: DecompositionBaseLog(21), + pbs_level: DecompositionLevelCount(1), + ks_level: DecompositionLevelCount(2), + ks_base_log: DecompositionBaseLog(8), + message_modulus: MessageModulus(4), + carry_modulus: CarryModulus(4), + ciphertext_modulus: CiphertextModulus::new_native(), + encryption_key_choice: EncryptionKeyChoice::Big, +}; + +pub const PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK: ClassicPBSParameters = ClassicPBSParameters { + encryption_key_choice: EncryptionKeyChoice::Small, + ..PARAM_MESSAGE_2_CARRY_2_COMPACT_PK +}; + /// Return a parameter set from a message and carry moduli. /// /// # Example diff --git a/tfhe/src/shortint/prelude.rs b/tfhe/src/shortint/prelude.rs index 24e05a3ff..0074f2595 100644 --- a/tfhe/src/shortint/prelude.rs +++ b/tfhe/src/shortint/prelude.rs @@ -19,5 +19,8 @@ pub use super::parameters::{ PARAM_MESSAGE_2_CARRY_6, PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_3_CARRY_4, PARAM_MESSAGE_3_CARRY_5, PARAM_MESSAGE_4_CARRY_4, }; -pub use super::public_key::{PublicKeyBase, PublicKeyBig, PublicKeySmall}; +pub use super::public_key::{ + CompactPublicKeyBase, CompactPublicKeyBig, CompactPublicKeySmall, PublicKeyBase, PublicKeyBig, + PublicKeySmall, +}; pub use super::server_key::ServerKey; diff --git a/tfhe/src/shortint/public_key/compact.rs b/tfhe/src/shortint/public_key/compact.rs new file mode 100644 index 000000000..2d0732919 --- /dev/null +++ b/tfhe/src/shortint/public_key/compact.rs @@ -0,0 +1,255 @@ +use serde::{Deserialize, Serialize}; + +use crate::core_crypto::prelude::{ + allocate_and_generate_new_seeded_lwe_compact_public_key, generate_lwe_compact_public_key, + LweCiphertextCount, LweCiphertextOwned, LweCompactCiphertextListOwned, + LweCompactPublicKeyOwned, Plaintext, PlaintextList, SeededLweCompactPublicKeyOwned, +}; + +use crate::core_crypto::prelude::encrypt_lwe_ciphertext_with_compact_public_key; + +use crate::shortint::ciphertext::{ + BootstrapKeyswitch, CompactCiphertextList, Degree, KeyswitchBootstrap, +}; +use crate::shortint::{CiphertextBase, ClientKey, PBSOrderMarker, ShortintParameterSet}; + +use crate::shortint::engine::ShortintEngine; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompactPublicKeyBase { + pub(crate) key: LweCompactPublicKeyOwned, + pub parameters: ShortintParameterSet, + pub _order_marker: std::marker::PhantomData, +} + +pub type CompactPublicKeyBig = CompactPublicKeyBase; +pub type CompactPublicKeySmall = CompactPublicKeyBase; + +fn to_plaintext_iterator( + message_iter: impl Iterator, + parameters: &ShortintParameterSet, +) -> impl Iterator> { + let message_modulus = parameters.message_modulus().0 as u64; + let carry_modulus = parameters.carry_modulus().0 as u64; + message_iter.map(move |message| { + //The delta is the one defined by the parameters + let delta = (1_u64 << 63) / (message_modulus * carry_modulus); + + //The input is reduced modulus the message_modulus + let m = message % message_modulus; + + let shifted_message = m * delta; + // encode the message + Plaintext(shifted_message) + }) +} + +impl CompactPublicKeyBase { + pub fn new(client_key: &ClientKey) -> CompactPublicKeyBase { + Self::try_new(client_key).expect( + "Incompatible parameters, the lwe_dimension of the secret key must be a power of two", + ) + } + + pub fn try_new(client_key: &ClientKey) -> Option> { + let parameters = client_key.parameters; + let (secret_encryption_key, encryption_noise) = match OpOrder::pbs_order() { + crate::shortint::PBSOrder::KeyswitchBootstrap => ( + &client_key.large_lwe_secret_key, + parameters.glwe_modular_std_dev(), + ), + crate::shortint::PBSOrder::BootstrapKeyswitch => ( + &client_key.small_lwe_secret_key, + parameters.lwe_modular_std_dev(), + ), + }; + + if !secret_encryption_key.lwe_dimension().0.is_power_of_two() { + return None; + } + + let mut key = LweCompactPublicKeyOwned::new( + 0u64, + secret_encryption_key.lwe_dimension(), + parameters.ciphertext_modulus(), + ); + ShortintEngine::with_thread_local_mut(|engine| { + generate_lwe_compact_public_key( + secret_encryption_key, + &mut key, + encryption_noise, + &mut engine.encryption_generator, + ); + }); + + Some(Self { + key, + parameters, + _order_marker: std::marker::PhantomData, + }) + } + + pub fn encrypt(&self, message: u64) -> CiphertextBase { + let plain = to_plaintext_iterator([message].iter().copied(), &self.parameters) + .next() + .unwrap(); + + // This allocates the required ct + let mut encrypted_ct = LweCiphertextOwned::new( + 0u64, + self.key.lwe_dimension().to_lwe_size(), + self.parameters.ciphertext_modulus(), + ); + + ShortintEngine::with_thread_local_mut(|engine| { + encrypt_lwe_ciphertext_with_compact_public_key( + &self.key, + &mut encrypted_ct, + plain, + self.parameters.glwe_modular_std_dev(), + self.parameters.lwe_modular_std_dev(), + &mut engine.secret_generator, + &mut engine.encryption_generator, + ); + }); + + let message_modulus = self.parameters.message_modulus(); + CiphertextBase { + ct: encrypted_ct, + degree: Degree(message_modulus.0 - 1), + message_modulus, + carry_modulus: self.parameters.carry_modulus(), + _order_marker: Default::default(), + } + } + + pub fn encrypt_slice(&self, messages: &[u64]) -> CompactCiphertextList { + self.encrypt_iter(messages.iter().copied()) + } + + pub fn encrypt_iter( + &self, + messages: impl Iterator, + ) -> CompactCiphertextList { + let plaintext_container = to_plaintext_iterator(messages, &self.parameters) + .map(|plaintext| plaintext.0) + .collect::>(); + + let plaintext_list = PlaintextList::from_container(plaintext_container); + let mut ct_list = LweCompactCiphertextListOwned::new( + 0u64, + self.key.lwe_dimension().to_lwe_size(), + LweCiphertextCount(plaintext_list.plaintext_count().0), + self.parameters.ciphertext_modulus(), + ); + + // No parallelism allowed + #[cfg(all(feature = "__wasm_api", not(feature = "parallel-wasm-api")))] + { + use crate::core_crypto::prelude::encrypt_lwe_compact_ciphertext_list_with_compact_public_key; + ShortintEngine::with_thread_local_mut(|engine| { + encrypt_lwe_compact_ciphertext_list_with_compact_public_key( + &self.key, + &mut ct_list, + &plaintext_list, + self.parameters.glwe_modular_std_dev(), + self.parameters.lwe_modular_std_dev(), + &mut engine.secret_generator, + &mut engine.encryption_generator, + ); + }); + } + + // Parallelism allowed + #[cfg(any(not(feature = "__wasm_api"), feature = "parallel-wasm-api"))] + { + use crate::core_crypto::prelude::par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key; + ShortintEngine::with_thread_local_mut(|engine| { + par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key( + &self.key, + &mut ct_list, + &plaintext_list, + self.parameters.glwe_modular_std_dev(), + self.parameters.lwe_modular_std_dev(), + &mut engine.secret_generator, + &mut engine.encryption_generator, + ); + }); + } + + let message_modulus = self.parameters.message_modulus(); + CompactCiphertextList { + ct_list, + degree: Degree(message_modulus.0 - 1), + message_modulus, + carry_modulus: self.parameters.carry_modulus(), + _order_marker: Default::default(), + } + } + + pub fn size_elements(&self) -> usize { + self.key.size_elements() + } + + pub fn size_bytes(&self) -> usize { + self.key.size_bytes() + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompressedCompactPublicKeyBase { + pub(crate) key: SeededLweCompactPublicKeyOwned, + pub parameters: ShortintParameterSet, + pub _order_marker: std::marker::PhantomData, +} + +pub type CompressedCompactPublicKeyBig = CompressedCompactPublicKeyBase; +pub type CompressedCompactPublicKeySmall = CompressedCompactPublicKeyBase; + +impl CompressedCompactPublicKeyBase { + pub fn new(client_key: &ClientKey) -> Self { + let parameters = client_key.parameters; + let (secret_encryption_key, encryption_noise) = match OpOrder::pbs_order() { + crate::shortint::PBSOrder::KeyswitchBootstrap => ( + &client_key.large_lwe_secret_key, + parameters.glwe_modular_std_dev(), + ), + crate::shortint::PBSOrder::BootstrapKeyswitch => ( + &client_key.small_lwe_secret_key, + parameters.lwe_modular_std_dev(), + ), + }; + + let key = ShortintEngine::with_thread_local_mut(|engine| { + allocate_and_generate_new_seeded_lwe_compact_public_key( + secret_encryption_key, + encryption_noise, + parameters.ciphertext_modulus(), + &mut engine.seeder, + ) + }); + + Self { + key, + parameters, + _order_marker: std::marker::PhantomData, + } + } + + pub fn decompress(self) -> CompactPublicKeyBase { + let decompressed_key = self.key.decompress_into_lwe_compact_public_key(); + CompactPublicKeyBase { + key: decompressed_key, + parameters: self.parameters, + _order_marker: self._order_marker, + } + } +} + +impl From> + for CompactPublicKeyBase +{ + fn from(value: CompressedCompactPublicKeyBase) -> Self { + value.decompress() + } +} diff --git a/tfhe/src/shortint/public_key/mod.rs b/tfhe/src/shortint/public_key/mod.rs index 9be04e0b3..d789c5e92 100644 --- a/tfhe/src/shortint/public_key/mod.rs +++ b/tfhe/src/shortint/public_key/mod.rs @@ -1,7 +1,12 @@ //! Module with the definition of the encryption PublicKey. +pub mod compact; pub mod compressed; pub mod standard; +pub use compact::{ + CompactPublicKeyBase, CompactPublicKeyBig, CompactPublicKeySmall, + CompressedCompactPublicKeyBase, CompressedCompactPublicKeyBig, CompressedCompactPublicKeySmall, +}; pub use compressed::{CompressedPublicKeyBase, CompressedPublicKeyBig, CompressedPublicKeySmall}; pub use standard::{PublicKeyBase, PublicKeyBig, PublicKeySmall}; diff --git a/tfhe/src/shortint/server_key/mod.rs b/tfhe/src/shortint/server_key/mod.rs index 391881170..31e8401d6 100644 --- a/tfhe/src/shortint/server_key/mod.rs +++ b/tfhe/src/shortint/server_key/mod.rs @@ -911,7 +911,7 @@ impl ServerKey { } pub fn key_switching_key_size_bytes(&self) -> usize { - self.key_switching_key_size_elements() * std::mem::size_of::() + std::mem::size_of_val(self.key_switching_key.as_ref()) } } diff --git a/tfhe/src/shortint/server_key/tests.rs b/tfhe/src/shortint/server_key/tests.rs index e075b81b7..ff5bf879b 100644 --- a/tfhe/src/shortint/server_key/tests.rs +++ b/tfhe/src/shortint/server_key/tests.rs @@ -2879,3 +2879,133 @@ where println!("(msg_true - msg_false) * control_bit + msg_false = {clear_mux}, res = {dec_res}"); assert_eq!(clear_mux, dec_res); } + +#[test] +fn test_shortint_compact_public_key_smart_add_big() { + test_shortint_compact_public_key_base_smart_add::< + crate::shortint::ciphertext::KeyswitchBootstrap, + >(PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); +} + +#[test] +fn test_shortint_compact_public_key_smart_add_small() { + test_shortint_compact_public_key_base_smart_add::< + crate::shortint::ciphertext::BootstrapKeyswitch, + >(PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); +} + +fn test_shortint_compact_public_key_base_smart_add( + params: ClassicPBSParameters, +) { + let (cks, sks) = crate::shortint::gen_keys(params); + let pk = crate::shortint::CompactPublicKeyBase::::new(&cks); + + let mut rng = rand::thread_rng(); + + let modulus = cks.parameters.message_modulus().0 as u64; + + for _ in 0..NB_TEST { + let clear_0 = rng.gen::() % modulus; + + let clear_1 = rng.gen::() % modulus; + + let mut ctxt_0 = pk.encrypt(clear_0); + + let ctxt_1 = pk.encrypt(clear_1); + + let d = cks.decrypt(&ctxt_0); + assert_eq!(d, clear_0); + let d = cks.decrypt(&ctxt_1); + assert_eq!(d, clear_1); + + let mut ct_res = sks.unchecked_add(&ctxt_0, &ctxt_1); + let mut clear = clear_0 + clear_1; + let d = cks.decrypt(&ct_res); + assert_eq!(d, clear % modulus); + + //add multiple times to raise the degree and test the smart operation + for _ in 0..40 { + sks.smart_add_assign(&mut ct_res, &mut ctxt_0); + clear += clear_0; + + // decryption of ct_res + let dec_res = cks.decrypt(&ct_res); + + // assert + assert_eq!(clear % modulus, dec_res); + } + } +} + +#[test] +fn test_shortint_compact_public_key_list_smart_sub_big() { + test_shortint_compact_public_key_base_list_smart_sub::< + crate::shortint::ciphertext::KeyswitchBootstrap, + >(PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); +} + +#[test] +fn test_shortint_compact_public_key_list_smart_sub_small() { + test_shortint_compact_public_key_base_list_smart_sub::< + crate::shortint::ciphertext::BootstrapKeyswitch, + >(PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); +} + +fn test_shortint_compact_public_key_base_list_smart_sub< + OpOrder: crate::shortint::PBSOrderMarker, +>( + params: ClassicPBSParameters, +) { + let (cks, sks) = crate::shortint::gen_keys(params); + let pk = crate::shortint::CompactPublicKeyBase::::new(&cks); + + let mut rng = rand::thread_rng(); + + let modulus = cks.parameters.message_modulus().0 as u64; + + const MAX_CT: usize = 5; + + let mut first_clear_vec = Vec::with_capacity(MAX_CT); + let mut second_clear_vec = Vec::with_capacity(MAX_CT); + + for _ in 0..(NB_TEST / 2).min(5) { + let num_ct_for_this_iter = rng.gen_range(1..=MAX_CT); + first_clear_vec.truncate(0); + second_clear_vec.truncate(0); + for _ in 0..num_ct_for_this_iter { + let clear_0 = rng.gen::() % modulus; + let clear_1 = rng.gen::() % modulus; + + first_clear_vec.push(clear_0); + second_clear_vec.push(clear_1); + } + + let first_compact_list = pk.encrypt_slice(&first_clear_vec); + let second_compact_list = pk.encrypt_slice(&second_clear_vec); + + let mut first_expanded_vec = first_compact_list.expand(); + let mut second_expanded_vec = second_compact_list.expand(); + + // decryption check + for i in 0..num_ct_for_this_iter { + let decrypted_0 = cks.decrypt(&first_expanded_vec[i]); + let decrypted_1 = cks.decrypt(&second_expanded_vec[i]); + + assert_eq!(decrypted_0, first_clear_vec[i]); + assert_eq!(decrypted_1, second_clear_vec[i]); + } + + for _ in 0..10 { + for i in 0..num_ct_for_this_iter { + sks.smart_sub_assign(&mut first_expanded_vec[i], &mut second_expanded_vec[i]); + first_clear_vec[i] = first_clear_vec[i].wrapping_sub(second_clear_vec[i]); + + let decrypted_0 = cks.decrypt(&first_expanded_vec[i]); + let decrypted_1 = cks.decrypt(&second_expanded_vec[i]); + + assert_eq!(decrypted_0, first_clear_vec[i] % modulus); + assert_eq!(decrypted_1, second_clear_vec[i] % modulus); + } + } + } +} diff --git a/tfhe/web_wasm_parallel_tests/favicon.ico b/tfhe/web_wasm_parallel_tests/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..81001a18fce188c81b2c7d0f4319199e6a845351 GIT binary patch literal 258062 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 + + + + + + + + +
diff --git a/tfhe/web_wasm_parallel_tests/index.js b/tfhe/web_wasm_parallel_tests/index.js index 63d170126..b3f626678 100644 --- a/tfhe/web_wasm_parallel_tests/index.js +++ b/tfhe/web_wasm_parallel_tests/index.js @@ -24,7 +24,16 @@ async function setup() { ); const demos = await Comlink.wrap(worker).demos; - const demoNames = ['publicKeyTest', 'compressedPublicKeyTest'] + const demoNames = [ + 'publicKeyTest', + 'compressedPublicKeyTest', + 'compactPublicKeyTest32BitBig', + 'compactPublicKeyTest32BitSmall', + 'compactPublicKeyTest256BitBig', + 'compactPublicKeyTest256BitSmall', + 'compressedCompactPublicKeyTest256BitBig', + 'compressedCompactPublicKeyTest256BitSmall' + ] function setupBtn(id) { // Handlers are named in the same way as buttons. @@ -32,6 +41,7 @@ async function setup() { let button = document.getElementById(id); if (button === null) { + console.error("button with id: ", id , "not found") return null; } @@ -42,11 +52,12 @@ async function setup() { document.getElementById("testSuccess").checked = false setButtonsDisabledState(demoNames, true); + console.log("Running: ", id) try { await fn() document.getElementById("testSuccess").checked = true } catch (error) { - console.error(error) + console.error(`Test Failed: ${error}`) document.getElementById("testSuccess").checked = false } document.getElementById("loader").hidden = true diff --git a/tfhe/web_wasm_parallel_tests/package.json b/tfhe/web_wasm_parallel_tests/package.json index 56d8978f7..fec2c69f6 100644 --- a/tfhe/web_wasm_parallel_tests/package.json +++ b/tfhe/web_wasm_parallel_tests/package.json @@ -5,9 +5,9 @@ "main": "index.js", "scripts": { "test": "jest ./test --runInBand", - "build": "cp -r ../../tfhe/pkg ./ && webpack build ./index.js --mode production -o dist --output-filename index.js && cp index.html dist/", + "build": "cp -r ../../tfhe/pkg ./ && webpack build ./index.js --mode production -o dist --output-filename index.js && cp index.html dist/ && cp favicon.ico dist/", "server": "serve --config ../serve.json dist/", - "test-separate-processes": "jest --listTests | xargs -L 1 jest", + "test-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand", "test2": "mocha" }, "author": "", diff --git a/tfhe/web_wasm_parallel_tests/test/common.mjs b/tfhe/web_wasm_parallel_tests/test/common.mjs index f87c90b61..470b3ec5a 100644 --- a/tfhe/web_wasm_parallel_tests/test/common.mjs +++ b/tfhe/web_wasm_parallel_tests/test/common.mjs @@ -12,6 +12,7 @@ async function runActualTest(page, buttonId) { const testSuccessCheckbox = await page.waitForSelector( successCheckBoxSelector ); + await page.waitForSelector(buttonSelector) const isCheckedBefore = await testSuccessCheckbox?.evaluate(el => el.checked); expect(isCheckedBefore).toBe(false); @@ -28,7 +29,6 @@ async function runActualTest(page, buttonId) { } async function runTestAttachedToButton(buttonId) { - console.log(puppeteer.launch) let browser if (isRoot()) { browser = await puppeteer.launch({ @@ -45,7 +45,7 @@ async function runTestAttachedToButton(buttonId) { await page.goto('http://localhost:3000'); page.on('console', msg => console.log('PAGE LOG:', msg.text())); - + await page.reload({ waitUntil: ["networkidle0", "domcontentloaded"] }); let errorCaught = null; diff --git a/tfhe/web_wasm_parallel_tests/test/compact-public-key.test.js b/tfhe/web_wasm_parallel_tests/test/compact-public-key.test.js new file mode 100644 index 000000000..66d631289 --- /dev/null +++ b/tfhe/web_wasm_parallel_tests/test/compact-public-key.test.js @@ -0,0 +1,26 @@ +import { runTestAttachedToButton } from "./common.mjs"; + + +it('Compact Public Key Test Big 32 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyTest32BitBig') +}); + +it('Compact Public Key Test Small 32 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyTest32BitSmall') +}); + +it('Compact Public Key Test Small 256 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyTest256BitSmall') +}); + +it('Compact Public Key Test Big 256 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyTest256BitBig') +}); + +it('Compressed Compact Public Key Test Small 256 Bit', async () => { + await runTestAttachedToButton('compressedCompactPublicKeyTest256BitSmall') +}); + +it('Compressed Compact Public Key Test Big 256 Bit', async () => { + await runTestAttachedToButton('compressedCompactPublicKeyTest256BitBig') +}); diff --git a/tfhe/web_wasm_parallel_tests/test/public-key-small.test.js b/tfhe/web_wasm_parallel_tests/test/public-key-small.test.js deleted file mode 100644 index 2d77b5a70..000000000 --- a/tfhe/web_wasm_parallel_tests/test/public-key-small.test.js +++ /dev/null @@ -1,7 +0,0 @@ -import { runTestAttachedToButton } from "./common.mjs"; - - -it('Public Key Test', async () => { - await runTestAttachedToButton('publicKeyTest') -}); - diff --git a/tfhe/web_wasm_parallel_tests/worker.js b/tfhe/web_wasm_parallel_tests/worker.js index 90f0fc1c3..33b54f93d 100644 --- a/tfhe/web_wasm_parallel_tests/worker.js +++ b/tfhe/web_wasm_parallel_tests/worker.js @@ -3,18 +3,26 @@ import * as Comlink from 'comlink'; import init, { initThreadPool, init_panic_hook, + ShortintParametersName, + ShortintParameters, TfheClientKey, TfhePublicKey, TfheCompressedPublicKey, TfheCompressedServerKey, + TfheCompressedCompactPublicKey, + TfheCompactPublicKey, TfheConfigBuilder, CompressedFheUint8, FheUint8, FheUint32, + CompactFheUint32, + CompactFheUint32List, CompressedFheUint128, FheUint128, CompressedFheUint256, - FheUint256 + FheUint256, + CompactFheUint256, + CompactFheUint256List, } from "./pkg/tfhe.js"; function assert(cond, text){ @@ -23,6 +31,12 @@ function assert(cond, text){ throw new Error(text || "Assertion failed!"); }; +function assert_eq(a, b, text){ + if( a === b ) return; + if( console.assert.useDebugger ) debugger; + throw new Error(text || `Equality assertion failed!: ${a} != ${b}`); +}; + async function compressedPublicKeyTest() { let config = TfheConfigBuilder.all_disabled() .enable_default_integers_small() @@ -51,7 +65,7 @@ async function compressedPublicKeyTest() { console.log("Ciphertext Size", ser.length); let decrypted = encrypted.decrypt(clientKey); - assert(decrypted === 255) + assert_eq(decrypted, 255) } async function publicKeyTest() { @@ -75,9 +89,204 @@ async function publicKeyTest() { console.log("Ciphertext Size", ser.length); let decrypted = encrypted.decrypt(clientKey); - assert(decrypted === 255) + assert_eq(decrypted, 255) } +const U32_MAX = 4294967295; + +async function compactPublicKeyTest32BitOnConfig(config) { + console.time('ClientKey Gen') + let clientKey = TfheClientKey.generate(config); + console.timeEnd('ClientKey Gen') + + console.time('CompactPublicKey Gen') + let publicKey = TfheCompactPublicKey.new(clientKey); + console.timeEnd('CompactPublicKey Gen') + + let serialized_pk = publicKey.serialize(); + console.log("Serialized CompactPublicKey size: ", serialized_pk.length); + + let values = [0, 1, 2394, U32_MAX]; + + console.time('CompactFheUint32List Encrypt') + let compact_list = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey); + console.timeEnd('CompactFheUint32List Encrypt') + + { + console.time('CompactFheUint32List Expand') + let encrypted_list = compact_list.expand(); + console.timeEnd('CompactFheUint32List Expand') + + assert_eq(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert_eq(decrypted, values[i]); + } + } + + let serialized_list = compact_list.serialize(); + console.log("Serialized CompactFheUint32List size: ", serialized_list.length); + + let deserialized_list = CompactFheUint32List.deserialize(serialized_list); + let encrypted_list = deserialized_list.expand(); + assert_eq(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert_eq(decrypted, values[i]); + } +} + +async function compactPublicKeyTest32BitBig() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + await compactPublicKeyTest32BitOnConfig(config) +} + +async function compactPublicKeyTest32BitSmall() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + await compactPublicKeyTest32BitOnConfig(config) +} + +async function compactPublicKeyTest256BitOnConfig(config) { + console.time('ClientKey Gen') + let clientKey = TfheClientKey.generate(config); + console.timeEnd('ClientKey Gen') + + console.time('CompactPublicKey Gen') + let publicKey = TfheCompactPublicKey.new(clientKey); + console.timeEnd('CompactPublicKey Gen') + + let serialized_pk = publicKey.serialize(); + console.log("Serialized CompactPublicKey size: ", serialized_pk.length); + + let values = [0, 1, 2394, U32_MAX].map((e) => BigInt(e)); + + console.time('CompactFheUint256List Encrypt') + let compact_list = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey); + console.timeEnd('CompactFheUint256List Encrypt') + + { + console.time('CompactFheUint256List Expand') + let encrypted_list = compact_list.expand(); + console.timeEnd('CompactFheUint256List Expand') + + assert_eq(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert_eq(decrypted, values[i]); + } + } + + let serialized_list = compact_list.serialize(); + console.log("Serialized CompactFheUint256List size: ", serialized_list.length); + + let deserialized_list = CompactFheUint256List.deserialize(serialized_list); + let encrypted_list = deserialized_list.expand(); + assert_eq(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert_eq(decrypted, values[i]); + } +} + +async function compactPublicKeyTest256BitBig() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + await compactPublicKeyTest256BitOnConfig(config) +} + +async function compactPublicKeyTest256BitSmall() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + await compactPublicKeyTest256BitOnConfig(config) +} + + +async function compressedCompactPublicKeyTest256BitOnConfig(config) { + console.time('ClientKey Gen') + let clientKey = TfheClientKey.generate(config); + console.timeEnd('ClientKey Gen') + + console.time('CompressedCompactPublicKey Gen') + let publicKey = TfheCompressedCompactPublicKey.new(clientKey); + console.timeEnd('CompressedCompactPublicKey Gen') + + let serialized_pk = publicKey.serialize(); + console.log("Serialized CompressedCompactPublicKey size: ", serialized_pk.length); + + console.time('CompressedCompactPublicKey Decompression') + publicKey = publicKey.decompress() + console.timeEnd('CompressedCompactPublicKey Decompression') + + let values = [0, 1, 2394, U32_MAX].map((e) => BigInt(e)); + + console.time('CompactFheUint256List Encrypt') + let compact_list = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey); + console.timeEnd('CompactFheUint256List Encrypt') + + { + console.time('CompactFheUint256List Expand') + let encrypted_list = compact_list.expand(); + console.timeEnd('CompactFheUint256List Expand') + + assert_eq(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert_eq(decrypted, values[i]); + } + } + + let serialized_list = compact_list.serialize(); + console.log("Serialized CompactFheUint256List size: ", serialized_list.length); + + let deserialized_list = CompactFheUint256List.deserialize(serialized_list); + let encrypted_list = deserialized_list.expand(); + assert_eq(encrypted_list.length, values.length); + + for (let i = 0; i < values.length; i++) + { + let decrypted = encrypted_list[i].decrypt(clientKey); + assert_eq(decrypted, values[i]); + } +} + +async function compressedCompactPublicKeyTest256BitBig() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + await compressedCompactPublicKeyTest256BitOnConfig(config) +} + +async function compressedCompactPublicKeyTest256BitSmall() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + await compressedCompactPublicKeyTest256BitOnConfig(config) +} + + + async function main() { await init() await initThreadPool(navigator.hardwareConcurrency); @@ -85,7 +294,13 @@ async function main() { return Comlink.proxy({ publicKeyTest, - compressedPublicKeyTest + compressedPublicKeyTest, + compactPublicKeyTest32BitSmall, + compactPublicKeyTest32BitBig, + compactPublicKeyTest256BitSmall, + compactPublicKeyTest256BitBig, + compressedCompactPublicKeyTest256BitSmall, + compressedCompactPublicKeyTest256BitBig, }) }