diff --git a/Makefile b/Makefile index 9976c821fa..b228ee7b79 100644 --- a/Makefile +++ b/Makefile @@ -175,6 +175,10 @@ fmt_gpu: install_rs_check_toolchain cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt cd "$(TFHECUDA_SRC)" && ./format_tfhe_cuda_backend.sh +.PHONY: fmt_c_tests # Format c tests +fmt_c_tests: + find tfhe/c_api_tests/ -regex '.*\.\(cpp\|hpp\|cu\|c\|h\)' -exec clang-format -style=file -i {} \; + .PHONY: check_fmt # Check rust code format check_fmt: install_rs_check_toolchain cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt --check @@ -274,7 +278,7 @@ clippy_trivium: install_rs_check_toolchain .PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.) clippy_all_targets: RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \ - --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache \ + --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,zk-pok-experimental \ -p $(TFHE_SPEC) -- --no-deps -D warnings .PHONY: clippy_concrete_csprng # Run clippy lints on concrete-csprng @@ -353,7 +357,7 @@ symlink_c_libs_without_fingerprint: .PHONY: build_c_api # Build the C API for boolean, shortint and integer build_c_api: install_rs_check_toolchain RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ - --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,$(FORWARD_COMPAT_FEATURE) \ + --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,zk-pok-experimental,$(FORWARD_COMPAT_FEATURE) \ -p $(TFHE_SPEC) @"$(MAKE)" symlink_c_libs_without_fingerprint @@ -376,7 +380,7 @@ build_web_js_api: install_rs_build_toolchain install_wasm_pack cd tfhe && \ RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \ wasm-pack build --release --target=web \ - -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api + -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,zk-pok-experimental .PHONY: build_web_js_api_parallel # Build the js API targeting the web browser with parallelism support build_web_js_api_parallel: install_rs_check_toolchain install_wasm_pack @@ -384,7 +388,7 @@ build_web_js_api_parallel: install_rs_check_toolchain install_wasm_pack rustup component add rust-src --toolchain $(RS_CHECK_TOOLCHAIN) && \ RUSTFLAGS="$(WASM_RUSTFLAGS) -C target-feature=+atomics,+bulk-memory,+mutable-globals" rustup run $(RS_CHECK_TOOLCHAIN) \ wasm-pack build --release --target=web \ - -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,parallel-wasm-api \ + -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,parallel-wasm-api,zk-pok-experimental \ -Z build-std=panic_abort,std .PHONY: build_node_js_api # Build the js API targeting nodejs @@ -392,7 +396,7 @@ build_node_js_api: install_rs_build_toolchain install_wasm_pack cd tfhe && \ RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \ wasm-pack build --release --target=nodejs \ - -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api + -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,zk-pok-experimental .PHONY: build_concrete_csprng # Build concrete_csprng build_concrete_csprng: install_rs_build_toolchain @@ -402,10 +406,10 @@ build_concrete_csprng: install_rs_build_toolchain .PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ - --features=$(TARGET_ARCH_FEATURE),experimental -p $(TFHE_SPEC) -- core_crypto:: + --features=$(TARGET_ARCH_FEATURE),experimental,zk-pok-experimental -p $(TFHE_SPEC) -- core_crypto:: @if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \ RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ - --features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p $(TFHE_SPEC) -- core_crypto::; \ + --features=$(TARGET_ARCH_FEATURE),experimental,zk-pok-experimental,$(AVX512_FEATURE) -p $(TFHE_SPEC) -- core_crypto::; \ fi .PHONY: test_core_crypto_cov # Run the tests of the core_crypto module with code coverage @@ -576,7 +580,7 @@ test_integer_cov: install_rs_check_toolchain install_tarpaulin .PHONY: test_high_level_api # Run all the tests for high_level_api test_high_level_api: install_rs_build_toolchain RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ - --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p $(TFHE_SPEC) \ + --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache,zk-pok-experimental -p $(TFHE_SPEC) \ -- high_level_api:: test_high_level_api_gpu: install_rs_build_toolchain install_cargo_nextest @@ -633,7 +637,7 @@ test_concrete_csprng: RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ --features=$(TARGET_ARCH_FEATURE) -p concrete-csprng -.PHONY: test_zk_pok # Run tfhe-zk-pok tests +.PHONY: test_zk_pok # Run tfhe-zk-pok-experimental tests test_zk_pok: RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ -p tfhe-zk-pok diff --git a/scripts/integer-tests.sh b/scripts/integer-tests.sh index 22d15efce9..512b5990af 100755 --- a/scripts/integer-tests.sh +++ b/scripts/integer-tests.sh @@ -162,7 +162,7 @@ cargo "${RUST_TOOLCHAIN}" nextest run \ --cargo-profile "${cargo_profile}" \ --package "${tfhe_package}" \ --profile ci \ - --features="${ARCH_FEATURE}",integer,internal-keycache,"${avx512_feature}" \ + --features="${ARCH_FEATURE}",integer,internal-keycache,zk-pok-experimental,"${avx512_feature}" \ --test-threads "${test_threads}" \ -E "$filter_expression" diff --git a/scripts/shortint-tests.sh b/scripts/shortint-tests.sh index d1b7236250..1a4421882a 100755 --- a/scripts/shortint-tests.sh +++ b/scripts/shortint-tests.sh @@ -120,7 +120,7 @@ and not test(~smart_add_and_mul)""" # This test is too slow --cargo-profile "${cargo_profile}" \ --package "${tfhe_package}" \ --profile ci \ - --features="${ARCH_FEATURE}",shortint,internal-keycache \ + --features="${ARCH_FEATURE}",shortint,internal-keycache,zk-pok-experimental \ --test-threads "${n_threads_small}" \ -E "${filter_expression_small_params}" diff --git a/tfhe/Cargo.toml b/tfhe/Cargo.toml index a74d8bfd4c..258d0e737c 100644 --- a/tfhe/Cargo.toml +++ b/tfhe/Cargo.toml @@ -69,6 +69,8 @@ paste = "1.0.7" fs2 = { version = "0.4.3", optional = true } # While we wait for repeat_n in rust standard library itertools = "0.11.0" +rand_core = { version = "0.6.4", features = ["std"] } +tfhe-zk-pok = { version = "0.1.0", path = "../tfhe-zk-pok", optional = true} # wasm deps wasm-bindgen = { version = "0.2.86", features = [ @@ -87,6 +89,7 @@ shortint = [] integer = ["shortint"] internal-keycache = ["dep:lazy_static", "dep:fs2"] gpu = ["tfhe-cuda-backend"] +zk-pok-experimental = ["dep:tfhe-zk-pok"] pbs-stats = [] @@ -270,6 +273,10 @@ required-features = ["boolean", "shortint", "internal-keycache"] # Real use-case examples +[[example]] +name = "demo" +required-features = ["integer", "zk-pok-experimental"] + [[example]] name = "fhe_strings" required-features = ["integer"] diff --git a/tfhe/build.rs b/tfhe/build.rs index 54deb47e58..ae11ff3670 100644 --- a/tfhe/build.rs +++ b/tfhe/build.rs @@ -57,6 +57,8 @@ fn gen_c_api() { "integer", #[cfg(feature = "gpu")] "gpu", + #[cfg(feature = "zk-pok-experimental")] + "zk-pok-experimental", ]; let parse_expand_vec = if parse_expand_features_vec.is_empty() { diff --git a/tfhe/c_api_tests/test_high_level_zk.c b/tfhe/c_api_tests/test_high_level_zk.c new file mode 100644 index 0000000000..076e407f9b --- /dev/null +++ b/tfhe/c_api_tests/test_high_level_zk.c @@ -0,0 +1,109 @@ +#include "tfhe.h" +#include +#include +#include + +int main(void) { + // We want to use zk-proof, which requires bounded random distributions + // tfhe-rs has the `TUniform` as an available bounded distribution. + + // Note that simply changing parameters like this does not yield secure parameters + // Its only done for the example / tests + ShortintPBSParameters params = SHORTINT_PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = new_t_uniform(9); + assert(params.encryption_key_choice == ShortintEncryptionKeyChoiceBig); + + int status; + + ConfigBuilder *builder; + status = config_builder_default(&builder); + assert(status == 0); + status = config_builder_use_custom_parameters(&builder, params); + assert(status == 0); + + Config *config; + status = config_builder_build(builder, &config); + assert(status == 0); + + // Compute the CRS + // Note that we do that before generating the client key + // as client_key_generate thakes ownership of the config + CompactPkeCrs *crs; + size_t max_num_bits = 32; + status = compact_pke_crs_from_config(config, max_num_bits, &crs); + assert(status == 0); + + CompactPkePublicParams *public_params; + status = compact_pke_crs_public_params(crs, &public_params); + assert(status == 0); + + ClientKey *client_key; + status = client_key_generate(config, &client_key); + assert(status == 0); + + // zk proofs of encryption works only using the CompactPublicKey + CompactPublicKey *pk; + status = compact_public_key_new(client_key, &pk); + assert(status == 0); + + // Demo of ProvenCompactFheUint32 + { + uint32_t msg = 8328937; + ProvenCompactFheUint32 *proven_fhe_uint; + status = proven_compact_fhe_uint32_try_encrypt(msg, public_params, pk, ZkComputeLoadProof, + &proven_fhe_uint); + assert(status == 0); + + FheUint32 *fhe_uint; + // This function does not take ownership of the proven fhe uint, so we have to cleanup later + status = + proven_compact_fhe_uint32_verify_and_expand(proven_fhe_uint, public_params, pk, &fhe_uint); + assert(status == 0); + + uint32_t decrypted; + status = fhe_uint32_decrypt(fhe_uint, client_key, &decrypted); + assert(status == 0); + + assert(decrypted == msg); + fhe_uint32_destroy(fhe_uint); + proven_compact_fhe_uint32_destroy(proven_fhe_uint); + } + + // Demo of ProvenCompactFheUint32List + { + uint32_t msgs[4] = {8328937, 217521191, 2753219039, 91099540}; + ProvenCompactFheUint32List *proven_fhe_list; + status = proven_compact_fhe_uint32_list_try_encrypt(msgs, 4, public_params, pk, + ZkComputeLoadProof, &proven_fhe_list); + assert(status == 0); + + size_t list_len; + status = proven_compact_fhe_uint32_list_len(proven_fhe_list, &list_len); + assert(status == 0); + assert(list_len == 4); + + FheUint32 *fhe_uints[4]; + // This function does not take ownership of the proven fhe uint, so we have to cleanup later + status = proven_compact_fhe_uint32_list_verify_and_expand(proven_fhe_list, public_params, pk, + &fhe_uints[0], 4); + assert(status == 0); + + for (size_t i = 0; i < 4; ++i) { + uint32_t decrypted; + status = fhe_uint32_decrypt(fhe_uints[i], client_key, &decrypted); + assert(status == 0); + + assert(decrypted == msgs[i]); + fhe_uint32_destroy(fhe_uints[i]); + } + + proven_compact_fhe_uint32_list_destroy(proven_fhe_list); + } + + compact_pke_public_params_destroy(public_params); + compact_pke_crs_destroy(crs); + compact_public_key_destroy(pk); + client_key_destroy(client_key); + + return EXIT_SUCCESS; +} diff --git a/tfhe/examples/demo.rs b/tfhe/examples/demo.rs new file mode 100644 index 0000000000..ead4d59130 --- /dev/null +++ b/tfhe/examples/demo.rs @@ -0,0 +1,295 @@ +use rand::prelude::*; +use tfhe::core_crypto::commons::generators::DeterministicSeeder; +use tfhe::core_crypto::prelude::*; + +use concrete_csprng::generators::AesniRandomGenerator; +use tfhe::core_crypto::commons::math::random::RandomGenerator; +use tfhe::prelude::FheDecrypt; +use tfhe::set_server_key; +use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS; + +pub fn round_decode(decrypted: Scalar, delta: Scalar) -> Scalar { + let rounding_bit = delta >> 1; + + //compute the rounding bit + let rounding = (decrypted & rounding_bit) << 1; + + (decrypted.wrapping_add(rounding)) / delta +} + +fn main() { + main_hlapi_compact(); + main_shortint(); + main_core_crypto(); +} + +macro_rules! time { + ( + $e:expr + ) => {{ + let time_z_ = std::time::Instant::now(); + let r = $e; + print!("{:?}", time_z_.elapsed()); + r + }}; + ( + $msg:literal, $e:expr + ) => {{ + let time_z_ = std::time::Instant::now(); + let r = $e; + println!("{}: {:?}", $msg, time_z_.elapsed()); + r + }}; +} + +fn main_hlapi_compact() { + fn call_server_function( + a: tfhe::ProvenCompactFheUint64, + b: tfhe::ProvenCompactFheUint64, + server_key: tfhe::ServerKey, + public_params: CompactPkePublicParams, + public_key: &tfhe::CompactPublicKey, + ) -> tfhe::Result { + let a = time! { + "time to verify a", + a.verify_and_expand(&public_params, public_key)? + }; + let b = time! { + "time to verify b", + b.verify_and_expand(&public_params, public_key)? + }; + + set_server_key(server_key); + + Ok(a + b) + } + + let tuniform = TUniform::new(9); + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::TUniform(tuniform); + + let mut root_seeder = new_seeder(); + let mut random_generator = RandomGenerator::::new(root_seeder.seed()); + + let config = tfhe::ConfigBuilder::with_custom_parameters(params, None).build(); + println!("Start loading CRS"); + const GENERATE_CRS: bool = true; + let crs = time! { + "Loading CRS", + { + if GENERATE_CRS { + let max_bit_size = 64; + let crs = CompactPkeCrs::from_config(config, max_bit_size).unwrap(); + bincode::serialize_into(std::fs::File::create("crs.bin").unwrap(), &crs).unwrap(); + crs + } else { + bincode::deserialize_from(std::io::BufReader::new(std::fs::File::open("crs.bin").unwrap())).unwrap() + } + } + }; + println!("Done"); + + let (ck, sk, pk) = time! { + "Generating keys", + { + let ck = tfhe::ClientKey::generate(config); + let sk = tfhe::ServerKey::new(&ck); + let pk = tfhe::CompactPublicKey::try_new(&ck).unwrap(); + (ck, sk, pk) + } + }; + + let clear_a = random_generator.gen::(); + let clear_b = random_generator.gen::(); + + let a = time! { + "time to encrypt and prove a", + tfhe::ProvenCompactFheUint64::try_encrypt(clear_a, crs.public_params(), &pk, ZkComputeLoad::Verify).unwrap() + }; + let b = time! { + "time to encrypt and prove b", + tfhe::ProvenCompactFheUint64::try_encrypt(clear_b, crs.public_params(), &pk, ZkComputeLoad::Proof).unwrap() + }; + + { + let a_plus_b = call_server_function( + a.clone(), + b.clone(), + sk.clone(), + crs.public_params().clone(), + &pk, + ) + .expect("Server failed, ciphertexts did not verify"); + + let a_plus_b: u64 = a_plus_b.decrypt(&ck); + assert_eq!(a_plus_b, clear_a + clear_b); + println!("Server did accept the real proofs"); + } +} + +fn main_shortint() { + let tuniform = TUniform::new(9); + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::TUniform(tuniform); + + let max_num_message = 100; + + let (cks, _) = tfhe::shortint::gen_keys(params); + let pk = tfhe::shortint::CompactPublicKey::new(&cks); + + let mut root_seeder = new_seeder(); + + let mut deterministic_seeder = + DeterministicSeeder::::new(root_seeder.seed()); + + let mut random_generator = + RandomGenerator::::new(deterministic_seeder.seed()); + + let crs = CompactPkeCrs::from_shortint_params(params, max_num_message).unwrap(); + + let message = random_generator.gen::() % params.message_modulus.0 as u64; + + let proven_ciphertext = pk + .encrypt_and_prove(message, crs.public_params(), ZkComputeLoad::Proof) + .unwrap(); + + let decrypted = cks.decrypt(proven_ciphertext.ciphertext()); + assert_eq!(decrypted, message); + + assert!(proven_ciphertext + .verify(crs.public_params(), &pk) + .is_valid()); +} + +fn main_core_crypto() { + let mut root_seeder = new_seeder(); + + let mut deterministic_seeder = + DeterministicSeeder::::new(root_seeder.seed()); + + let mut random_generator = + RandomGenerator::::new(deterministic_seeder.seed()); + let mut secret_generator = + SecretRandomGenerator::::new(deterministic_seeder.seed()); + let mut encryption_generator = EncryptionRandomGenerator::::new( + deterministic_seeder.seed(), + &mut deterministic_seeder, + ); + + let tuniform = TUniform::new(9); + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::TUniform(tuniform); + + let mut rng = thread_rng(); + + let lwe_dim = params + .glwe_dimension + .to_equivalent_lwe_dimension(params.polynomial_size); + let noise_distrib = params.glwe_noise_distribution; + + let lwe_sk = + allocate_and_generate_new_binary_lwe_secret_key::(lwe_dim, &mut secret_generator); + + let pk = allocate_and_generate_new_lwe_compact_public_key( + &lwe_sk, + params.glwe_noise_distribution, + params.ciphertext_modulus, + &mut encryption_generator, + ); + + let plaintext_modulus = (params.message_modulus.0 * params.carry_modulus.0) as u64; + let delta = (1u64 << 63) / plaintext_modulus; + + let max_num_message = 100; + + let t = std::time::Instant::now(); + let crs = CompactPkeCrs::from_shortint_params(params, max_num_message).unwrap(); + println!("CRS done: {:?}", t.elapsed()); + + { + let message = Cleartext(rng.gen_range(0..plaintext_modulus)); + + let mut output = LweCiphertext::new(0u64, lwe_dim.to_lwe_size(), params.ciphertext_modulus); + + let proof = encrypt_and_prove_lwe_ciphertext_with_compact_public_key( + &pk, + &mut output, + message, + delta, + tuniform, + tuniform, + &mut secret_generator, + &mut encryption_generator, + &mut random_generator, + crs.public_params(), + ZkComputeLoad::Verify, + ) + .unwrap(); + { + let p = decrypt_lwe_ciphertext(&lwe_sk, &output); + let decoded = round_decode(p.0, delta); + assert_eq!(decoded, message.0); + } + + assert!(verify_lwe_ciphertext(&output, &pk, &proof, crs.public_params()).is_valid()); + } + + for _ in 0..20 { + let num_messages = rng.gen_range(1usize..=max_num_message); + println!("Num message in this batch: {num_messages}"); + + let messages = (0..num_messages) + .map(|_| rng.gen_range(0..plaintext_modulus)) + .collect::>(); + + let mut output = LweCompactCiphertextList::new( + 0u64, + lwe_dim.to_lwe_size(), + LweCiphertextCount(num_messages), + params.ciphertext_modulus, + ); + + let proof = encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( + &pk, + &mut output, + &messages, + delta, + noise_distrib, + noise_distrib, + &mut secret_generator, + &mut encryption_generator, + &mut random_generator, + crs.public_params(), + ZkComputeLoad::Verify, + ) + .unwrap(); + + { + let mut list = LweCiphertextList::new( + 0u64, + lwe_dim.to_lwe_size(), + LweCiphertextCount(num_messages), + params.ciphertext_modulus, + ); + expand_lwe_compact_ciphertext_list(&mut list, &output); + + let mut pl_list = PlaintextList::new(0u64, PlaintextCount(num_messages)); + decrypt_lwe_ciphertext_list(&lwe_sk, &list, &mut pl_list); + for (p, message) in pl_list + .into_container() + .into_iter() + .zip(messages.iter().copied()) + { + let decoded = round_decode(p, delta); + assert_eq!(decoded, message) + } + } + + let t = std::time::Instant::now(); + assert!( + verify_lwe_compact_ciphertext_list(&output, &pk, &proof, crs.public_params()) + .is_valid() + ); + println!("verify: {:?}", t.elapsed()); + } +} diff --git a/tfhe/js_on_wasm_tests/test-hlapi-unsigned.js b/tfhe/js_on_wasm_tests/test-hlapi-unsigned.js index 50cea53ee4..d74fcf81d1 100644 --- a/tfhe/js_on_wasm_tests/test-hlapi-unsigned.js +++ b/tfhe/js_on_wasm_tests/test-hlapi-unsigned.js @@ -3,6 +3,7 @@ const assert = require('node:assert').strict; const {performance} = require('perf_hooks'); const { init_panic_hook, + Shortint, ShortintParametersName, ShortintParameters, TfheClientKey, @@ -21,9 +22,15 @@ const { CompressedFheUint256, CompactFheUint256, CompactFheUint256List, + ProvenCompactFheUint64, + ProvenCompactFheUint64List, + CompactPkeCrs, + ZkComputeLoad, FheUint256 } = require("../pkg/tfhe.js"); - +const { + randomBytes, +} = require('node:crypto'); const U256_MAX = BigInt("115792089237316195423570985008687907853269984665640564039457584007913129639935"); const U128_MAX = BigInt("340282366920938463463374607431768211455"); @@ -639,3 +646,50 @@ test('hlapi_compact_public_key_encrypt_decrypt_uint256_big_list_compact', (t) => hlapi_compact_public_key_encrypt_decrypt_uint256_list_compact(config); }); + +function generateRandomBigInt(bitLength) { + const bytesNeeded = Math.ceil(bitLength / 8); + const randomBytesBuffer = randomBytes(bytesNeeded); + + // Convert random bytes to BigInt + const randomBigInt = BigInt(`0x${randomBytesBuffer.toString('hex')}`); + + return randomBigInt; +} + +test('hlapi_compact_public_key_encrypt_and_prove_compact_uint256', (t) => { + let block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK_PBS_KS); + block_params.set_lwe_noise_distribution(Shortint.try_new_t_uniform(9)); + + let config = TfheConfigBuilder.default() + .use_custom_parameters(block_params) + .build(); + + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let crs = CompactPkeCrs.from_parameters(block_params, 128); + let public_params = crs.public_params(); + + { + let input = generateRandomBigInt(64) + let encrypted = ProvenCompactFheUint64.encrypt_with_compact_public_key( + input, public_params, publicKey, ZkComputeLoad.Proof); + assert.deepStrictEqual(encrypted.verifies(public_params, publicKey), true); + let expanded = encrypted.verify_and_expand(public_params, publicKey); + let decrypted = expanded.decrypt(clientKey); + assert.deepStrictEqual(decrypted, input); + } + + { + let inputs = [generateRandomBigInt(64), generateRandomBigInt(64), generateRandomBigInt(64), generateRandomBigInt(64)]; + let encrypted = ProvenCompactFheUint64List.encrypt_with_compact_public_key( + inputs, public_params, publicKey, ZkComputeLoad.Proof); + assert.deepStrictEqual(encrypted.verifies(public_params, publicKey), true); + let expanded_list = encrypted.verify_and_expand(public_params, publicKey); + for (let i = 0; i < inputs.length; i++) { + let decrypted = expanded_list[i].decrypt(clientKey); + assert.deepStrictEqual(decrypted, inputs[i]); + } + } +}); diff --git a/tfhe/src/c_api/high_level_api/booleans.rs b/tfhe/src/c_api/high_level_api/booleans.rs index 7b3f7f2088..0174502ff5 100644 --- a/tfhe/src/c_api/high_level_api/booleans.rs +++ b/tfhe/src/c_api/high_level_api/booleans.rs @@ -139,3 +139,122 @@ pub unsafe extern "C" fn compact_fhe_bool_list_expand( } }) } + +#[cfg(feature = "zk-pok-experimental")] +mod zk { + use crate::c_api::high_level_api::utils::{ + impl_clone_on_type, impl_destroy_on_type, impl_safe_serialize_on_type, + impl_serialize_deserialize_on_type, + }; + use std::ffi::c_int; + + pub struct ProvenCompactFheBool(crate::high_level_api::ProvenCompactFheBool); + + impl_destroy_on_type!(ProvenCompactFheBool); + impl_clone_on_type!(ProvenCompactFheBool); + impl_serialize_deserialize_on_type!(ProvenCompactFheBool); + impl_safe_serialize_on_type!(ProvenCompactFheBool); + + #[no_mangle] + pub unsafe extern "C" fn proven_compact_fhe_bool_try_encrypt( + message: bool, + public_params: &crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &crate::c_api::high_level_api::keys::CompactPublicKey, + compute_load: crate::c_api::high_level_api::zk::ZkComputeLoad, + out_result: *mut *mut ProvenCompactFheBool, + ) -> c_int { + crate::c_api::utils::catch_panic(|| { + let result = crate::high_level_api::ProvenCompactFheBool::try_encrypt( + message, + &public_params.0, + &pk.0, + compute_load.into(), + ) + .unwrap(); + + *out_result = Box::into_raw(Box::new(ProvenCompactFheBool(result))); + }) + } + + #[no_mangle] + pub unsafe extern "C" fn proven_compact_fhe_bool_verify_and_expand( + ct: *const ProvenCompactFheBool, + public_params: &crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &crate::c_api::high_level_api::keys::CompactPublicKey, + out_result: *mut *mut super::FheBool, + ) -> c_int { + crate::c_api::utils::catch_panic(|| { + let ct = crate::c_api::utils::get_ref_checked(ct).unwrap(); + + let result = + ct.0.clone() + .verify_and_expand(&public_params.0, &pk.0) + .unwrap(); + + *out_result = Box::into_raw(Box::new(super::FheBool(result))); + }) + } + + pub struct ProvenCompactFheBoolList(crate::high_level_api::ProvenCompactFheBoolList); + + impl_destroy_on_type!(ProvenCompactFheBoolList); + impl_clone_on_type!(ProvenCompactFheBoolList); + impl_serialize_deserialize_on_type!(ProvenCompactFheBoolList); + impl_safe_serialize_on_type!(ProvenCompactFheBoolList); + + #[no_mangle] + pub unsafe extern "C" fn proven_compact_fhe_bool_list_try_encrypt( + input: *const bool, + input_len: usize, + public_params: &crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &crate::c_api::high_level_api::keys::CompactPublicKey, + compute_load: crate::c_api::high_level_api::zk::ZkComputeLoad, + out_result: *mut *mut ProvenCompactFheBoolList, + ) -> ::std::os::raw::c_int { + crate::c_api::utils::catch_panic(|| { + let messages = std::slice::from_raw_parts(input, input_len); + + let result = crate::high_level_api::ProvenCompactFheBoolList::try_encrypt( + messages, + &public_params.0, + &pk.0, + compute_load.into(), + ) + .unwrap(); + + *out_result = Box::into_raw(Box::new(ProvenCompactFheBoolList(result))); + }) + } + + #[no_mangle] + pub unsafe extern "C" fn proven_compact_fhe_bool_list_len( + sself: *const ProvenCompactFheBoolList, + 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 proven_compact_fhe_bool_list_verify_and_expand( + list: &ProvenCompactFheBoolList, + public_params: &crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &crate::c_api::high_level_api::keys::CompactPublicKey, + output: *mut *mut super::FheBool, + output_len: usize, + ) -> ::std::os::raw::c_int { + crate::c_api::utils::catch_panic(|| { + let expanded = list.0.verify_and_expand(&public_params.0, &pk.0).unwrap(); + + 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(super::FheBool(fhe_uint))); + } + }) + } +} diff --git a/tfhe/src/c_api/high_level_api/integers.rs b/tfhe/src/c_api/high_level_api/integers.rs index 9ddc0b9fda..5d0cef2388 100644 --- a/tfhe/src/c_api/high_level_api/integers.rs +++ b/tfhe/src/c_api/high_level_api/integers.rs @@ -388,6 +388,7 @@ macro_rules! create_integer_wrapper_type { }) } } + // The compact list version of the ciphertext type ::paste::paste! { pub struct []($crate::high_level_api::[]); @@ -434,6 +435,135 @@ macro_rules! create_integer_wrapper_type { }) } } + + + // The zk compact proven version of the compact ciphertext type + #[cfg(feature = "zk-pok-experimental")] + ::paste::paste! { + pub struct []($crate::high_level_api::[]); + + impl_destroy_on_type!([]); + + impl_clone_on_type!([]); + + impl_serialize_deserialize_on_type!([]); + + impl_safe_serialize_on_type!([]); + + #[no_mangle] + pub unsafe extern "C" fn []( + message: $clear_scalar_type, + public_params: &$crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &$crate::c_api::high_level_api::keys::CompactPublicKey, + compute_load: $crate::c_api::high_level_api::zk::ZkComputeLoad, + out_result: *mut *mut [], + ) -> c_int { + $crate::c_api::utils::catch_panic(|| { + let message = <$clear_scalar_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(message); + + let result = $crate::high_level_api::[]::try_encrypt( + message, + &public_params.0, + &pk.0, + compute_load.into() + ).unwrap(); + + *out_result = Box::into_raw(Box::new([](result))); + }) + } + + #[no_mangle] + pub unsafe extern "C" fn []( + ct: *const [], + public_params: &$crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &$crate::c_api::high_level_api::keys::CompactPublicKey, + out_result: *mut *mut $name, + ) -> c_int { + $crate::c_api::utils::catch_panic(|| { + let ct = $crate::c_api::utils::get_ref_checked(ct).unwrap(); + + let result = ct.0.clone().verify_and_expand(&public_params.0, &pk.0).unwrap(); + + *out_result = Box::into_raw(Box::new($name(result))); + }) + } + } + + // The zk compact proven version of the compact ciphertext list type + #[cfg(feature = "zk-pok-experimental")] + ::paste::paste! { + pub struct []($crate::high_level_api::[]); + + impl_destroy_on_type!([]); + + impl_clone_on_type!([]); + + impl_serialize_deserialize_on_type!([]); + + impl_safe_serialize_on_type!([]); + + + #[no_mangle] + pub unsafe extern "C" fn []( + input: *const $clear_scalar_type, + input_len: usize, + public_params: &$crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &$crate::c_api::high_level_api::keys::CompactPublicKey, + compute_load: $crate::c_api::high_level_api::zk::ZkComputeLoad, + out_result: *mut *mut [], + ) -> ::std::os::raw::c_int { + $crate::c_api::utils::catch_panic(|| { + let messages = std::slice::from_raw_parts(input, input_len) + .iter() + .copied() + .map(|value| { + <$clear_scalar_type as $crate::c_api::high_level_api::utils::CApiIntegerType>::to_rust(value) + }) + .collect::>(); + + let result = $crate::high_level_api::[]::try_encrypt( + &messages, + &public_params.0, + &pk.0, + compute_load.into() + ).unwrap(); + + *out_result = Box::into_raw(Box::new([](result))); + }) + } + + #[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 []( + list: &[], + public_params: &$crate::c_api::high_level_api::zk::CompactPkePublicParams, + pk: &$crate::c_api::high_level_api::keys::CompactPublicKey, + output: *mut *mut $name, + output_len: usize + ) -> ::std::os::raw::c_int { + $crate::c_api::utils::catch_panic(|| { + let expanded = list.0.verify_and_expand(&public_params.0, &pk.0).unwrap(); + + 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))); + } + }) + } + } }; // This entry point is meant for unsigned types @@ -803,7 +933,6 @@ macro_rules! impl_oprf_for_int { crate::high_level_api::SignedRandomizationSpec::FullSigned, ); *out_result = Box::into_raw(Box::new($name(result))); - }) } } diff --git a/tfhe/src/c_api/high_level_api/mod.rs b/tfhe/src/c_api/high_level_api/mod.rs index b90e95825f..a6780de192 100644 --- a/tfhe/src/c_api/high_level_api/mod.rs +++ b/tfhe/src/c_api/high_level_api/mod.rs @@ -9,3 +9,5 @@ mod threading; pub mod u128; pub mod u256; mod utils; +#[cfg(feature = "zk-pok-experimental")] +mod zk; diff --git a/tfhe/src/c_api/high_level_api/zk.rs b/tfhe/src/c_api/high_level_api/zk.rs new file mode 100644 index 0000000000..37d02d06ba --- /dev/null +++ b/tfhe/src/c_api/high_level_api/zk.rs @@ -0,0 +1,59 @@ +use super::utils::*; +use crate::c_api::high_level_api::config::Config; +use crate::c_api::utils::get_ref_checked; +use std::ffi::c_int; + +#[repr(C)] +#[derive(Copy, Clone)] +pub enum ZkComputeLoad { + ZkComputeLoadProof, + ZkComputeLoadVerify, +} + +impl From for crate::zk::ZkComputeLoad { + fn from(value: ZkComputeLoad) -> Self { + match value { + ZkComputeLoad::ZkComputeLoadProof => Self::Proof, + ZkComputeLoad::ZkComputeLoadVerify => Self::Verify, + } + } +} + +pub struct CompactPkePublicParams(pub(crate) crate::core_crypto::entities::CompactPkePublicParams); +impl_destroy_on_type!(CompactPkePublicParams); +impl_serialize_deserialize_on_type!(CompactPkePublicParams); + +pub struct CompactPkeCrs(pub(crate) crate::core_crypto::entities::CompactPkeCrs); + +impl_destroy_on_type!(CompactPkeCrs); +impl_serialize_deserialize_on_type!(CompactPkeCrs); + +#[no_mangle] +pub unsafe extern "C" fn compact_pke_crs_from_config( + config: *const Config, + max_num_bits: usize, + out_result: *mut *mut CompactPkeCrs, +) -> c_int { + crate::c_api::utils::catch_panic(|| { + let config = get_ref_checked(config).unwrap(); + + let crs = crate::core_crypto::entities::CompactPkeCrs::from_config(config.0, max_num_bits) + .unwrap(); + + *out_result = Box::into_raw(Box::new(CompactPkeCrs(crs))); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn compact_pke_crs_public_params( + crs: *const CompactPkeCrs, + out_public_params: *mut *mut CompactPkePublicParams, +) -> c_int { + crate::c_api::utils::catch_panic(|| { + let crs = get_ref_checked(crs).unwrap(); + + *out_public_params = Box::into_raw(Box::new(CompactPkePublicParams( + crs.0.public_params().clone(), + ))); + }) +} diff --git a/tfhe/src/core_crypto/algorithms/lwe_encryption.rs b/tfhe/src/core_crypto/algorithms/lwe_encryption.rs index c1b5387567..1d700e9607 100644 --- a/tfhe/src/core_crypto/algorithms/lwe_encryption.rs +++ b/tfhe/src/core_crypto/algorithms/lwe_encryption.rs @@ -5,6 +5,8 @@ use crate::core_crypto::algorithms::slice_algorithms::*; use crate::core_crypto::algorithms::*; use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulusKind; use crate::core_crypto::commons::generators::{EncryptionRandomGenerator, SecretRandomGenerator}; +#[cfg(feature = "zk-pok-experimental")] +use crate::core_crypto::commons::math::random::BoundedDistribution; use crate::core_crypto::commons::math::random::{ ActivatedRandomGenerator, Distribution, RandomGenerable, RandomGenerator, Uniform, UniformBinary, @@ -13,6 +15,28 @@ use crate::core_crypto::commons::parameters::*; use crate::core_crypto::commons::traits::*; use crate::core_crypto::entities::*; use rayon::prelude::*; +#[cfg(feature = "zk-pok-experimental")] +use tfhe_zk_pok::proofs::pke::{commit, prove}; +fn polymul_rev(a: &[Scalar], b: &[Scalar]) -> Vec +where + Scalar: UnsignedInteger, +{ + assert_eq!(a.len(), b.len()); + let d = a.len(); + let mut c = vec![Scalar::ZERO; d]; + + for i in 0..d { + for j in 0..d { + if i + j < d { + c[i + j] = c[i + j].wrapping_add(a[i].wrapping_mul(b[d - j - 1])); + } else { + c[i + j - d] = c[i + j - d].wrapping_sub(a[i].wrapping_mul(b[d - j - 1])); + } + } + } + + c +} /// Convenience function to share the core logic of the LWE encryption between all functions needing /// it. @@ -1683,6 +1707,202 @@ where seeded_ct } +/// This struct stores random vectors that were generated during +/// the encryption of a lwe ciphertext or lwe compact ciphertext list. +/// +/// These are needed by the zero-knowledge proof +struct CompactPublicKeyRandomVectors { + // This is 'r' + #[cfg_attr(not(feature = "zk-pok-experimental"), allow(unused))] + binary_random_vector: Vec, + // This is e1 + #[cfg_attr(not(feature = "zk-pok-experimental"), allow(unused))] + mask_noise: Vec, + // This is e2 + #[cfg_attr(not(feature = "zk-pok-experimental"), allow(unused))] + body_noise: Vec, +} + +#[cfg(feature = "zk-pok-experimental")] +fn verify_zero_knowledge_preconditions( + lwe_compact_public_key: &LweCompactPublicKey, + ciphertext_count: LweCiphertextCount, + ciphertext_modulus: CiphertextModulus, + delta: Scalar, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: BodyDistribution, + public_params: &CompactPkePublicParams, +) -> crate::Result<()> +where + Scalar: UnsignedInteger + CastFrom, + Scalar::Signed: CastFrom, + i64: CastFrom, + u64: CastFrom + CastInto, + MaskDistribution: BoundedDistribution, + BodyDistribution: BoundedDistribution, + KeyCont: Container, +{ + let exclusive_max = public_params.exclusive_max_noise(); + if Scalar::BITS < 64 && (1u64 << Scalar::BITS) >= exclusive_max { + return Err( + "The given random distribution would create random values out \ + of the expected bounds of given to the CRS" + .into(), + ); + } + + if mask_noise_distribution.contains(exclusive_max) { + // The proof expect noise bound between [-b, b) (aka -b..b) + return Err( + "The given random distribution would create random values out \ + of the expected bounds of given to the CRS" + .into(), + ); + } + if body_noise_distribution.contains(exclusive_max) { + // The proof expect noise bound between [-b, b) (aka -b..b) + return Err( + "The given random distribution would create random values out \ + of the expected bounds of given to the CRS" + .into(), + ); + } + + if !ciphertext_modulus.is_native_modulus() { + return Err("This operation only supports native modulus".into()); + } + + if Scalar::BITS > 64 { + return Err("Zero knowledge proof do not support moduli greater than 2**64".into()); + } + + let expected_q = if Scalar::BITS == 64 { + 0u64 + } else { + 164 << Scalar::BITS + }; + + if expected_q != public_params.q { + return Err("Mismatched modulus between CRS and ciphertexts".into()); + } + + if ciphertext_count.0 > public_params.k { + return Err(format!( + "CRS allows at most {} ciphertexts to be proven at once, {} contained in the list", + public_params.k, ciphertext_count.0 + ) + .into()); + } + + if lwe_compact_public_key.lwe_dimension().0 > public_params.d { + return Err(format!( + "CRS allows a LweDimension of at most {}, current dimension: {}", + public_params.d, + lwe_compact_public_key.lwe_dimension().0 + ) + .into()); + } + + // 2**64 /delta == ((2**63) / delta) *2 + let plaintext_modulus = ((1u64 << (u64::BITS - 1) as usize) / u64::cast_from(delta)) * 2; + if plaintext_modulus != public_params.t { + return Err(format!( + "Mismatched plaintext modulus: CRS expects {}, requested modulus: {plaintext_modulus:?}", + public_params.t + ).into()); + } + + Ok(()) +} + +fn encrypt_lwe_ciphertext_with_compact_public_key_impl< + Scalar, + KeyCont, + OutputCont, + MaskDistribution, + NoiseDistribution, + SecretGen, + EncryptionGen, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCiphertext, + encoded: Plaintext, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: NoiseDistribution, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, +) -> CompactPublicKeyRandomVectors +where + Scalar: Encryptable + RandomGenerable, + KeyCont: Container, + OutputCont: ContainerMut, + MaskDistribution: Distribution, + NoiseDistribution: Distribution, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, +{ + assert!( + output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(), + "Mismatch between LweDimension of output ciphertext 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 ciphertext 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 mask_noise = vec![Scalar::ZERO; lwe_compact_public_key.lwe_dimension().0]; + encryption_generator + .fill_slice_with_random_noise_from_distribution(&mut mask_noise, mask_noise_distribution); + + let body_noise = vec![Scalar::ZERO; 1]; + encryption_generator + .fill_slice_with_random_noise_from_distribution(&mut mask_noise, body_noise_distribution); + + { + 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 + slice_wrapping_add_assign(ct_mask.as_mut(), mask_noise.as_slice()); + } + + { + *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(body_noise[0]); + *ct_body.data = (*ct_body.data).wrapping_add(encoded.0); + } + } + + CompactPublicKeyRandomVectors { + binary_random_vector, + mask_noise, + body_noise, + } +} + /// 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. @@ -1775,71 +1995,38 @@ pub fn encrypt_lwe_ciphertext_with_compact_public_key< SecretGen: ByteRandomGenerator, EncryptionGen: ByteRandomGenerator, { - assert!( - output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(), - "Mismatch between LweDimension of output ciphertext 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 ciphertext 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_integer_slice_wrapping_add_random_noise_from_distribution_assign( - ct_mask.as_mut(), + let _ = encrypt_lwe_ciphertext_with_compact_public_key_impl( + lwe_compact_public_key, + output, + encoded, mask_noise_distribution, + body_noise_distribution, + secret_generator, + encryption_generator, ); - - *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_from_distribution(body_noise_distribution)); - *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. +/// Encrypt an input cleartext 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 /// /// ```rust +/// use tfhe::core_crypto::commons::math::random::RandomGenerator; /// 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_noise_distribution = -/// Gaussian::from_dispersion_parameter(StandardDev(0.00000000000000029403601535432533), 0.0); +/// let glwe_noise_distribution = TUniform::new(9); /// let ciphertext_modulus = CiphertextModulus::new_native(); +/// let delta_log = 60; +/// let delta = 1u64 << delta_log; +/// let plaintext_modulus = 1u64 << (64 - delta_log); /// /// // Create the PRNG /// let mut seeder = new_seeder(); @@ -1848,6 +2035,7 @@ pub fn encrypt_lwe_ciphertext_with_compact_public_key< /// EncryptionRandomGenerator::::new(seeder.seed(), seeder); /// let mut secret_generator = /// SecretRandomGenerator::::new(seeder.seed()); +/// let mut random_generator = RandomGenerator::::new(seeder.seed()); /// /// // Create the LweSecretKey /// let lwe_secret_key = @@ -1860,61 +2048,178 @@ pub fn encrypt_lwe_ciphertext_with_compact_public_key< /// &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, +/// let crs = CompactPkeCrs::new( +/// lwe_dimension, +/// 1, +/// glwe_noise_distribution, /// ciphertext_modulus, -/// ); +/// plaintext_modulus, +/// &mut random_generator, +/// ) +/// .unwrap(); /// -/// encrypt_lwe_compact_ciphertext_list_with_compact_public_key( +/// // Create the plaintext +/// let msg = Cleartext(3u64); +/// +/// // Create a new LweCiphertext +/// let mut lwe = LweCiphertext::new(0u64, lwe_dimension.to_lwe_size(), ciphertext_modulus); +/// +/// let proof = encrypt_and_prove_lwe_ciphertext_with_compact_public_key( /// &lwe_compact_public_key, -/// &mut output_compact_ct_list, -/// &input_plaintext_list, +/// &mut lwe, +/// msg, +/// delta, /// glwe_noise_distribution, /// glwe_noise_distribution, /// &mut secret_generator, /// &mut encryption_generator, +/// &mut random_generator, +/// crs.public_params(), +/// ZkComputeLoad::Proof, +/// ) +/// .unwrap(); +/// +/// // verify the ciphertext list with the proof +/// assert!( +/// verify_lwe_ciphertext(&lwe, &lwe_compact_public_key, &proof, crs.public_params(),) +/// .is_valid() /// ); /// -/// 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(); +/// let decrypted_plaintext = decrypt_lwe_ciphertext(&lwe_secret_key, &lwe); /// -/// decrypt_lwe_ciphertext_list( -/// &lwe_secret_key, -/// &lwe_ciphertext_list, -/// &mut output_plaintext_list, -/// ); +/// // Round and remove encoding +/// // First create a decomposer working on the high 4 bits corresponding to our encoding. +/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); /// -/// let signed_decomposer = -/// SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1)); +/// let rounded = decomposer.closest_representable(decrypted_plaintext.0); /// -/// // Round the plaintexts -/// output_plaintext_list -/// .iter_mut() -/// .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0)); +/// // Remove the encoding +/// let cleartext = rounded >> 60; /// -/// // Check we recovered the original messages -/// assert_eq!(input_plaintext_list, output_plaintext_list); +/// // Check we recovered the original message +/// assert_eq!(cleartext, msg.0); /// ``` -pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< +#[cfg(feature = "zk-pok-experimental")] +#[allow(clippy::too_many_arguments)] +pub fn encrypt_and_prove_lwe_ciphertext_with_compact_public_key< Scalar, + KeyCont, + OutputCont, MaskDistribution, NoiseDistribution, + SecretGen, + EncryptionGen, + G, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCiphertext, + message: Cleartext, + delta: Scalar, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: NoiseDistribution, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, + random_generator: &mut RandomGenerator, + public_params: &CompactPkePublicParams, + load: ZkComputeLoad, +) -> crate::Result +where + Scalar: Encryptable + + RandomGenerable + + CastFrom, + Scalar::Signed: CastFrom, + i64: CastFrom, + u64: CastFrom + CastInto, + KeyCont: Container, + OutputCont: ContainerMut, + MaskDistribution: BoundedDistribution, + NoiseDistribution: BoundedDistribution, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, + G: ByteRandomGenerator, +{ + verify_zero_knowledge_preconditions( + lwe_compact_public_key, + LweCiphertextCount(1), + output.ciphertext_modulus(), + delta, + mask_noise_distribution, + body_noise_distribution, + public_params, + )?; + + let CompactPublicKeyRandomVectors { + binary_random_vector, + mask_noise, + body_noise, + } = encrypt_lwe_ciphertext_with_compact_public_key_impl( + lwe_compact_public_key, + output, + Plaintext(message.0 * delta), + mask_noise_distribution, + body_noise_distribution, + secret_generator, + encryption_generator, + ); + + let (c1, c2) = output.get_mask_and_body(); + + let (public_commit, private_commit) = commit( + lwe_compact_public_key + .get_mask() + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + lwe_compact_public_key + .get_body() + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + c1.as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + vec![i64::cast_from(*c2.data)], + binary_random_vector + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + mask_noise + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + vec![i64::cast_from(message.0)], + body_noise + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + public_params, + random_generator, + ); + + Ok(prove( + (public_params, &public_commit), + &private_commit, + load, + random_generator, + )) +} + +fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key_impl< + Scalar, KeyCont, InputCont, OutputCont, + MaskDistribution, + NoiseDistribution, SecretGen, EncryptionGen, >( @@ -1925,13 +2230,14 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< body_noise_distribution: NoiseDistribution, secret_generator: &mut SecretRandomGenerator, encryption_generator: &mut EncryptionRandomGenerator, -) where +) -> CompactPublicKeyRandomVectors +where Scalar: Encryptable + RandomGenerable, - MaskDistribution: Distribution, - NoiseDistribution: Distribution, KeyCont: Container, InputCont: Container, OutputCont: ContainerMut, + MaskDistribution: Distribution, + NoiseDistribution: Distribution, SecretGen: ByteRandomGenerator, EncryptionGen: ByteRandomGenerator, { @@ -1964,22 +2270,21 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< "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 output_mask_list, mut output_body_list) = output.get_mut_mask_and_body_list(); 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 mut mask_noise = vec![Scalar::ZERO; output_mask_list.lwe_mask_list_size()]; + encryption_generator + .fill_slice_with_random_noise_from_distribution(&mut mask_noise, mask_noise_distribution); - 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"); + let mut body_noise = vec![Scalar::ZERO; encoded.plaintext_count().0]; + encryption_generator + .fill_slice_with_random_noise_from_distribution(&mut body_noise, body_noise_distribution); - // Loop over the ciphertext "bins" + let max_ciphertext_per_bin = lwe_compact_public_key.lwe_dimension().0; output_mask_list .iter_mut() .zip( @@ -1987,44 +2292,51 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< .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), + .zip(mask_noise.as_slice().chunks(max_ciphertext_per_bin)) + .zip(body_noise.as_slice().chunks(max_ciphertext_per_bin)), ) .for_each( |( mut output_mask, ( - ((mut output_body_chunk, input_plaintext_chunk), binary_random_slice), - mut loop_generator, + ( + ((mut output_body_chunk, input_plaintext_chunk), binary_random_slice), + mask_noise, + ), + body_noise, ), )| { - // 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, - ); + // 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; max_ciphertext_per_bin]; - // 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, + 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_integer_slice_wrapping_add_random_noise_from_distribution_assign( - output_mask.as_mut(), - mask_noise_distribution, - ); + let zk_body_convolved = polymul_rev(pk_body.as_ref(), binary_random_slice); - // Fill the body chunk afterwards manually as it most likely will be smaller than - // the full convolution result. rev(b convolved r) + Delta * m + e2 - // taking noise from Chi_2 for the body part of the encryption - // The reverse is to make the first element product match the single ciphertext case + assert_eq!(&pk_body_convolved, &zk_body_convolved); + + slice_wrapping_add_assign(output_mask.as_mut(), mask_noise); + + // Fill the body chunk afterward 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( @@ -2033,22 +2345,23 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< .rev() .zip(input_plaintext_chunk.iter()), ) - .for_each(|(dst, (&src, plaintext))| { - *dst.data = src - .wrapping_add( - loop_generator - .random_noise_from_distribution(body_noise_distribution), - ) - .wrapping_add(*plaintext.0); + .zip(body_noise) + .for_each(|((dst, (&src, plaintext)), body_noise)| { + *dst.data = src.wrapping_add(*body_noise).wrapping_add(*plaintext.0); }); }, ); + CompactPublicKeyRandomVectors { + binary_random_vector, + mask_noise, + body_noise, + } } -/// 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. +/// 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 /// @@ -2099,7 +2412,7 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< /// ciphertext_modulus, /// ); /// -/// par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key( +/// encrypt_lwe_compact_ciphertext_list_with_compact_public_key( /// &lwe_compact_public_key, /// &mut output_compact_ct_list, /// &input_plaintext_list, @@ -2112,7 +2425,7 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< /// 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(); +/// let lwe_ciphertext_list = output_compact_ct_list.expand_into_lwe_ciphertext_list(); /// /// decrypt_lwe_ciphertext_list( /// &lwe_secret_key, @@ -2131,7 +2444,7 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< /// // 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< +pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< Scalar, MaskDistribution, NoiseDistribution, @@ -2149,17 +2462,303 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< secret_generator: &mut SecretRandomGenerator, encryption_generator: &mut EncryptionRandomGenerator, ) where + Scalar: Encryptable + RandomGenerable, + MaskDistribution: Distribution, + NoiseDistribution: Distribution, + KeyCont: Container, + InputCont: Container, + OutputCont: ContainerMut, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, +{ + let _ = encrypt_lwe_compact_ciphertext_list_with_compact_public_key_impl( + lwe_compact_public_key, + output, + encoded, + mask_noise_distribution, + body_noise_distribution, + secret_generator, + encryption_generator, + ); +} + +/// Encrypt and generates a zero-knowledge proof of an input cleartext 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. +/// +/// - The input cleartext list must have a length smaller or equal the maximum number of message +/// authorized by the CRS. +/// +/// - The noise distributions must be bounded +/// +/// +/// # Example +/// +/// ```rust +/// use tfhe::core_crypto::commons::math::random::RandomGenerator; +/// 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(4); +/// let glwe_noise_distribution = TUniform::new(9); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// let delta_log = 60; +/// let delta = 1u64 << delta_log; +/// let plaintext_modulus = 1u64 << (64 - delta_log); +/// +/// // 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()); +/// let mut random_generator = RandomGenerator::::new(seeder.seed()); +/// +/// let crs = CompactPkeCrs::new( +/// lwe_dimension, +/// lwe_ciphertext_count.0, +/// glwe_noise_distribution, +/// ciphertext_modulus, +/// plaintext_modulus, +/// &mut random_generator, +/// ) +/// .unwrap(); +/// +/// // 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_noise_distribution, +/// ciphertext_modulus, +/// &mut encryption_generator, +/// ); +/// +/// let cleartexts = (0..lwe_ciphertext_count.0 as u64).collect::>(); +/// +/// // Create a new LweCompactCiphertextList +/// let mut output_compact_ct_list = LweCompactCiphertextList::new( +/// 0u64, +/// lwe_dimension.to_lwe_size(), +/// lwe_ciphertext_count, +/// ciphertext_modulus, +/// ); +/// +/// let proof = encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( +/// &lwe_compact_public_key, +/// &mut output_compact_ct_list, +/// &cleartexts, +/// delta, +/// glwe_noise_distribution, +/// glwe_noise_distribution, +/// &mut secret_generator, +/// &mut encryption_generator, +/// &mut random_generator, +/// crs.public_params(), +/// ZkComputeLoad::Proof, +/// ) +/// .unwrap(); +/// +/// // verify the ciphertext list with the proof +/// assert!(verify_lwe_compact_ciphertext_list( +/// &output_compact_ct_list, +/// &lwe_compact_public_key, +/// &proof, +/// crs.public_params(), +/// ) +/// .is_valid()); +/// +/// let mut output_plaintext_list = +/// PlaintextList::new(0u64, PlaintextCount(lwe_ciphertext_count.0)); +/// +/// 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) >> 60); +/// +/// // Check we recovered the original messages +/// assert_eq!(&cleartexts, output_plaintext_list.as_ref()); +/// ``` +#[cfg(feature = "zk-pok-experimental")] +#[allow(clippy::too_many_arguments)] +pub fn encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key< + Scalar, + KeyCont, + InputCont, + OutputCont, + MaskDistribution, + NoiseDistribution, + SecretGen, + EncryptionGen, + G, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCompactCiphertextList, + messages: &InputCont, + delta: Scalar, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: NoiseDistribution, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, + random_generator: &mut RandomGenerator, + public_params: &CompactPkePublicParams, + load: ZkComputeLoad, +) -> crate::Result +where Scalar: Encryptable + RandomGenerable - + Sync - + Send, - MaskDistribution: Distribution + Sync, - NoiseDistribution: Distribution + Sync, + + CastFrom, + Scalar::Signed: CastFrom, + i64: CastFrom, + u64: CastFrom + CastInto, + MaskDistribution: BoundedDistribution, + NoiseDistribution: BoundedDistribution, KeyCont: Container, InputCont: Container, OutputCont: ContainerMut, SecretGen: ByteRandomGenerator, - EncryptionGen: ParallelByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, + G: ByteRandomGenerator, +{ + verify_zero_knowledge_preconditions( + lwe_compact_public_key, + output.lwe_ciphertext_count(), + output.ciphertext_modulus(), + delta, + mask_noise_distribution, + body_noise_distribution, + public_params, + )?; + + let encoded = PlaintextList::from_container( + messages + .as_ref() + .iter() + .copied() + .map(|m| m * delta) + .collect::>(), + ); + + let CompactPublicKeyRandomVectors { + binary_random_vector, + mask_noise, + body_noise, + } = encrypt_lwe_compact_ciphertext_list_with_compact_public_key_impl( + lwe_compact_public_key, + output, + &encoded, + mask_noise_distribution, + body_noise_distribution, + secret_generator, + encryption_generator, + ); + + let (c1, c2) = output.get_mask_and_body_list(); + + let (public_commit, private_commit) = commit( + lwe_compact_public_key + .get_mask() + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + lwe_compact_public_key + .get_body() + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + c1.as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + c2.as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + binary_random_vector + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + mask_noise + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + messages + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + body_noise + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + public_params, + random_generator, + ); + + Ok(prove( + (public_params, &public_commit), + &private_commit, + load, + random_generator, + )) +} + +fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key_impl< + Scalar, + KeyCont, + InputCont, + OutputCont, + MaskDistribution, + NoiseDistribution, + SecretGen, + EncryptionGen, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCompactCiphertextList, + encoded: &PlaintextList, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: NoiseDistribution, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, +) -> CompactPublicKeyRandomVectors +where + Scalar: Encryptable + RandomGenerable, + KeyCont: Container, + InputCont: Container, + OutputCont: ContainerMut, + MaskDistribution: Distribution, + NoiseDistribution: Distribution, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, { assert!( output.lwe_size().to_lwe_dimension() == lwe_compact_public_key.lwe_dimension(), @@ -2190,22 +2789,21 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< "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 output_mask_list, mut output_body_list) = output.get_mut_mask_and_body_list(); 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 mut mask_noise = vec![Scalar::ZERO; output_mask_list.lwe_mask_list_size()]; + encryption_generator + .fill_slice_with_random_noise_from_distribution(&mut mask_noise, mask_noise_distribution); - 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"); + let mut body_noise = vec![Scalar::ZERO; encoded.plaintext_count().0]; + encryption_generator + .fill_slice_with_random_noise_from_distribution(&mut body_noise, body_noise_distribution); - // Loop over the ciphertext "bins" + let max_ciphertext_per_bin = lwe_compact_public_key.lwe_dimension().0; output_mask_list .par_iter_mut() .zip( @@ -2213,19 +2811,23 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< .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), + .zip(mask_noise.as_slice().par_chunks(max_ciphertext_per_bin)) + .zip(body_noise.as_slice().par_chunks(max_ciphertext_per_bin)), ) .for_each( |( mut output_mask, ( - ((mut output_body_chunk, input_plaintext_chunk), binary_random_slice), - mut loop_generator, + ( + ((mut output_body_chunk, input_plaintext_chunk), binary_random_slice), + mask_noise, + ), + body_noise, ), )| { - // 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]; + // 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; max_ciphertext_per_bin]; rayon::join( || { @@ -2236,7 +2838,7 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< ); }, || { - // Fill the temp buffer with b convolved with r + // // Fill the temp buffer with b convolved with r slice_semi_reverse_negacyclic_convolution( pk_body_convolved.as_mut_slice(), pk_body.as_ref(), @@ -2245,17 +2847,15 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< }, ); - // Noise from Chi_1 for the mask part of the encryption - loop_generator - .unsigned_integer_slice_wrapping_add_random_noise_from_distribution_assign( - output_mask.as_mut(), - mask_noise_distribution, - ); + let zk_body_convolved = polymul_rev(pk_body.as_ref(), binary_random_slice); + + assert_eq!(&pk_body_convolved, &zk_body_convolved); - // Fill the body chunk afterwards manually as it most likely will be smaller than - // the full convolution result. rev(b convolved r) + Delta * m + e2 + slice_wrapping_add_assign(output_mask.as_mut(), mask_noise); + + // Fill the body chunk afterward 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 - // The reverse is to make the first element product match the single ciphertext case output_body_chunk .iter_mut() .zip( @@ -2264,16 +2864,395 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< .rev() .zip(input_plaintext_chunk.iter()), ) - .for_each(|(dst, (&src, plaintext))| { - *dst.data = src - .wrapping_add( - loop_generator - .random_noise_from_distribution(body_noise_distribution), - ) - .wrapping_add(*plaintext.0); + .zip(body_noise) + .for_each(|((dst, (&src, plaintext)), body_noise)| { + *dst.data = src.wrapping_add(*body_noise).wrapping_add(*plaintext.0); }); }, ); + CompactPublicKeyRandomVectors { + binary_random_vector, + mask_noise, + body_noise, + } +} + +/// 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 +/// +/// ```rust +/// 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_noise_distribution = +/// Gaussian::from_dispersion_parameter(StandardDev(0.00000000000000029403601535432533), 0.0); +/// 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_noise_distribution, +/// 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_noise_distribution, +/// glwe_noise_distribution, +/// &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, + MaskDistribution, + NoiseDistribution, + KeyCont, + InputCont, + OutputCont, + SecretGen, + EncryptionGen, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCompactCiphertextList, + encoded: &PlaintextList, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: NoiseDistribution, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, +) where + Scalar: Encryptable + + RandomGenerable + + Sync + + Send, + MaskDistribution: Distribution + Sync, + NoiseDistribution: Distribution + Sync, + KeyCont: Container, + InputCont: Container, + OutputCont: ContainerMut, + SecretGen: ByteRandomGenerator, + EncryptionGen: ParallelByteRandomGenerator, +{ + let _ = par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key_impl( + lwe_compact_public_key, + output, + encoded, + mask_noise_distribution, + body_noise_distribution, + secret_generator, + encryption_generator, + ); +} + +/// Encrypt and generates a zero-knowledge proof of an input cleartext 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. +/// +/// - The input cleartext list must have a length smaller or equal the maximum number of message +/// authorized by the CRS. +/// +/// - The noise distributions must be bounded +/// +/// +/// # Example +/// +/// ```rust +/// use tfhe::core_crypto::commons::math::random::RandomGenerator; +/// use tfhe::core_crypto::prelude::*; +/// use tfhe::zk::ZkComputeLoad; +/// +/// // 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(4); +/// let glwe_noise_distribution = TUniform::new(9); +/// let ciphertext_modulus = CiphertextModulus::new_native(); +/// let delta_log = 60; +/// let delta = 1u64 << delta_log; +/// let plaintext_modulus = 1u64 << (64 - delta_log); +/// +/// // 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()); +/// let mut random_generator = RandomGenerator::::new(seeder.seed()); +/// +/// let crs = CompactPkeCrs::new( +/// lwe_dimension, +/// lwe_ciphertext_count.0, +/// glwe_noise_distribution, +/// ciphertext_modulus, +/// plaintext_modulus, +/// &mut random_generator, +/// ) +/// .unwrap(); +/// +/// // 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_noise_distribution, +/// ciphertext_modulus, +/// &mut encryption_generator, +/// ); +/// +/// let cleartexts = (0..lwe_ciphertext_count.0 as u64).collect::>(); +/// +/// // Create a new LweCompactCiphertextList +/// let mut output_compact_ct_list = LweCompactCiphertextList::new( +/// 0u64, +/// lwe_dimension.to_lwe_size(), +/// lwe_ciphertext_count, +/// ciphertext_modulus, +/// ); +/// +/// let proof = par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( +/// &lwe_compact_public_key, +/// &mut output_compact_ct_list, +/// &cleartexts, +/// delta, +/// glwe_noise_distribution, +/// glwe_noise_distribution, +/// &mut secret_generator, +/// &mut encryption_generator, +/// &mut random_generator, +/// crs.public_params(), +/// ZkComputeLoad::Proof, +/// ) +/// .unwrap(); +/// +/// // verify the ciphertext list with the proof +/// assert!(verify_lwe_compact_ciphertext_list( +/// &output_compact_ct_list, +/// &lwe_compact_public_key, +/// &proof, +/// crs.public_params(), +/// ) +/// .is_valid()); +/// +/// let mut output_plaintext_list = +/// PlaintextList::new(0u64, PlaintextCount(lwe_ciphertext_count.0)); +/// +/// 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) >> 60); +/// +/// // Check we recovered the original messages +/// assert_eq!(&cleartexts, output_plaintext_list.as_ref()); +/// ``` +#[cfg(feature = "zk-pok-experimental")] +#[allow(clippy::too_many_arguments)] +pub fn par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key< + Scalar, + KeyCont, + InputCont, + OutputCont, + MaskDistribution, + NoiseDistribution, + SecretGen, + EncryptionGen, + G, +>( + lwe_compact_public_key: &LweCompactPublicKey, + output: &mut LweCompactCiphertextList, + messages: &InputCont, + delta: Scalar, + mask_noise_distribution: MaskDistribution, + body_noise_distribution: NoiseDistribution, + secret_generator: &mut SecretRandomGenerator, + encryption_generator: &mut EncryptionRandomGenerator, + random_generator: &mut RandomGenerator, + public_params: &CompactPkePublicParams, + load: ZkComputeLoad, +) -> crate::Result +where + Scalar: Encryptable + + RandomGenerable + + CastFrom, + Scalar::Signed: CastFrom, + i64: CastFrom, + u64: CastFrom + CastInto, + MaskDistribution: BoundedDistribution, + NoiseDistribution: BoundedDistribution, + KeyCont: Container, + InputCont: Container, + OutputCont: ContainerMut, + SecretGen: ByteRandomGenerator, + EncryptionGen: ByteRandomGenerator, + G: ByteRandomGenerator, +{ + verify_zero_knowledge_preconditions( + lwe_compact_public_key, + output.lwe_ciphertext_count(), + output.ciphertext_modulus(), + delta, + mask_noise_distribution, + body_noise_distribution, + public_params, + )?; + + let encoded = PlaintextList::from_container( + messages + .as_ref() + .iter() + .copied() + .map(|m| m * delta) + .collect::>(), + ); + + let CompactPublicKeyRandomVectors { + binary_random_vector, + mask_noise, + body_noise, + } = encrypt_lwe_compact_ciphertext_list_with_compact_public_key_impl( + lwe_compact_public_key, + output, + &encoded, + mask_noise_distribution, + body_noise_distribution, + secret_generator, + encryption_generator, + ); + + let (c1, c2) = output.get_mask_and_body_list(); + + let (public_commit, private_commit) = commit( + lwe_compact_public_key + .get_mask() + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + lwe_compact_public_key + .get_body() + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + c1.as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + c2.as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + binary_random_vector + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + mask_noise + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + messages + .as_ref() + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + body_noise + .iter() + .copied() + .map(CastFrom::cast_from) + .collect::>(), + public_params, + random_generator, + ); + + Ok(prove( + (public_params, &public_commit), + &private_commit, + load, + random_generator, + )) } #[cfg(test)] diff --git a/tfhe/src/core_crypto/algorithms/lwe_zero_knowledge_verification.rs b/tfhe/src/core_crypto/algorithms/lwe_zero_knowledge_verification.rs new file mode 100644 index 0000000000..5e3ecd1dd5 --- /dev/null +++ b/tfhe/src/core_crypto/algorithms/lwe_zero_knowledge_verification.rs @@ -0,0 +1,102 @@ +use crate::core_crypto::entities::{LweCompactCiphertextList, LweCompactPublicKey}; +use crate::core_crypto::prelude::{CastFrom, Container, LweCiphertext, UnsignedInteger}; +use crate::zk::{CompactPkeProof, CompactPkePublicParams, ZkVerificationOutCome}; +use tfhe_zk_pok::proofs::pke::{verify, PublicCommit}; + +/// Verifies with the given proof that a [`LweCompactCiphertextList`](LweCompactCiphertextList) +/// is valid. +pub fn verify_lwe_compact_ciphertext_list( + lwe_compact_list: &LweCompactCiphertextList, + compact_public_key: &LweCompactPublicKey, + proof: &CompactPkeProof, + public_params: &CompactPkePublicParams, +) -> ZkVerificationOutCome +where + Scalar: UnsignedInteger, + i64: CastFrom, + ListCont: Container, + KeyCont: Container, +{ + if Scalar::BITS > 64 { + return ZkVerificationOutCome::Invalid; + } + let public_commit = PublicCommit::new( + compact_public_key + .get_mask() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + compact_public_key + .get_body() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + lwe_compact_list + .get_mask_list() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + lwe_compact_list + .get_body_list() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + ); + match verify(proof, (public_params, &public_commit)) { + Ok(_) => ZkVerificationOutCome::Valid, + Err(_) => ZkVerificationOutCome::Invalid, + } +} + +pub fn verify_lwe_ciphertext( + lwe_ciphertext: &LweCiphertext, + compact_public_key: &LweCompactPublicKey, + proof: &CompactPkeProof, + public_params: &CompactPkePublicParams, +) -> ZkVerificationOutCome +where + Scalar: UnsignedInteger, + i64: CastFrom, + Cont: Container, + KeyCont: Container, +{ + if Scalar::BITS > 64 { + return ZkVerificationOutCome::Invalid; + } + let public_commit = PublicCommit::new( + compact_public_key + .get_mask() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + compact_public_key + .get_body() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + lwe_ciphertext + .get_mask() + .as_ref() + .iter() + .copied() + .map(|x| i64::cast_from(x)) + .collect(), + vec![i64::cast_from(*lwe_ciphertext.get_body().data); 1], + ); + match verify(proof, (public_params, &public_commit)) { + Ok(_) => ZkVerificationOutCome::Valid, + Err(_) => ZkVerificationOutCome::Invalid, + } +} diff --git a/tfhe/src/core_crypto/algorithms/mod.rs b/tfhe/src/core_crypto/algorithms/mod.rs index 1d0436e4f0..6bed3630a2 100644 --- a/tfhe/src/core_crypto/algorithms/mod.rs +++ b/tfhe/src/core_crypto/algorithms/mod.rs @@ -27,6 +27,8 @@ pub mod lwe_programmable_bootstrapping; pub mod lwe_public_key_generation; pub mod lwe_secret_key_generation; pub mod lwe_wopbs; +#[cfg(feature = "zk-pok-experimental")] +pub mod lwe_zero_knowledge_verification; pub mod misc; pub mod polynomial_algorithms; pub mod seeded_ggsw_ciphertext_decompression; @@ -73,6 +75,8 @@ pub use lwe_programmable_bootstrapping::*; pub use lwe_public_key_generation::*; pub use lwe_secret_key_generation::*; pub use lwe_wopbs::*; +#[cfg(feature = "zk-pok-experimental")] +pub use lwe_zero_knowledge_verification::*; pub use seeded_ggsw_ciphertext_decompression::*; pub use seeded_ggsw_ciphertext_list_decompression::*; pub use seeded_glwe_ciphertext_decompression::*; diff --git a/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs b/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs index 715ca80a4e..4ee7f140d3 100644 --- a/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs +++ b/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs @@ -1,6 +1,10 @@ use super::*; use crate::core_crypto::commons::generators::DeterministicSeeder; +#[cfg(feature = "zk-pok-experimental")] +use crate::core_crypto::commons::math::random::RandomGenerator; use crate::core_crypto::commons::test_tools; +#[cfg(feature = "zk-pok-experimental")] +use rand::Rng; #[cfg(not(tarpaulin))] const NB_TESTS: usize = 10; @@ -182,7 +186,7 @@ fn lwe_encrypt_decrypt_custom_mod(params: ClassicTestPara assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -237,7 +241,7 @@ fn lwe_allocate_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -290,7 +294,7 @@ fn lwe_trivial_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -340,7 +344,7 @@ fn lwe_allocate_trivial_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -403,7 +407,7 @@ fn lwe_list_encrypt_decrypt_custom_mod(params: ClassicTes assert!(check_encrypted_content_respects_mod( &list, - ciphertext_modulus + ciphertext_modulus, )); let mut plaintext_list = @@ -474,7 +478,7 @@ fn lwe_list_par_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &list, - ciphertext_modulus + ciphertext_modulus, )); let mut plaintext_list = @@ -548,7 +552,7 @@ fn lwe_public_encrypt_decrypt_custom_mod(params: ClassicT assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -623,7 +627,7 @@ fn lwe_seeded_public_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -689,7 +693,7 @@ fn lwe_seeded_list_par_encrypt_decrypt_custom_mod(params: ClassicT assert!(check_encrypted_content_respects_mod( &std::slice::from_ref(seeded_ct.get_body().data), - ciphertext_modulus + ciphertext_modulus, )); let ct = seeded_ct.decompress_into_lwe_ciphertext(); assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -826,14 +830,14 @@ fn lwe_seeded_allocate_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &std::slice::from_ref(seeded_ct.get_body().data), - ciphertext_modulus + ciphertext_modulus, )); let ct = seeded_ct.decompress_into_lwe_ciphertext(); assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -971,7 +975,7 @@ fn lwe_compact_public_encrypt_decrypt_custom_mod( assert!(check_encrypted_content_respects_mod( &ct, - ciphertext_modulus + ciphertext_modulus, )); let decrypted = decrypt_lwe_ciphertext(&lwe_sk, &ct); @@ -991,3 +995,324 @@ fn lwe_compact_public_encrypt_decrypt_custom_mod( create_parametrized_test!(lwe_compact_public_encrypt_decrypt_custom_mod { TEST_PARAMS_4_BITS_NATIVE_U64 }); + +#[cfg(feature = "zk-pok-experimental")] +fn lwe_compact_public_encrypt_prove_verify_decrypt_custom_mod( + params: ClassicTestParams, +) where + Scalar: UnsignedTorus + CastFrom, + Scalar::Signed: CastFrom, + i64: CastFrom, + u64: CastFrom + CastInto, + rand_distr::Standard: rand_distr::Distribution, +{ + let lwe_dimension = LweDimension(params.polynomial_size.0); + let glwe_noise_distribution = TUniform::new(9); + 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(); + let mut random_generator = RandomGenerator::::new(rsc.seeder.seed()); + + let msg_modulus = Scalar::ONE.shl(message_modulus_log.0); + let mut msg = msg_modulus; + let delta: Scalar = encoding_with_padding / msg_modulus; + + let crs = CompactPkeCrs::new( + lwe_dimension, + 1, + glwe_noise_distribution, + ciphertext_modulus, + msg_modulus * Scalar::TWO, + &mut random_generator, + ) + .unwrap(); + + 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_noise_distribution, + ciphertext_modulus, + &mut rsc.encryption_random_generator, + ); + + let mut ct = LweCiphertext::new( + Scalar::ZERO, + lwe_dimension.to_lwe_size(), + ciphertext_modulus, + ); + + let proof = encrypt_and_prove_lwe_ciphertext_with_compact_public_key( + &pk, + &mut ct, + Cleartext(msg), + delta, + glwe_noise_distribution, + glwe_noise_distribution, + &mut rsc.secret_random_generator, + &mut rsc.encryption_random_generator, + &mut random_generator, + crs.public_params(), + ZkComputeLoad::Proof, + ) + .unwrap(); + + assert!(check_encrypted_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); + + // Verify the proof + assert!(verify_lwe_ciphertext(&ct, &pk, &proof, crs.public_params()).is_valid()); + + // verify proof with invalid ciphertext + let index = random_generator.gen::() % ct.as_ref().len(); + let value_to_add = random_generator.gen::(); + ct.as_mut()[index] = ct.as_mut()[index].wrapping_add(value_to_add); + assert!(verify_lwe_ciphertext(&ct, &pk, &proof, crs.public_params()).is_invalid()); + } + + // In coverage, we break after one while loop iteration, changing message values does not + // yield higher coverage + #[cfg(tarpaulin)] + break; + } +} + +#[cfg(feature = "zk-pok-experimental")] +create_parametrized_test!(lwe_compact_public_encrypt_prove_verify_decrypt_custom_mod { + TEST_PARAMS_4_BITS_NATIVE_U64 +}); + +#[cfg(feature = "zk-pok-experimental")] +#[test] +fn test_par_compact_lwe_list_public_key_encryption_and_proof() { + use rand::Rng; + + let lwe_dimension = LweDimension(2048); + let glwe_noise_distribution = TUniform::new(9); + let ciphertext_modulus = CiphertextModulus::new_native(); + + let delta_log = 59; + let delta = 1u64 << delta_log; + let message_modulus = 1u64 << (64 - (delta_log + 1)); + let plaintext_modulus = 1u64 << (64 - delta_log); + let mut thread_rng = rand::thread_rng(); + + let max_num_body = 512; + let crs = CompactPkeCrs::new( + lwe_dimension, + max_num_body, + glwe_noise_distribution, + ciphertext_modulus, + plaintext_modulus, + &mut thread_rng, + ) + .unwrap(); + + for _ in 0..4 { + let ct_count = thread_rng.gen_range(1..=max_num_body); + let lwe_ciphertext_count = LweCiphertextCount(ct_count); + + println!("{lwe_dimension:?} {ct_count:?}"); + + let seed = test_tools::random_seed(); + let cleartexts = (0..ct_count) + .map(|_| thread_rng.gen::() % message_modulus) + .collect::>(); + + let par_lwe_ct_list = { + let mut deterministic_seeder = + DeterministicSeeder::::new(seed); + let mut random_generator = + RandomGenerator::::new(deterministic_seeder.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_noise_distribution, + &mut encryption_random_generator, + ); + + let mut output_compact_ct_list = LweCompactCiphertextList::new( + 0u64, + lwe_dimension.to_lwe_size(), + lwe_ciphertext_count, + ciphertext_modulus, + ); + + let proof = par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( + &compact_lwe_pk, + &mut output_compact_ct_list, + &cleartexts, + delta, + glwe_noise_distribution, + glwe_noise_distribution, + &mut secret_random_generator, + &mut encryption_random_generator, + &mut random_generator, + crs.public_params(), + ZkComputeLoad::Proof, + ) + .unwrap(); + + assert!(verify_lwe_compact_ciphertext_list( + &output_compact_ct_list, + &compact_lwe_pk, + &proof, + crs.public_params() + ) + .is_valid()); + + let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(ct_count)); + + let lwe_ciphertext_list = output_compact_ct_list + .clone() + .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(5), DecompositionLevelCount(1)); + + output_plaintext_list + .iter_mut() + .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0) >> delta_log); + + assert_eq!(cleartexts.as_slice(), output_plaintext_list.as_ref()); + + // verify proof with invalid ciphertext + let index = random_generator.gen::() % output_compact_ct_list.as_ref().len(); + let value_to_add = random_generator.gen(); + output_compact_ct_list.as_mut()[index] = + output_compact_ct_list.as_mut()[index].wrapping_add(value_to_add); + assert!(verify_lwe_compact_ciphertext_list( + &output_compact_ct_list, + &compact_lwe_pk, + &proof, + crs.public_params() + ) + .is_invalid()); + + lwe_ciphertext_list + }; + + let ser_lwe_ct_list = { + let mut deterministic_seeder = + DeterministicSeeder::::new(seed); + let mut random_generator = + RandomGenerator::::new(deterministic_seeder.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_noise_distribution, + &mut encryption_random_generator, + ); + + let mut output_compact_ct_list = LweCompactCiphertextList::new( + 0u64, + lwe_dimension.to_lwe_size(), + lwe_ciphertext_count, + ciphertext_modulus, + ); + + let proof = par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( + &compact_lwe_pk, + &mut output_compact_ct_list, + &cleartexts, + delta, + glwe_noise_distribution, + glwe_noise_distribution, + &mut secret_random_generator, + &mut encryption_random_generator, + &mut random_generator, + crs.public_params(), + ZkComputeLoad::Proof, + ) + .unwrap(); + + assert!(verify_lwe_compact_ciphertext_list( + &output_compact_ct_list, + &compact_lwe_pk, + &proof, + crs.public_params() + ) + .is_valid()); + + let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(ct_count)); + + let lwe_ciphertext_list = output_compact_ct_list + .clone() + .expand_into_lwe_ciphertext_list(); + + decrypt_lwe_ciphertext_list(&lwe_sk, &lwe_ciphertext_list, &mut output_plaintext_list); + + let signed_decomposer = + SignedDecomposer::new(DecompositionBaseLog(5), DecompositionLevelCount(1)); + + output_plaintext_list + .iter_mut() + .for_each(|x| *x.0 = signed_decomposer.closest_representable(*x.0) >> delta_log); + + assert_eq!(cleartexts.as_slice(), output_plaintext_list.as_ref()); + + // verify proof with invalid ciphertext + let index = random_generator.gen::() % output_compact_ct_list.as_ref().len(); + let value_to_add = random_generator.gen(); + output_compact_ct_list.as_mut()[index] = + output_compact_ct_list.as_mut()[index].wrapping_add(value_to_add); + assert!(verify_lwe_compact_ciphertext_list( + &output_compact_ct_list, + &compact_lwe_pk, + &proof, + crs.public_params() + ) + .is_invalid()); + + lwe_ciphertext_list + }; + + assert_eq!(ser_lwe_ct_list, par_lwe_ct_list); + } +} diff --git a/tfhe/src/core_crypto/commons/math/random/gaussian.rs b/tfhe/src/core_crypto/commons/math/random/gaussian.rs index 29c9c2b1d1..9094e0adbc 100644 --- a/tfhe/src/core_crypto/commons/math/random/gaussian.rs +++ b/tfhe/src/core_crypto/commons/math/random/gaussian.rs @@ -1,6 +1,5 @@ use super::*; use crate::core_crypto::commons::math::torus::FromTorus; -use crate::core_crypto::commons::numeric::{CastInto, Numeric}; use serde::{Deserialize, Serialize}; // Clippy false positive, does not repro with smaller code diff --git a/tfhe/src/core_crypto/commons/math/random/generator.rs b/tfhe/src/core_crypto/commons/math/random/generator.rs index bee97c39de..a8c4f38249 100644 --- a/tfhe/src/core_crypto/commons/math/random/generator.rs +++ b/tfhe/src/core_crypto/commons/math/random/generator.rs @@ -796,3 +796,32 @@ impl RandomGenerator { .map(|iter| iter.map(Self)) } } + +impl rand_core::RngCore for RandomGenerator { + fn next_u32(&mut self) -> u32 { + let mut bytes = [0u8; std::mem::size_of::()]; + bytes.iter_mut().for_each(|b| *b = self.generate_next()); + u32::from_ne_bytes(bytes) + } + + fn next_u64(&mut self) -> u64 { + let mut bytes = [0u8; std::mem::size_of::()]; + bytes.iter_mut().for_each(|b| *b = self.generate_next()); + u64::from_ne_bytes(bytes) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.iter_mut().for_each(|b| *b = self.generate_next()); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + if let Some(limit) = self.remaining_bytes() { + if limit < dest.len() { + return Err(rand_core::Error::new(format!("The random generator is bounded and cannot fill the slice {} bytes requested, {limit} possible", dest.len())) + ); + } + } + self.fill_bytes(dest); + Ok(()) + } +} diff --git a/tfhe/src/core_crypto/commons/math/random/mod.rs b/tfhe/src/core_crypto/commons/math/random/mod.rs index 342d729783..376dae8772 100644 --- a/tfhe/src/core_crypto/commons/math/random/mod.rs +++ b/tfhe/src/core_crypto/commons/math/random/mod.rs @@ -15,7 +15,9 @@ //! [`RandomGenerator`] instead. use crate::core_crypto::commons::dispersion::{DispersionParameter, StandardDev, Variance}; use crate::core_crypto::commons::numeric::{FloatingPoint, UnsignedInteger}; +use std::ops::Bound; +use crate::core_crypto::prelude::{CastFrom, CastInto, Numeric, SignedInteger}; /// Convenience alias for the most efficient CSPRNG implementation available. pub use activated_random_generator::ActivatedRandomGenerator; pub use gaussian::*; @@ -102,6 +104,86 @@ impl Distribution for UniformTernary {} impl Distribution for Gaussian {} impl Distribution for TUniform {} +pub trait BoundedDistribution: Distribution { + fn low_bound(&self) -> Bound; + fn high_bound(&self) -> Bound; + + fn contains(self, value: T) -> bool + where + T: Numeric, + High: Numeric + CastFrom, + Low: Numeric + CastFrom, + { + { + let value = Low::cast_from(value); + match self.low_bound() { + Bound::Included(inclusive_low) => { + if value < inclusive_low { + return false; + } + } + Bound::Excluded(exclusive_low) => { + if value <= exclusive_low { + return false; + } + } + Bound::Unbounded => {} + } + } + + { + let value = High::cast_from(value); + match self.high_bound() { + Bound::Included(inclusive_high) => { + if value > inclusive_high { + return false; + } + } + Bound::Excluded(exclusive_high) => { + if value >= exclusive_high { + return false; + } + } + Bound::Unbounded => {} + } + } + + true + } +} + +impl BoundedDistribution for TUniform +where + T: UnsignedInteger, +{ + fn low_bound(&self) -> Bound { + Bound::Included(self.min_value_inclusive()) + } + + fn high_bound(&self) -> Bound { + Bound::Included(self.max_value_inclusive().into_unsigned()) + } +} + +impl BoundedDistribution for DynamicDistribution +where + T: UnsignedInteger, +{ + fn low_bound(&self) -> Bound { + match self { + Self::Gaussian(_) => Bound::Unbounded, + Self::TUniform(tu) => tu.low_bound(), + } + } + + fn high_bound(&self) -> Bound { + match self { + Self::Gaussian(_) => Bound::Unbounded, + Self::TUniform(tu) => tu.high_bound(), + } + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] pub enum DynamicDistribution { Gaussian(Gaussian), diff --git a/tfhe/src/core_crypto/commons/math/random/t_uniform.rs b/tfhe/src/core_crypto/commons/math/random/t_uniform.rs index 14eeff1edf..3b3e318ef1 100644 --- a/tfhe/src/core_crypto/commons/math/random/t_uniform.rs +++ b/tfhe/src/core_crypto/commons/math/random/t_uniform.rs @@ -1,5 +1,4 @@ use super::*; -use crate::core_crypto::commons::numeric::Numeric; use serde::{Deserialize, Serialize}; /// The distribution $TUniform(1, -2^b, 2^b)$ is defined as follows, any value in the interval diff --git a/tfhe/src/core_crypto/commons/numeric/signed.rs b/tfhe/src/core_crypto/commons/numeric/signed.rs index c6f6f94b8c..50e7848cec 100644 --- a/tfhe/src/core_crypto/commons/numeric/signed.rs +++ b/tfhe/src/core_crypto/commons/numeric/signed.rs @@ -41,6 +41,10 @@ pub trait SignedInteger: /// Return a bit representation of the integer, where blocks of length `block_length` are /// separated by whitespaces to increase the readability. fn to_bits_string(&self, block_length: usize) -> String; + + /// Return the absoluted balue + #[must_use] + fn wrapping_abs(self) -> Self; } macro_rules! implement { @@ -77,6 +81,11 @@ macro_rules! implement { } strn } + + #[inline] + fn wrapping_abs(self) -> Self { + self.wrapping_abs() + } } }; } diff --git a/tfhe/src/core_crypto/commons/numeric/unsigned.rs b/tfhe/src/core_crypto/commons/numeric/unsigned.rs index cdbf843b4a..ed730257e6 100644 --- a/tfhe/src/core_crypto/commons/numeric/unsigned.rs +++ b/tfhe/src/core_crypto/commons/numeric/unsigned.rs @@ -87,6 +87,8 @@ pub trait UnsignedInteger: #[must_use] fn is_power_of_two(self) -> bool; #[must_use] + fn next_power_of_two(self) -> Self; + #[must_use] fn ilog2(self) -> u32; #[must_use] fn ceil_ilog2(self) -> u32 { @@ -240,6 +242,10 @@ macro_rules! implement { self.is_power_of_two() } #[inline] + fn next_power_of_two(self) -> Self { + self.next_power_of_two() + } + #[inline] fn ilog2(self) -> u32 { self.ilog2() } diff --git a/tfhe/src/core_crypto/entities/mod.rs b/tfhe/src/core_crypto/entities/mod.rs index 9f846d73cc..1bee3d0992 100644 --- a/tfhe/src/core_crypto/entities/mod.rs +++ b/tfhe/src/core_crypto/entities/mod.rs @@ -53,6 +53,8 @@ pub use crate::core_crypto::fft_impl::fft64::crypto::ggsw::{ FourierGgswCiphertext, FourierGgswCiphertextList, FourierGgswLevelMatrix, FourierGgswLevelRow, }; pub use crate::core_crypto::fft_impl::fft64::math::polynomial::FourierPolynomial; +#[cfg(feature = "zk-pok-experimental")] +pub use crate::zk::*; pub use cleartext::*; pub use ggsw_ciphertext::*; pub use ggsw_ciphertext_list::*; diff --git a/tfhe/src/error.rs b/tfhe/src/error.rs new file mode 100644 index 0000000000..b2559f0f31 --- /dev/null +++ b/tfhe/src/error.rs @@ -0,0 +1,59 @@ +use std::fmt::{Debug, Display, Formatter}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum ErrorKind { + Message(String), + /// The zero knowledge proof and the content it is supposed to prove + /// failed to correctly prove + #[cfg(feature = "zk-pok-experimental")] + InvalidZkProof, +} + +#[derive(Debug, Clone)] +pub struct Error { + kind: ErrorKind, +} + +impl Error { + pub(crate) fn new(message: String) -> Self { + Self::from(ErrorKind::Message(message)) + } + + pub fn kind(&self) -> &ErrorKind { + &self.kind + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.kind() { + ErrorKind::Message(msg) => { + write!(f, "{msg}") + } + #[cfg(feature = "zk-pok-experimental")] + ErrorKind::InvalidZkProof => { + write!(f, "The zero knowledge proof and the content it is supposed to prove were not valid") + } + } + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Self { kind } + } +} + +impl<'a> From<&'a str> for Error { + fn from(message: &'a str) -> Self { + Self::new(message.to_string()) + } +} + +impl From for Error { + fn from(message: String) -> Self { + Self::new(message) + } +} + +impl std::error::Error for Error {} diff --git a/tfhe/src/high_level_api/booleans/compact.rs b/tfhe/src/high_level_api/booleans/compact.rs index 573c2d5e80..9dffe880e9 100644 --- a/tfhe/src/high_level_api/booleans/compact.rs +++ b/tfhe/src/high_level_api/booleans/compact.rs @@ -36,7 +36,7 @@ use crate::{CompactPublicKey, FheBoolConformanceParams, ServerKey}; #[cfg_attr(all(doc, not(doctest)), doc(cfg(feature = "integer")))] #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct CompactFheBool { - list: CompactCiphertextList, + pub(in crate::high_level_api) list: CompactCiphertextList, } impl CompactFheBool { @@ -55,7 +55,7 @@ impl CompactFheBool { } impl FheTryEncrypt for CompactFheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: bool, key: &CompactPublicKey) -> Result { let mut ciphertext = key.key.try_encrypt_compact(&[u8::from(value)], 1); @@ -145,7 +145,7 @@ impl CompactFheBoolList { } impl<'a> FheTryEncrypt<&'a [bool], CompactPublicKey> for CompactFheBoolList { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; /// Encrypts a slice of bool /// diff --git a/tfhe/src/high_level_api/booleans/compressed.rs b/tfhe/src/high_level_api/booleans/compressed.rs index a1ae79c5a2..2bc0861d0a 100644 --- a/tfhe/src/high_level_api/booleans/compressed.rs +++ b/tfhe/src/high_level_api/booleans/compressed.rs @@ -58,7 +58,7 @@ impl CompressedFheBool { } impl FheTryEncrypt for CompressedFheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; /// Creates a compressed encryption of a boolean value fn try_encrypt(value: bool, key: &ClientKey) -> Result { diff --git a/tfhe/src/high_level_api/booleans/encrypt.rs b/tfhe/src/high_level_api/booleans/encrypt.rs index ac348bf012..bab0b9bc6b 100644 --- a/tfhe/src/high_level_api/booleans/encrypt.rs +++ b/tfhe/src/high_level_api/booleans/encrypt.rs @@ -12,7 +12,7 @@ use crate::shortint::ciphertext::Degree; use crate::{ClientKey, CompactPublicKey, CompressedPublicKey, PublicKey}; impl FheTryEncrypt for FheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: bool, key: &ClientKey) -> Result { let integer_client_key = &key.key.key; @@ -23,7 +23,7 @@ impl FheTryEncrypt for FheBool { } impl FheTryEncrypt for FheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: bool, key: &CompactPublicKey) -> Result { let mut ciphertext = key.key.key.encrypt_radix(value as u8, 1); @@ -66,7 +66,7 @@ impl FheTrivialEncrypt for FheBool { } impl FheTryEncrypt for FheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: bool, key: &CompressedPublicKey) -> Result { let key = &key.key; @@ -77,7 +77,7 @@ impl FheTryEncrypt for FheBool { } impl FheTryEncrypt for FheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: bool, key: &PublicKey) -> Result { let key = &key.key; @@ -95,7 +95,7 @@ impl FheDecrypt for FheBool { } impl FheTryTrivialEncrypt for FheBool { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt_trivial(value: bool) -> Result { let ciphertext = global_state::with_internal_keys(|key| match key { diff --git a/tfhe/src/high_level_api/booleans/mod.rs b/tfhe/src/high_level_api/booleans/mod.rs index 5e6184fdaa..b95a0dc356 100644 --- a/tfhe/src/high_level_api/booleans/mod.rs +++ b/tfhe/src/high_level_api/booleans/mod.rs @@ -1,6 +1,8 @@ pub use base::{FheBool, FheBoolConformanceParams}; pub use compact::{CompactFheBool, CompactFheBoolList, CompactFheBoolListConformanceParams}; pub use compressed::CompressedFheBool; +#[cfg(feature = "zk-pok-experimental")] +pub use zk::{ProvenCompactFheBool, ProvenCompactFheBoolList}; mod base; mod compact; @@ -9,3 +11,5 @@ mod encrypt; mod inner; #[cfg(test)] mod tests; +#[cfg(feature = "zk-pok-experimental")] +mod zk; diff --git a/tfhe/src/high_level_api/booleans/tests.rs b/tfhe/src/high_level_api/booleans/tests.rs index 60c300e13d..314e610054 100644 --- a/tfhe/src/high_level_api/booleans/tests.rs +++ b/tfhe/src/high_level_api/booleans/tests.rs @@ -825,7 +825,57 @@ mod cpu { let expanded_list = deserialized_list.expand(); for (fhe_uint, expected) in expanded_list.iter().zip(clears.into_iter()) { let decrypted: bool = fhe_uint.decrypt(&client_key); - assert_eq!(decrypted, expected); + assert_eq!(decrypted, expected) + } + } + + #[cfg(feature = "zk-pok-experimental")] + #[test] + fn test_fhe_bool_zk() { + use crate::core_crypto::prelude::DynamicDistribution; + use crate::zk::{CompactPkeCrs, ZkComputeLoad}; + + let mut params = crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::new_t_uniform(9); + + let config = ConfigBuilder::with_custom_parameters(params, None).build(); + let crs = CompactPkeCrs::from_config(config, 2).unwrap(); + let ck = ClientKey::generate(config); + let pk = CompactPublicKey::new(&ck); + + for msg in [true, false] { + let proven_compact_fhe_bool = crate::ProvenCompactFheBool::try_encrypt( + msg, + crs.public_params(), + &pk, + ZkComputeLoad::Proof, + ) + .unwrap(); + let fhe_bool = proven_compact_fhe_bool + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted = fhe_bool.decrypt(&ck); + assert_eq!(decrypted, msg); + assert_degree_is_ok(&fhe_bool); + } + + let proven_compact_fhe_bool_list = crate::ProvenCompactFheBoolList::try_encrypt( + &[true, false], + crs.public_params(), + &pk, + ZkComputeLoad::Proof, + ) + .unwrap(); + let fhe_bools = proven_compact_fhe_bool_list + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted = fhe_bools + .iter() + .map(|fb| fb.decrypt(&ck)) + .collect::>(); + assert_eq!(decrypted.as_slice(), &[true, false]); + for fhe_bool in fhe_bools { + assert_degree_is_ok(&fhe_bool); } } } diff --git a/tfhe/src/high_level_api/booleans/zk.rs b/tfhe/src/high_level_api/booleans/zk.rs new file mode 100644 index 0000000000..964ed7641f --- /dev/null +++ b/tfhe/src/high_level_api/booleans/zk.rs @@ -0,0 +1,134 @@ +use crate::integer::{BooleanBlock, ProvenCompactCiphertextList, RadixCiphertext}; +use crate::named::Named; +use crate::shortint::ciphertext::Degree; +use crate::zk::{CompactPkePublicParams, ZkComputeLoad, ZkVerificationOutCome}; +use crate::{CompactPublicKey, FheBool}; +use serde::{Deserialize, Serialize}; + +/// A `CompactFheBool` tied to a Zero-Knowledge proof +/// +/// The zero-knowledge proof allows to verify that the ciphertext is correctly +/// encrypted. +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactFheBool { + inner: ProvenCompactCiphertextList, +} + +impl Named for ProvenCompactFheBool { + const NAME: &'static str = "high_level_api::ProvenCompactFheBool"; +} + +impl ProvenCompactFheBool { + /// Encrypts the message while also generating the zero-knowledge proof + pub fn try_encrypt( + value: bool, + public_params: &CompactPkePublicParams, + key: &CompactPublicKey, + load: ZkComputeLoad, + ) -> crate::Result { + let value = value as u8; + let inner = key.key.key.encrypt_and_prove_radix_compact( + &[value], + 1, /* num blocks */ + public_params, + load, + )?; + Ok(Self { inner }) + } + + /// Verifies the ciphertext and the proof + /// + /// If the proof and ciphertext are valid, it returns an `Ok` with + /// the underlying `FheBool`. + pub fn verify_and_expand( + self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result { + let mut radix = self + .inner + .verify_and_expand_one::(public_params, &public_key.key.key)?; + assert_eq!(radix.blocks.len(), 1); + radix.blocks[0].degree = Degree::new(1); + Ok(FheBool::new(BooleanBlock::new_unchecked( + radix.blocks.pop().unwrap(), + ))) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.inner.verify(public_params, &public_key.key.key) + } +} + +/// A `CompactFheBoolList` tied to a Zero-Knowledge proof +/// +/// The zero-knowledge proof allows to verify that the ciphertext list is correctly +/// encrypted. +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactFheBoolList { + inner: ProvenCompactCiphertextList, +} + +impl Named for ProvenCompactFheBoolList { + const NAME: &'static str = "high_level_api::ProvenCompactFheBoolList"; +} + +impl ProvenCompactFheBoolList { + /// Encrypts the message while also generating the zero-knowledge proof + pub fn try_encrypt( + values: &[bool], + public_params: &CompactPkePublicParams, + key: &CompactPublicKey, + load: ZkComputeLoad, + ) -> crate::Result { + let values = values.iter().copied().map(u8::from).collect::>(); + let inner = key.key.key.encrypt_and_prove_radix_compact( + &values, + 1, /* num_blocks */ + public_params, + load, + )?; + Ok(Self { inner }) + } + + pub fn len(&self) -> usize { + self.inner.ciphertext_count() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Verifies the ciphertext and the proof + /// + /// If the proof and ciphertext are valid, it returns an `Ok` with + /// the underlying `FheBool`s. + pub fn verify_and_expand( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result> { + Ok(self + .inner + .verify_and_expand::(public_params, &public_key.key.key)? + .into_iter() + .map(|mut radix| { + assert_eq!(radix.blocks.len(), 1); + radix.blocks[0].degree = Degree::new(1); + FheBool::new(BooleanBlock::new_unchecked(radix.blocks.pop().unwrap())) + }) + .collect()) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.inner.verify(public_params, &public_key.key.key) + } +} diff --git a/tfhe/src/high_level_api/config.rs b/tfhe/src/high_level_api/config.rs index cca6cf56a6..cce4aec994 100644 --- a/tfhe/src/high_level_api/config.rs +++ b/tfhe/src/high_level_api/config.rs @@ -1,7 +1,7 @@ use crate::high_level_api::keys::IntegerConfig; /// The config type -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Config { pub(crate) inner: IntegerConfig, } diff --git a/tfhe/src/high_level_api/errors.rs b/tfhe/src/high_level_api/errors.rs index 83a1ecb5bc..19c6406a40 100644 --- a/tfhe/src/high_level_api/errors.rs +++ b/tfhe/src/high_level_api/errors.rs @@ -38,43 +38,3 @@ impl Display for UninitializedServerKey { } impl std::error::Error for UninitializedServerKey {} - -/// Error when trying to create a short integer from a value that was too big to be represented -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct OutOfRangeError; - -impl Display for OutOfRangeError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "Value is out of range") - } -} - -impl std::error::Error for OutOfRangeError {} - -#[non_exhaustive] -#[derive(Debug, Eq, PartialEq)] -pub enum Error { - OutOfRange, - UninitializedServerKey, -} - -impl From for Error { - fn from(_: OutOfRangeError) -> Self { - Self::OutOfRange - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::OutOfRange => { - write!(f, "{OutOfRangeError}") - } - Self::UninitializedServerKey => { - write!(f, "{UninitializedServerKey}") - } - } - } -} - -impl std::error::Error for Error {} diff --git a/tfhe/src/high_level_api/integers/signed/compact.rs b/tfhe/src/high_level_api/integers/signed/compact.rs index b325ac2b5e..63264c683c 100644 --- a/tfhe/src/high_level_api/integers/signed/compact.rs +++ b/tfhe/src/high_level_api/integers/signed/compact.rs @@ -66,7 +66,7 @@ where T: crate::integer::block_decomposition::DecomposableInto, Id: FheIntId, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &CompactPublicKey) -> Result { let id = Id::default(); @@ -169,7 +169,7 @@ where T: crate::integer::block_decomposition::DecomposableInto, Id: FheIntId, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(values: &'a [T], key: &CompactPublicKey) -> Result { let id = Id::default(); diff --git a/tfhe/src/high_level_api/integers/signed/compressed.rs b/tfhe/src/high_level_api/integers/signed/compressed.rs index aaf3f1650d..1153bc4edd 100644 --- a/tfhe/src/high_level_api/integers/signed/compressed.rs +++ b/tfhe/src/high_level_api/integers/signed/compressed.rs @@ -88,7 +88,7 @@ where Id: FheIntId, T: DecomposableInto + SignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &ClientKey) -> Result { let integer_client_key = &key.key.key; diff --git a/tfhe/src/high_level_api/integers/signed/encrypt.rs b/tfhe/src/high_level_api/integers/signed/encrypt.rs index d6747ba670..8d60710d72 100644 --- a/tfhe/src/high_level_api/integers/signed/encrypt.rs +++ b/tfhe/src/high_level_api/integers/signed/encrypt.rs @@ -43,7 +43,7 @@ where Id: FheIntId, T: DecomposableInto + SignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &ClientKey) -> Result { let ciphertext = key @@ -59,7 +59,7 @@ where Id: FheIntId, T: DecomposableInto + SignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &PublicKey) -> Result { let ciphertext = key @@ -74,7 +74,7 @@ where Id: FheIntId, T: DecomposableInto + SignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &CompressedPublicKey) -> Result { let ciphertext = key @@ -89,7 +89,7 @@ where Id: FheIntId, T: DecomposableInto + SignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &CompactPublicKey) -> Result { let ciphertext = key @@ -105,7 +105,7 @@ where T: DecomposableInto, Id: FheIntId, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; /// Creates a trivial encryption of a signed integer. /// diff --git a/tfhe/src/high_level_api/integers/signed/mod.rs b/tfhe/src/high_level_api/integers/signed/mod.rs index cb0ffdeb69..cc74f2c651 100644 --- a/tfhe/src/high_level_api/integers/signed/mod.rs +++ b/tfhe/src/high_level_api/integers/signed/mod.rs @@ -9,6 +9,8 @@ mod scalar_ops; mod static_; #[cfg(test)] mod tests; +#[cfg(feature = "zk-pok-experimental")] +mod zk; pub use base::{FheInt, FheIntId}; pub use compact::{CompactFheInt, CompactFheIntList}; diff --git a/tfhe/src/high_level_api/integers/signed/static_.rs b/tfhe/src/high_level_api/integers/signed/static_.rs index 13f0edf779..1bb144d981 100644 --- a/tfhe/src/high_level_api/integers/signed/static_.rs +++ b/tfhe/src/high_level_api/integers/signed/static_.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "zk-pok-experimental")] +use super::zk::{ProvenCompactFheInt, ProvenCompactFheIntList}; use crate::high_level_api::integers::signed::base::{FheInt, FheIntConformanceParams, FheIntId}; use crate::high_level_api::integers::signed::compact::{ CompactFheInt, CompactFheIntList, CompactFheIntListConformanceParams, @@ -57,6 +59,13 @@ macro_rules! static_int_type { #[cfg_attr(all(doc, not(doctest)), cfg(feature = "integer"))] pub type [] = CompactFheIntListConformanceParams<[]>; + + // Zero-knowledge Stuff + #[cfg(feature = "zk-pok-experimental")] + pub type [] = ProvenCompactFheInt<[]>; + + #[cfg(feature = "zk-pok-experimental")] + pub type [] = ProvenCompactFheIntList<[]>; } }; } diff --git a/tfhe/src/high_level_api/integers/signed/tests.rs b/tfhe/src/high_level_api/integers/signed/tests.rs index a07355fe4a..fe564ce9a6 100644 --- a/tfhe/src/high_level_api/integers/signed/tests.rs +++ b/tfhe/src/high_level_api/integers/signed/tests.rs @@ -801,3 +801,50 @@ fn test_safe_deserialize_conformant_compact_fhe_int32_list() { )); assert!(deserialized_list.is_conformant(¶ms)); } + +#[cfg(feature = "zk-pok-experimental")] +#[test] +fn test_fhe_int_zk() { + use crate::core_crypto::prelude::DynamicDistribution; + use crate::zk::{CompactPkeCrs, ZkComputeLoad}; + + let mut params = crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::new_t_uniform(9); + + let config = ConfigBuilder::with_custom_parameters(params, None).build(); + let crs = CompactPkeCrs::from_config(config, 32).unwrap(); + let ck = ClientKey::generate(config); + let pk = CompactPublicKey::new(&ck); + + let msg = random::(); + + let proven_compact_fhe_uint = crate::ProvenCompactFheInt32::try_encrypt( + msg, + crs.public_params(), + &pk, + ZkComputeLoad::Proof, + ) + .unwrap(); + let fhe_uint = proven_compact_fhe_uint + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted: i32 = fhe_uint.decrypt(&ck); + assert_eq!(decrypted, msg); + + let messages = (0..4).map(|_| random()).collect::>(); + let proven_compact_fhe_uint_list = crate::ProvenCompactFheInt32List::try_encrypt( + &messages, + crs.public_params(), + &pk, + ZkComputeLoad::Proof, + ) + .unwrap(); + let fhe_uints = proven_compact_fhe_uint_list + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted = fhe_uints + .iter() + .map(|fb| fb.decrypt(&ck)) + .collect::>(); + assert_eq!(decrypted.as_slice(), &messages); +} diff --git a/tfhe/src/high_level_api/integers/signed/zk.rs b/tfhe/src/high_level_api/integers/signed/zk.rs new file mode 100644 index 0000000000..402fccac1b --- /dev/null +++ b/tfhe/src/high_level_api/integers/signed/zk.rs @@ -0,0 +1,140 @@ +use crate::core_crypto::commons::math::random::{Deserialize, Serialize}; +use crate::core_crypto::prelude::SignedNumeric; +use crate::high_level_api::integers::FheIntId; +use crate::integer::block_decomposition::DecomposableInto; +use crate::integer::{ProvenCompactCiphertextList, SignedRadixCiphertext}; +use crate::named::Named; +use crate::zk::{CompactPkePublicParams, ZkComputeLoad, ZkVerificationOutCome}; +use crate::{CompactPublicKey, FheInt}; + +/// A `CompactFheInt` tied to a Zero-Knowledge proof +/// +/// The zero-knowledge proof allows to verify that the ciphertext is correctly +/// encrypted. +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactFheInt { + inner: ProvenCompactCiphertextList, + _id: Id, +} + +impl Named for ProvenCompactFheInt { + const NAME: &'static str = "high_level_api::ProvenCompactFheUintList"; +} + +impl ProvenCompactFheInt +where + Id: FheIntId, +{ + /// Encrypts the message while also generating the zero-knowledge proof + pub fn try_encrypt( + value: Clear, + public_params: &CompactPkePublicParams, + key: &CompactPublicKey, + load: ZkComputeLoad, + ) -> crate::Result + where + Clear: DecomposableInto + SignedNumeric, + { + let inner = key.key.key.encrypt_and_prove_radix_compact( + &[value], + Id::num_blocks(key.key.key.key.parameters.message_modulus()), + public_params, + load, + )?; + Ok(Self { + inner, + _id: Id::default(), + }) + } + + /// Verifies the ciphertext and the proof + /// + /// If the proof and ciphertext are valid, it returns an `Ok` with + /// the underlying `FheInt` + pub fn verify_and_expand( + self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result> { + let expanded_inner = self + .inner + .verify_and_expand_one::(public_params, &public_key.key.key)?; + Ok(FheInt::new(expanded_inner)) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.inner.verify(public_params, &public_key.key.key) + } +} + +/// A `CompactFheIntList` tied to a Zero-Knowledge proof +/// +/// The zero-knowledge proof allows to verify that the ciphertext list is correctly +/// encrypted. +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactFheIntList { + inner: ProvenCompactCiphertextList, + _id: Id, +} + +impl Named for ProvenCompactFheIntList { + const NAME: &'static str = "high_level_api::ProvenCompactFheIntList"; +} + +impl ProvenCompactFheIntList +where + Id: FheIntId, +{ + /// Encrypts the message while also generating the zero-knowledge proof + pub fn try_encrypt( + values: &[Clear], + public_params: &CompactPkePublicParams, + key: &CompactPublicKey, + load: ZkComputeLoad, + ) -> crate::Result + where + Clear: DecomposableInto + SignedNumeric, + { + let inner = key.key.key.encrypt_and_prove_radix_compact( + values, + Id::num_blocks(key.key.key.key.parameters.message_modulus()), + public_params, + load, + )?; + Ok(Self { + inner, + _id: Id::default(), + }) + } + + pub fn len(&self) -> usize { + self.inner.ciphertext_count() + } + + /// Verifies the ciphertext and the proof + /// + /// If the proof and ciphertext are valid, it returns an `Ok` with + /// the underlying `FheInt`s. + pub fn verify_and_expand( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result>> { + let expanded_inners = self + .inner + .verify_and_expand::(public_params, &public_key.key.key)?; + Ok(expanded_inners.into_iter().map(FheInt::new).collect()) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.inner.verify(public_params, &public_key.key.key) + } +} diff --git a/tfhe/src/high_level_api/integers/unsigned/compact.rs b/tfhe/src/high_level_api/integers/unsigned/compact.rs index 35eff5fb47..31b5de508b 100644 --- a/tfhe/src/high_level_api/integers/unsigned/compact.rs +++ b/tfhe/src/high_level_api/integers/unsigned/compact.rs @@ -69,7 +69,7 @@ where T: crate::integer::block_decomposition::DecomposableInto, Id: FheUintId, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &CompactPublicKey) -> Result { let ciphertext = key @@ -175,7 +175,7 @@ where T: crate::integer::block_decomposition::DecomposableInto, Id: FheUintId, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(values: &'a [T], key: &CompactPublicKey) -> Result { let ciphertext = key diff --git a/tfhe/src/high_level_api/integers/unsigned/compressed.rs b/tfhe/src/high_level_api/integers/unsigned/compressed.rs index 551d298fa0..c872b600ca 100644 --- a/tfhe/src/high_level_api/integers/unsigned/compressed.rs +++ b/tfhe/src/high_level_api/integers/unsigned/compressed.rs @@ -91,7 +91,7 @@ where Id: FheUintId, T: DecomposableInto + UnsignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &ClientKey) -> Result { let inner = key diff --git a/tfhe/src/high_level_api/integers/unsigned/encrypt.rs b/tfhe/src/high_level_api/integers/unsigned/encrypt.rs index 83b69f7347..bb3ef27e07 100644 --- a/tfhe/src/high_level_api/integers/unsigned/encrypt.rs +++ b/tfhe/src/high_level_api/integers/unsigned/encrypt.rs @@ -45,7 +45,7 @@ where Id: FheUintId, T: DecomposableInto + UnsignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &ClientKey) -> Result { let cpu_ciphertext = key @@ -65,7 +65,7 @@ where Id: FheUintId, T: DecomposableInto + UnsignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &PublicKey) -> Result { let cpu_ciphertext = key @@ -84,7 +84,7 @@ where Id: FheUintId, T: DecomposableInto + UnsignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &CompressedPublicKey) -> Result { let cpu_ciphertext = key @@ -102,7 +102,7 @@ where Id: FheUintId, T: DecomposableInto + UnsignedNumeric, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt(value: T, key: &CompactPublicKey) -> Result { let cpu_ciphertext = key @@ -121,7 +121,7 @@ where T: DecomposableInto + UnsignedNumeric, Id: FheUintId, { - type Error = crate::high_level_api::errors::Error; + type Error = crate::Error; fn try_encrypt_trivial(value: T) -> Result { global_state::with_internal_keys(|key| match key { diff --git a/tfhe/src/high_level_api/integers/unsigned/mod.rs b/tfhe/src/high_level_api/integers/unsigned/mod.rs index 0d70967a54..1f03d7fb71 100644 --- a/tfhe/src/high_level_api/integers/unsigned/mod.rs +++ b/tfhe/src/high_level_api/integers/unsigned/mod.rs @@ -23,3 +23,5 @@ mod overflowing_ops; mod scalar_ops; #[cfg(test)] mod tests; +#[cfg(feature = "zk-pok-experimental")] +mod zk; diff --git a/tfhe/src/high_level_api/integers/unsigned/static_.rs b/tfhe/src/high_level_api/integers/unsigned/static_.rs index b436e2a52a..59816e51f5 100644 --- a/tfhe/src/high_level_api/integers/unsigned/static_.rs +++ b/tfhe/src/high_level_api/integers/unsigned/static_.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "zk-pok-experimental")] +use super::zk::{ProvenCompactFheUint, ProvenCompactFheUintList}; use crate::high_level_api::integers::unsigned::base::{ FheUint, FheUintConformanceParams, FheUintId, }; @@ -56,6 +58,12 @@ macro_rules! static_int_type { #[cfg_attr(all(doc, not(doctest)), cfg(feature = "integer"))] pub type [] = CompactFheUintListConformanceParams<[]>; + + #[cfg(feature = "zk-pok-experimental")] + pub type [] = ProvenCompactFheUint<[]>; + + #[cfg(feature = "zk-pok-experimental")] + pub type [] = ProvenCompactFheUintList<[]>; } }; } diff --git a/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs b/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs index de3dddaaeb..46da9237b0 100644 --- a/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs +++ b/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs @@ -10,7 +10,7 @@ use crate::{ CompressedPublicKey, Config, FheInt16, FheInt32, FheInt8, FheUint128, FheUint16, FheUint256, FheUint32, FheUint32ConformanceParams, }; -use rand::{random, Rng}; +use rand::prelude::*; fn setup_cpu(params: Option>) -> ClientKey { let config = params @@ -543,3 +543,49 @@ fn test_safe_deserialize_conformant_compact_fhe_uint32_list() { assert_eq!(decrypted, expected); } } + +#[cfg(feature = "zk-pok-experimental")] +#[test] +fn test_fhe_uint_zk() { + use crate::zk::{CompactPkeCrs, ZkComputeLoad}; + + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::new_t_uniform(9); + + let config = ConfigBuilder::with_custom_parameters(params, None).build(); + let crs = CompactPkeCrs::from_config(config, 32).unwrap(); + let ck = ClientKey::generate(config); + let pk = CompactPublicKey::new(&ck); + + let msg = random::(); + + let proven_compact_fhe_uint = crate::ProvenCompactFheUint32::try_encrypt( + msg, + crs.public_params(), + &pk, + ZkComputeLoad::Proof, + ) + .unwrap(); + let fhe_uint = proven_compact_fhe_uint + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted: u32 = fhe_uint.decrypt(&ck); + assert_eq!(decrypted, msg); + + let messages = (0..4).map(|_| random()).collect::>(); + let proven_compact_fhe_uint_list = crate::ProvenCompactFheUint32List::try_encrypt( + &messages, + crs.public_params(), + &pk, + ZkComputeLoad::Proof, + ) + .unwrap(); + let fhe_uints = proven_compact_fhe_uint_list + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted = fhe_uints + .iter() + .map(|fb| fb.decrypt(&ck)) + .collect::>(); + assert_eq!(decrypted.as_slice(), &messages); +} diff --git a/tfhe/src/high_level_api/integers/unsigned/zk.rs b/tfhe/src/high_level_api/integers/unsigned/zk.rs new file mode 100644 index 0000000000..3f0818f7c2 --- /dev/null +++ b/tfhe/src/high_level_api/integers/unsigned/zk.rs @@ -0,0 +1,140 @@ +use super::FheUintId; +use crate::core_crypto::commons::math::random::{Deserialize, Serialize}; +use crate::core_crypto::prelude::UnsignedNumeric; +use crate::integer::block_decomposition::DecomposableInto; +use crate::integer::{ProvenCompactCiphertextList, RadixCiphertext}; +use crate::named::Named; +use crate::zk::{CompactPkePublicParams, ZkComputeLoad, ZkVerificationOutCome}; +use crate::{CompactPublicKey, FheUint}; + +/// A `CompactFheUint` tied to a Zero-Knowledge proof +/// +/// The zero-knowledge proof allows to verify that the ciphertext is correctly +/// encrypted. +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactFheUint { + inner: ProvenCompactCiphertextList, + _id: Id, +} + +impl Named for ProvenCompactFheUint { + const NAME: &'static str = "high_level_api::ProvenCompactFheUint"; +} + +impl ProvenCompactFheUint +where + Id: FheUintId, +{ + /// Encrypts the message while also generating the zero-knowledge proof + pub fn try_encrypt( + value: Clear, + public_params: &CompactPkePublicParams, + key: &CompactPublicKey, + load: ZkComputeLoad, + ) -> crate::Result + where + Clear: DecomposableInto + UnsignedNumeric, + { + let inner = key.key.key.encrypt_and_prove_radix_compact( + &[value], + Id::num_blocks(key.key.key.key.parameters.message_modulus()), + public_params, + load, + )?; + Ok(Self { + inner, + _id: Id::default(), + }) + } + + /// Verifies the ciphertext and the proof + /// + /// If the proof and ciphertext are valid, it returns an `Ok` with + /// the underlying `FheUint` + pub fn verify_and_expand( + self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result> { + let expanded_inner = self + .inner + .verify_and_expand_one::(public_params, &public_key.key.key)?; + Ok(FheUint::new(expanded_inner)) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.inner.verify(public_params, &public_key.key.key) + } +} + +/// A `CompactFheUintList` tied to a Zero-Knowledge proof +/// +/// The zero-knowledge proof allows to verify that the ciphertext list is correctly +/// encrypted. +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactFheUintList { + inner: ProvenCompactCiphertextList, + _id: Id, +} + +impl Named for ProvenCompactFheUintList { + const NAME: &'static str = "high_level_api::ProvenCompactFheUintList"; +} + +impl ProvenCompactFheUintList +where + Id: FheUintId, +{ + /// Encrypts the message while also generating the zero-knowledge proof + pub fn try_encrypt( + values: &[Clear], + public_params: &CompactPkePublicParams, + key: &CompactPublicKey, + load: ZkComputeLoad, + ) -> crate::Result + where + Clear: DecomposableInto + UnsignedNumeric, + { + let inner = key.key.key.encrypt_and_prove_radix_compact( + values, + Id::num_blocks(key.key.key.key.parameters.message_modulus()), + public_params, + load, + )?; + Ok(Self { + inner, + _id: Id::default(), + }) + } + + pub fn len(&self) -> usize { + self.inner.ciphertext_count() + } + + /// Verifies the ciphertext and the proof + /// + /// If the proof and ciphertext are valid, it returns an `Ok` with + /// the underlying `FheUint`s. + pub fn verify_and_expand( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result>> { + let expanded_inners = self + .inner + .verify_and_expand::(public_params, &public_key.key.key)?; + Ok(expanded_inners.into_iter().map(FheUint::new).collect()) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.inner.verify(public_params, &public_key.key.key) + } +} diff --git a/tfhe/src/high_level_api/mod.rs b/tfhe/src/high_level_api/mod.rs index ebb1457230..cb152de5fd 100644 --- a/tfhe/src/high_level_api/mod.rs +++ b/tfhe/src/high_level_api/mod.rs @@ -23,6 +23,13 @@ macro_rules! expand_pub_use_fhe_type( )* }; + #[cfg(feature = "zk-pok-experimental")] + pub use $module_path::{ + $( + [], + [], + )* + }; } } ); @@ -30,7 +37,6 @@ macro_rules! expand_pub_use_fhe_type( pub use crate::core_crypto::commons::math::random::Seed; pub use crate::integer::oprf::SignedRandomizationSpec; 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 integers::{ @@ -51,6 +57,8 @@ pub use crate::high_level_api::booleans::{ CompactFheBool, CompactFheBoolList, CompactFheBoolListConformanceParams, CompressedFheBool, FheBool, FheBoolConformanceParams, }; +#[cfg(feature = "zk-pok-experimental")] +pub use crate::high_level_api::booleans::{ProvenCompactFheBool, ProvenCompactFheBoolList}; expand_pub_use_fhe_type!( pub use crate::high_level_api::integers{ FheUint2, FheUint4, FheUint6, FheUint8, FheUint10, FheUint12, FheUint14, FheUint16, @@ -60,6 +68,7 @@ expand_pub_use_fhe_type!( FheInt32, FheInt64, FheInt128, FheInt160, FheInt256 }; ); + pub use safe_serialize::safe_serialize; mod config; @@ -68,12 +77,14 @@ mod keys; mod traits; mod booleans; -pub mod errors; +mod errors; mod integers; pub(in crate::high_level_api) mod details; /// The tfhe prelude. pub mod prelude; +#[cfg(feature = "zk-pok-experimental")] +mod zk; /// Devices supported by tfhe-rs #[derive(Copy, Clone, PartialEq, Eq, Debug)] diff --git a/tfhe/src/high_level_api/tests/mod.rs b/tfhe/src/high_level_api/tests/mod.rs index e81864a8fd..a4ea1d7b1e 100644 --- a/tfhe/src/high_level_api/tests/mod.rs +++ b/tfhe/src/high_level_api/tests/mod.rs @@ -99,9 +99,9 @@ fn test_with_seed() { let builder = ConfigBuilder::default(); 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 cks1 = ClientKey::generate_with_seed(config, Seed(125)); + let cks2 = ClientKey::generate(config); + let cks3 = ClientKey::generate_with_seed(config, Seed(125)); let cks4 = ClientKey::generate_with_seed(config, Seed(127)); let cks1_serialized = bincode::serialize(&cks1).unwrap(); diff --git a/tfhe/src/high_level_api/zk.rs b/tfhe/src/high_level_api/zk.rs new file mode 100644 index 0000000000..8b11d6c73b --- /dev/null +++ b/tfhe/src/high_level_api/zk.rs @@ -0,0 +1,11 @@ +use crate::zk::CompactPkeCrs; +use crate::Config; + +impl CompactPkeCrs { + pub fn from_config(config: Config, max_bit_size: usize) -> crate::Result { + let max_num_message = + max_bit_size / config.inner.block_parameters.message_modulus().0.ilog2() as usize; + let crs = Self::from_shortint_params(config.inner.block_parameters, max_num_message)?; + Ok(crs) + } +} diff --git a/tfhe/src/integer/mod.rs b/tfhe/src/integer/mod.rs index 30d0c832a7..d57cfc607f 100755 --- a/tfhe/src/integer/mod.rs +++ b/tfhe/src/integer/mod.rs @@ -65,6 +65,11 @@ pub mod wopbs; #[cfg(feature = "gpu")] pub mod gpu; +#[cfg(feature = "zk-pok-experimental")] +mod zk; + +#[cfg(feature = "zk-pok-experimental")] +pub use zk::ProvenCompactCiphertextList; pub use bigint::i256::I256; pub use bigint::i512::I512; @@ -76,7 +81,9 @@ pub use ciphertext::{ SignedRadixCiphertext, }; pub use client_key::{ClientKey, CrtClientKey, RadixClientKey}; -pub use public_key::{CompressedCompactPublicKey, CompressedPublicKey, PublicKey}; +pub use public_key::{ + CompactPublicKey, CompressedCompactPublicKey, CompressedPublicKey, PublicKey, +}; pub use server_key::{CheckError, CompressedServerKey, ServerKey}; /// Enum to indicate which kind of computations the [`ServerKey`] will be performing, this changes diff --git a/tfhe/src/integer/server_key/radix_parallel/ilog2.rs b/tfhe/src/integer/server_key/radix_parallel/ilog2.rs index 98bba139b3..79b528d4ef 100644 --- a/tfhe/src/integer/server_key/radix_parallel/ilog2.rs +++ b/tfhe/src/integer/server_key/radix_parallel/ilog2.rs @@ -241,6 +241,7 @@ impl ServerKey { let counter_num_blocks = ((num_bits_in_ciphertext - 1).ilog2() + 1 + 1) .div_ceil(self.message_modulus().0.ilog2()) as usize; + // 11111000 // x.ilog2() = (x.num_bit() - 1) - x.leading_zeros() // - (x.num_bit() - 1) is trivially known // - we can get leading zeros via a sum diff --git a/tfhe/src/integer/zk.rs b/tfhe/src/integer/zk.rs new file mode 100644 index 0000000000..2e038ab885 --- /dev/null +++ b/tfhe/src/integer/zk.rs @@ -0,0 +1,139 @@ +use crate::integer::block_decomposition::{BlockDecomposer, DecomposableInto}; +use crate::integer::encryption::KnowsMessageModulus; +use crate::integer::public_key::CompactPublicKey; +use crate::integer::IntegerRadixCiphertext; +use crate::zk::{CompactPkePublicParams, ZkComputeLoad, ZkVerificationOutCome}; +use serde::{Deserialize, Serialize}; + +impl CompactPublicKey { + pub fn encrypt_and_prove_radix_compact>( + &self, + messages: &[T], + num_blocks_per_integer: usize, + public_params: &CompactPkePublicParams, + load: ZkComputeLoad, + ) -> crate::Result { + let messages = messages + .iter() + .copied() + .flat_map(|message| { + BlockDecomposer::new(message, self.key.message_modulus().0.ilog2()) + .iter_as::() + .take(num_blocks_per_integer) + }) + .collect::>(); + + let proved_list = self + .key + .encrypt_and_prove_slice(&messages, public_params, load)?; + + Ok(ProvenCompactCiphertextList { + proved_list, + num_blocks_per_integer, + }) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactCiphertextList { + pub(crate) proved_list: crate::shortint::ciphertext::ProvenCompactCiphertextList, + // 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_per_integer: usize, +} + +impl ProvenCompactCiphertextList { + pub fn verify_and_expand_one( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result { + let blocks = self + .proved_list + .verify_and_expand(public_params, &public_key.key)?; + assert_eq!(blocks.len(), self.num_blocks_per_integer); + + Ok(T::from_blocks(blocks)) + } + + pub fn ciphertext_count(&self) -> usize { + self.proved_list.ciphertext_count() / self.num_blocks_per_integer + } + + pub fn verify_and_expand( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result> { + let blocks = self + .proved_list + .verify_and_expand(public_params, &public_key.key)?; + + let mut integers = Vec::with_capacity(self.ciphertext_count()); + let mut blocks_iter = blocks.into_iter(); + for _ in 0..self.ciphertext_count() { + let radix_blocks = blocks_iter + .by_ref() + .take(self.num_blocks_per_integer) + .collect::>(); + integers.push(T::from_blocks(radix_blocks)); + } + Ok(integers) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + self.proved_list.verify(public_params, &public_key.key) + } +} + +#[cfg(test)] +mod tests { + use crate::integer::{ClientKey, CompactPublicKey}; + use crate::shortint::parameters::DynamicDistribution; + use crate::shortint::prelude::PARAM_MESSAGE_2_CARRY_2_KS_PBS; + use crate::zk::{CompactPkeCrs, ZkComputeLoad}; + use rand::random; + + #[test] + fn test_zk_compact_ciphertext_list_encryption_ci_run_filter() { + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::new_t_uniform(9); + + let num_blocks = 4usize; + let modulus = (params.message_modulus.0 as u64) + .checked_pow(num_blocks as u32) + .unwrap(); + + let crs = CompactPkeCrs::from_shortint_params(params, 512).unwrap(); + let cks = ClientKey::new(params); + let pk = CompactPublicKey::new(&cks); + + let msgs = (0..512) + .map(|_| random::() % modulus) + .collect::>(); + + let proven_ct = pk + .encrypt_and_prove_radix_compact( + &msgs, + num_blocks, + crs.public_params(), + ZkComputeLoad::Proof, + ) + .unwrap(); + assert!(proven_ct.verify(crs.public_params(), &pk).is_valid()); + + let expanded = proven_ct + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted = expanded + .iter() + .map(|ciphertext| cks.decrypt_radix(ciphertext)) + .collect::>(); + assert_eq!(msgs, decrypted); + } +} diff --git a/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs b/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs index 4f1ae35cd7..58bc7e73a4 100644 --- a/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs +++ b/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs @@ -91,6 +91,9 @@ macro_rules! create_wrapper_type_non_native_type ( compressed_type_name: $compressed_type_name:ident, compact_type_name: $compact_type_name:ident, compact_list_type_name: $compact_list_type_name:ident, + proven_type: $proven_type:ident, + proven_compact_type_name: $proven_compact_type_name:ident, + proven_compact_list_type_name: $proven_compact_list_type_name:ident, rust_type: $rust_type:ty $(,)? } ) => { @@ -376,6 +379,146 @@ macro_rules! create_wrapper_type_non_native_type ( }) } } + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + pub struct $proven_compact_type_name(pub(crate) crate::high_level_api::$proven_compact_type_name); + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + impl $proven_compact_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + value: JsValue, + public_params: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey, + compute_load: crate::js_on_wasm_api::js_high_level_api::zk::ZkComputeLoad, + ) -> Result<$proven_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::$proven_compact_type_name::try_encrypt( + value, + &public_params.0, + &public_key.0, + compute_load.into() + ).map($proven_compact_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn verifies( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> bool { + self.0.verify(&public_parameters.0, &public_key.0).is_valid() + } + + #[wasm_bindgen] + pub fn verify_and_expand( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> Result<$type_name, JsError> { + catch_panic(||{ + self.0 + .clone() + .verify_and_expand(&public_parameters.0, &public_key.0) + .map($type_name) + .unwrap() + }) + } + + #[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<$proven_compact_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($proven_compact_type_name) + .map_err(into_js_error) + }) + } + } + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + pub struct $proven_compact_list_type_name(pub(crate) crate::high_level_api::$proven_compact_list_type_name); + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + impl $proven_compact_list_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + values: Vec, + public_params: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey, + compute_load: crate::js_on_wasm_api::js_high_level_api::zk::ZkComputeLoad, + ) -> Result<$proven_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::$proven_compact_list_type_name::try_encrypt( + &values, + &public_params.0, + &public_key.0, + compute_load.into() + ).map($proven_compact_list_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn verifies( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> bool { + self.0.verify(&public_parameters.0, &public_key.0).is_valid() + } + + #[wasm_bindgen] + pub fn verify_and_expand( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> Result, JsError> { + catch_panic(||{ + self.0 + .clone() + .verify_and_expand(&public_parameters.0, &public_key.0) + .map(|vec| vec.into_iter().map($type_name).collect::>()) + .unwrap() + }) + } + + #[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<$proven_compact_list_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($proven_compact_list_type_name) + .map_err(into_js_error) + }) + } + } + }; ( @@ -385,6 +528,9 @@ macro_rules! create_wrapper_type_non_native_type ( compressed_type_name: $compressed_type_name:ident, compact_type_name: $compact_type_name:ident, compact_list_type_name: $compact_list_type_name:ident, + proven_type: $proven_type:ident, + proven_compact_type_name: $proven_compact_type_name:ident, + proven_compact_list_type_name: $proven_compact_list_type_name:ident, rust_type: $rust_type:ty $(,)? } ),* @@ -397,6 +543,9 @@ macro_rules! create_wrapper_type_non_native_type ( compressed_type_name: $compressed_type_name, compact_type_name: $compact_type_name, compact_list_type_name: $compact_list_type_name, + proven_type: $proven_type, + proven_compact_type_name: $proven_compact_type_name, + proven_compact_list_type_name: $proven_compact_list_type_name, rust_type: $rust_type } ); @@ -405,25 +554,34 @@ macro_rules! create_wrapper_type_non_native_type ( ); create_wrapper_type_non_native_type!( - { - type_name: FheUint160, - compressed_type_name: CompressedFheUint160, - compact_type_name: CompactFheUint160, - compact_list_type_name: CompactFheUint160List, - rust_type: U256, - }, { type_name: FheUint128, compressed_type_name: CompressedFheUint128, compact_type_name: CompactFheUint128, compact_list_type_name: CompactFheUint128List, + proven_type: ProvenFheUint128, + proven_compact_type_name: ProvenCompactFheUint128, + proven_compact_list_type_name: ProvenCompactFheUint128List, rust_type: u128, }, + { + type_name: FheUint160, + compressed_type_name: CompressedFheUint160, + compact_type_name: CompactFheUint160, + compact_list_type_name: CompactFheUint160List, + proven_type: ProvenFheUint160, + proven_compact_type_name: ProvenCompactFheUint160, + proven_compact_list_type_name: ProvenCompactFheUint160List, + rust_type: U256, + }, { type_name: FheUint256, compressed_type_name: CompressedFheUint256, compact_type_name: CompactFheUint256, compact_list_type_name: CompactFheUint256List, + proven_type: ProvenFheUint256, + proven_compact_type_name: ProvenCompactFheUint256, + proven_compact_list_type_name: ProvenCompactFheUint256List, rust_type: U256, }, // Signed @@ -432,6 +590,9 @@ create_wrapper_type_non_native_type!( compressed_type_name: CompressedFheInt128, compact_type_name: CompactFheInt128, compact_list_type_name: CompactFheInt128List, + proven_type: ProvenFheInt128, + proven_compact_type_name: ProvenCompactFheInt128, + proven_compact_list_type_name: ProvenCompactFheInt128List, rust_type: i128, }, { @@ -439,6 +600,9 @@ create_wrapper_type_non_native_type!( compressed_type_name: CompressedFheInt160, compact_type_name: CompactFheInt160, compact_list_type_name: CompactFheInt160List, + proven_type: ProvenFheInt160, + proven_compact_type_name: ProvenCompactFheInt160, + proven_compact_list_type_name: ProvenCompactFheInt160List, rust_type: I256, }, { @@ -446,6 +610,9 @@ create_wrapper_type_non_native_type!( compressed_type_name: CompressedFheInt256, compact_type_name: CompactFheInt256, compact_list_type_name: CompactFheInt256List, + proven_type: ProvenFheInt256, + proven_compact_type_name: ProvenCompactFheInt256, + proven_compact_list_type_name: ProvenCompactFheInt256List, rust_type: I256, }, ); @@ -460,6 +627,9 @@ macro_rules! create_wrapper_type_that_has_native_type ( compressed_type_name: $compressed_type_name:ident, compact_type_name: $compact_type_name:ident, compact_list_type_name: $compact_list_type_name:ident, + proven_type: $proven_type:ident, + proven_compact_type_name: $proven_compact_type_name:ident, + proven_compact_list_type_name: $proven_compact_list_type_name:ident, native_type: $native_type:ty $(,)? } ) => { @@ -708,6 +878,116 @@ macro_rules! create_wrapper_type_that_has_native_type ( } } + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + pub struct $proven_compact_type_name(pub(crate) crate::high_level_api::$proven_compact_type_name); + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + impl $proven_compact_type_name { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + value: $native_type, + public_params: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey, + compute_load: crate::js_on_wasm_api::js_high_level_api::zk::ZkComputeLoad, + ) -> Result<$proven_compact_type_name, JsError> { + catch_panic_result(|| { + crate::high_level_api::$proven_compact_type_name::try_encrypt( + value, + &public_params.0, + &public_key.0, + compute_load.into() + ).map($proven_compact_type_name) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn verifies( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> bool { + self.0.verify(&public_parameters.0, &public_key.0).is_valid() + } + + #[wasm_bindgen] + pub fn verify_and_expand( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> Result<$type_name, JsError> { + catch_panic(||{ + self.0 + .clone() + .verify_and_expand(&public_parameters.0, &public_key.0) + .map($type_name) + .unwrap() + }) + } + + #[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<$proven_compact_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($proven_compact_type_name) + .map_err(into_js_error) + }) + } + } + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + pub struct $proven_compact_list_type_name(pub(crate) crate::high_level_api::$proven_compact_list_type_name); + + #[cfg(feature = "zk-pok-experimental")] + #[wasm_bindgen] + impl $proven_compact_list_type_name { + #[wasm_bindgen] + pub fn verifies( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> bool { + self.0.verify(&public_parameters.0, &public_key.0).is_valid() + } + + #[wasm_bindgen] + pub fn verify_and_expand( + &self, + public_parameters: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey + ) -> Result, JsError> { + catch_panic(||{ + self.0 + .clone() + .verify_and_expand(&public_parameters.0, &public_key.0) + .map(|vec| vec.into_iter().map($type_name).collect::>()) + .unwrap() + }) + } + + #[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<$proven_compact_list_type_name, JsError> { + catch_panic_result(|| { + bincode::deserialize(buffer) + .map($proven_compact_list_type_name) + .map_err(into_js_error) + }) + } + } + }; ( $( @@ -716,6 +996,9 @@ macro_rules! create_wrapper_type_that_has_native_type ( compressed_type_name: $compressed_type_name:ident, compact_type_name: $compact_type_name:ident, compact_list_type_name: $compact_list_type_name:ident, + proven_type: $proven_type:ident, + proven_compact_type_name: $proven_compact_type_name:ident, + proven_compact_list_type_name: $proven_compact_list_type_name:ident, native_type: $native_type:ty $(,)? } ),* @@ -728,6 +1011,9 @@ macro_rules! create_wrapper_type_that_has_native_type ( compressed_type_name: $compressed_type_name, compact_type_name: $compact_type_name, compact_list_type_name: $compact_list_type_name, + proven_type: $proven_type, + proven_compact_type_name: $proven_compact_type_name, + proven_compact_list_type_name: $proven_compact_list_type_name, native_type: $native_type } ); @@ -741,6 +1027,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheBool, compact_type_name: CompactFheBool, compact_list_type_name: CompactFheBoolList, + proven_type: ProvenFheBool, + proven_compact_type_name: ProvenCompactFheBool, + proven_compact_list_type_name: ProvenCompactFheBoolList, native_type: bool, }, { @@ -748,6 +1037,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint2, compact_type_name: CompactFheUint2, compact_list_type_name: CompactFheUint2List, + proven_type: ProvenFheUint2, + proven_compact_type_name: ProvenCompactFheUint2, + proven_compact_list_type_name: ProvenCompactFheUint2List, native_type: u8, }, { @@ -755,6 +1047,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint4, compact_type_name: CompactFheUint4, compact_list_type_name: CompactFheUint4List, + proven_type: ProvenFheUint4, + proven_compact_type_name: ProvenCompactFheUint4, + proven_compact_list_type_name: ProvenCompactFheUint4List, native_type: u8, }, { @@ -762,6 +1057,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint6, compact_type_name: CompactFheUint6, compact_list_type_name: CompactFheUint6List, + proven_type: ProvenFheUint6, + proven_compact_type_name: ProvenCompactFheUint6, + proven_compact_list_type_name: ProvenCompactFheUint6List, native_type: u8, }, { @@ -769,6 +1067,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint8, compact_type_name: CompactFheUint8, compact_list_type_name: CompactFheUint8List, + proven_type: ProvenFheUint8, + proven_compact_type_name: ProvenCompactFheUint8, + proven_compact_list_type_name: ProvenCompactFheUint8List, native_type: u8, }, { @@ -776,6 +1077,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint10, compact_type_name: CompactFheUint10, compact_list_type_name: CompactFheUint10List, + proven_type: ProvenFheUint10, + proven_compact_type_name: ProvenCompactFheUint10, + proven_compact_list_type_name: ProvenCompactFheUint10List, native_type: u16, }, { @@ -783,6 +1087,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint12, compact_type_name: CompactFheUint12, compact_list_type_name: CompactFheUint12List, + proven_type: ProvenFheUint12, + proven_compact_type_name: ProvenCompactFheUint12, + proven_compact_list_type_name: ProvenCompactFheUint12List, native_type: u16, }, { @@ -790,6 +1097,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint14, compact_type_name: CompactFheUint14, compact_list_type_name: CompactFheUint14List, + proven_type: ProvenFheUint14, + proven_compact_type_name: ProvenCompactFheUint14, + proven_compact_list_type_name: ProvenCompactFheUint14List, native_type: u16, }, { @@ -797,6 +1107,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint16, compact_type_name: CompactFheUint16, compact_list_type_name: CompactFheUint16List, + proven_type: ProvenFheUint16, + proven_compact_type_name: ProvenCompactFheUint16, + proven_compact_list_type_name: ProvenCompactFheUint16List, native_type: u16, }, { @@ -804,6 +1117,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint32, compact_type_name: CompactFheUint32, compact_list_type_name: CompactFheUint32List, + proven_type: ProvenFheUint32, + proven_compact_type_name: ProvenCompactFheUint32, + proven_compact_list_type_name: ProvenCompactFheUint32List, native_type: u32, }, { @@ -811,6 +1127,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheUint64, compact_type_name: CompactFheUint64, compact_list_type_name: CompactFheUint64List, + proven_type: ProvenFheUint64, + proven_compact_type_name: ProvenCompactFheUint64, + proven_compact_list_type_name: ProvenCompactFheUint64List, native_type: u64, }, // Signed @@ -819,6 +1138,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt2, compact_type_name: CompactFheInt2, compact_list_type_name: CompactFheInt2List, + proven_type: ProvenFheInt2, + proven_compact_type_name: ProvenCompactFheInt2, + proven_compact_list_type_name: ProvenCompactFheInt2List, native_type: i8, }, { @@ -826,6 +1148,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt4, compact_type_name: CompactFheInt4, compact_list_type_name: CompactFheInt4List, + proven_type: ProvenFheInt4, + proven_compact_type_name: ProvenCompactFheInt4, + proven_compact_list_type_name: ProvenCompactFheInt4List, native_type: i8, }, { @@ -833,6 +1158,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt6, compact_type_name: CompactFheInt6, compact_list_type_name: CompactFheInt6List, + proven_type: ProvenFheInt6, + proven_compact_type_name: ProvenCompactFheInt6, + proven_compact_list_type_name: ProvenCompactFheInt6List, native_type: i8, }, { @@ -840,6 +1168,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt8, compact_type_name: CompactFheInt8, compact_list_type_name: CompactFheInt8List, + proven_type: ProvenFheInt8, + proven_compact_type_name: ProvenCompactFheInt8, + proven_compact_list_type_name: ProvenCompactFheInt8List, native_type: i8, }, { @@ -847,6 +1178,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt10, compact_type_name: CompactFheInt10, compact_list_type_name: CompactFheInt10List, + proven_type: ProvenFheInt10, + proven_compact_type_name: ProvenCompactFheInt10, + proven_compact_list_type_name: ProvenCompactFheInt10List, native_type: i16, }, { @@ -854,6 +1188,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt12, compact_type_name: CompactFheInt12, compact_list_type_name: CompactFheInt12List, + proven_type: ProvenFheInt12, + proven_compact_type_name: ProvenCompactFheInt12, + proven_compact_list_type_name: ProvenCompactFheInt12List, native_type: i16, }, { @@ -861,6 +1198,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt14, compact_type_name: CompactFheInt14, compact_list_type_name: CompactFheInt14List, + proven_type: ProvenFheInt14, + proven_compact_type_name: ProvenCompactFheInt14, + proven_compact_list_type_name: ProvenCompactFheInt14List, native_type: i16, }, { @@ -868,6 +1208,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt16, compact_type_name: CompactFheInt16, compact_list_type_name: CompactFheInt16List, + proven_type: ProvenFheInt16, + proven_compact_type_name: ProvenCompactFheInt16, + proven_compact_list_type_name: ProvenCompactFheInt16List, native_type: i16, }, { @@ -875,6 +1218,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt32, compact_type_name: CompactFheInt32, compact_list_type_name: CompactFheInt32List, + proven_type: ProvenFheInt32, + proven_compact_type_name: ProvenCompactFheInt32, + proven_compact_list_type_name: ProvenCompactFheInt32List, native_type: i32, }, { @@ -882,6 +1228,9 @@ create_wrapper_type_that_has_native_type!( compressed_type_name: CompressedFheInt64, compact_type_name: CompactFheInt64, compact_list_type_name: CompactFheInt64List, + proven_type: ProvenFheInt64, + proven_compact_type_name: ProvenCompactFheInt64, + proven_compact_list_type_name: ProvenCompactFheInt64List, native_type: i64, }, ); @@ -967,3 +1316,93 @@ impl CompactFheBoolList { }) } } + +#[cfg(feature = "zk-pok-experimental")] +macro_rules! define_prove_and_encrypt_list_with_compact_public_key { + ( + $( + {$proven_compact_list_type_name:ident, $native_type:ty} + ),* + $(,)? + ) => { + $( + #[wasm_bindgen] + impl $proven_compact_list_type_name { + + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + values: Vec<$native_type>, + public_params: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey, + compute_load: crate::js_on_wasm_api::js_high_level_api::zk::ZkComputeLoad, + ) -> Result<$proven_compact_list_type_name, JsError> { + catch_panic_result(|| { + $crate::high_level_api::$proven_compact_list_type_name::try_encrypt( + &values, + &public_params.0, + &public_key.0, + compute_load.into(), + ).map($proven_compact_list_type_name) + .map_err(into_js_error) + }) + } + } + )* + }; +} + +#[cfg(feature = "zk-pok-experimental")] +define_prove_and_encrypt_list_with_compact_public_key!( + {ProvenCompactFheUint2List, u8}, + {ProvenCompactFheUint4List, u8}, + {ProvenCompactFheUint6List, u8}, + {ProvenCompactFheUint8List, u8}, + {ProvenCompactFheUint12List, u16}, + {ProvenCompactFheUint14List, u16}, + {ProvenCompactFheUint16List, u16}, + {ProvenCompactFheUint32List, u32}, + {ProvenCompactFheUint64List, u64}, + // Signed + {ProvenCompactFheInt2List, i8}, + {ProvenCompactFheInt4List, i8}, + {ProvenCompactFheInt6List, i8}, + {ProvenCompactFheInt8List, i8}, + {ProvenCompactFheInt12List, i16}, + {ProvenCompactFheInt14List, i16}, + {ProvenCompactFheInt16List, i16}, + {ProvenCompactFheInt32List, i32}, + {ProvenCompactFheInt64List, i64}, +); + +#[cfg(feature = "zk-pok-experimental")] +#[allow(clippy::use_self)] +#[allow(clippy::needless_pass_by_value)] +#[wasm_bindgen] +impl ProvenCompactFheBoolList { + #[wasm_bindgen] + pub fn encrypt_with_compact_public_key( + values: Vec, + public_params: &crate::js_on_wasm_api::js_high_level_api::zk::CompactPkePublicParams, + public_key: &crate::js_on_wasm_api::js_high_level_api::keys::TfheCompactPublicKey, + compute_load: crate::js_on_wasm_api::js_high_level_api::zk::ZkComputeLoad, + ) -> Result { + catch_panic_result(|| { + let booleans = values + .iter() + .map(|jsvalue| { + jsvalue + .as_bool() + .ok_or_else(|| JsError::new("Value is not a boolean")) + }) + .collect::, JsError>>()?; + crate::high_level_api::ProvenCompactFheBoolList::try_encrypt( + &booleans, + &public_params.0, + &public_key.0, + compute_load.into(), + ) + .map(ProvenCompactFheBoolList) + .map_err(into_js_error) + }) + } +} diff --git a/tfhe/src/js_on_wasm_api/js_high_level_api/keys.rs b/tfhe/src/js_on_wasm_api/js_high_level_api/keys.rs index 920c8eb8e5..8b1fc3bf3a 100644 --- a/tfhe/src/js_on_wasm_api/js_high_level_api/keys.rs +++ b/tfhe/src/js_on_wasm_api/js_high_level_api/keys.rs @@ -15,7 +15,7 @@ pub struct TfheClientKey(pub(crate) hlapi::ClientKey); impl TfheClientKey { #[wasm_bindgen] pub fn generate(config: &TfheConfig) -> Result { - catch_panic(|| Self(hlapi::ClientKey::generate(config.0.clone()))) + catch_panic(|| Self(hlapi::ClientKey::generate(config.0))) } #[wasm_bindgen] @@ -26,7 +26,7 @@ impl TfheClientKey { 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)); + let key = hlapi::ClientKey::generate_with_seed(config.0, crate::Seed(seed)); Ok(Self(key)) }) } diff --git a/tfhe/src/js_on_wasm_api/js_high_level_api/mod.rs b/tfhe/src/js_on_wasm_api/js_high_level_api/mod.rs index 5f1106d110..208c9e1cf6 100644 --- a/tfhe/src/js_on_wasm_api/js_high_level_api/mod.rs +++ b/tfhe/src/js_on_wasm_api/js_high_level_api/mod.rs @@ -5,6 +5,8 @@ pub(crate) mod integers; // using Self does not work well with #[wasm_bindgen] macro #[allow(clippy::use_self)] pub(crate) mod keys; +#[cfg(feature = "zk-pok-experimental")] +mod zk; pub(crate) fn into_js_error(e: E) -> wasm_bindgen::JsError { wasm_bindgen::JsError::new(format!("{e:?}").as_str()) diff --git a/tfhe/src/js_on_wasm_api/js_high_level_api/zk.rs b/tfhe/src/js_on_wasm_api/js_high_level_api/zk.rs new file mode 100644 index 0000000000..0fcf327f51 --- /dev/null +++ b/tfhe/src/js_on_wasm_api/js_high_level_api/zk.rs @@ -0,0 +1,76 @@ +use wasm_bindgen::prelude::*; + +use crate::js_on_wasm_api::js_high_level_api::config::TfheConfig; +use crate::js_on_wasm_api::js_high_level_api::{catch_panic_result, into_js_error}; +use crate::js_on_wasm_api::shortint::ShortintParameters; + +#[derive(Copy, Clone, Eq, PartialEq)] +#[wasm_bindgen] +pub enum ZkComputeLoad { + Proof, + Verify, +} + +impl Into for ZkComputeLoad { + fn into(self) -> crate::zk::ZkComputeLoad { + match self { + Self::Proof => crate::zk::ZkComputeLoad::Proof, + Self::Verify => crate::zk::ZkComputeLoad::Verify, + } + } +} + +#[wasm_bindgen] +pub struct CompactPkeCrs(pub(crate) crate::core_crypto::entities::CompactPkeCrs); + +#[wasm_bindgen] +pub struct CompactPkePublicParams(pub(crate) crate::zk::CompactPkePublicParams); + +#[wasm_bindgen] +impl CompactPkePublicParams { + #[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(CompactPkePublicParams) + .map_err(into_js_error) + }) + } +} + +#[wasm_bindgen] +impl CompactPkeCrs { + #[wasm_bindgen] + pub fn from_parameters( + parameters: ShortintParameters, + max_num_message: usize, + ) -> Result { + catch_panic_result(|| { + crate::core_crypto::entities::CompactPkeCrs::from_shortint_params( + parameters.0, + max_num_message, + ) + .map(CompactPkeCrs) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn from_config(config: &TfheConfig, max_num_bits: usize) -> Result { + catch_panic_result(|| { + crate::core_crypto::entities::CompactPkeCrs::from_config(config.0, max_num_bits) + .map(CompactPkeCrs) + .map_err(into_js_error) + }) + } + + #[wasm_bindgen] + pub fn public_params(&self) -> CompactPkePublicParams { + CompactPkePublicParams(self.0.public_params().clone()) + } +} diff --git a/tfhe/src/js_on_wasm_api/shortint.rs b/tfhe/src/js_on_wasm_api/shortint.rs index 89fb69ffe7..21908319cf 100644 --- a/tfhe/src/js_on_wasm_api/shortint.rs +++ b/tfhe/src/js_on_wasm_api/shortint.rs @@ -30,13 +30,136 @@ pub struct Shortint {} #[wasm_bindgen] pub struct ShortintParameters(pub(crate) crate::shortint::ClassicPBSParameters); +#[wasm_bindgen] +impl ShortintParameters { + #[wasm_bindgen] + pub fn lwe_dimension(&self) -> usize { + self.0.lwe_dimension.0 + } + + #[wasm_bindgen] + pub fn set_lwe_dimension(&mut self, new_value: usize) { + self.0.lwe_dimension.0 = new_value; + } + + #[wasm_bindgen] + pub fn glwe_dimension(&self) -> usize { + self.0.glwe_dimension.0 + } + + #[wasm_bindgen] + pub fn set_glwe_dimension(&mut self, new_value: usize) { + self.0.glwe_dimension.0 = new_value; + } + + #[wasm_bindgen] + pub fn polynomial_size(&self) -> usize { + self.0.polynomial_size.0 + } + + #[wasm_bindgen] + pub fn set_polynomial_size(&mut self, new_value: usize) { + self.0.polynomial_size.0 = new_value; + } + + #[wasm_bindgen] + pub fn lwe_noise_distribution(&self) -> ShortintNoiseDistribution { + ShortintNoiseDistribution(self.0.lwe_noise_distribution) + } + + #[wasm_bindgen] + pub fn set_lwe_noise_distribution(&mut self, new_value: &ShortintNoiseDistribution) { + self.0.lwe_noise_distribution = new_value.0; + } + + #[wasm_bindgen] + pub fn glwe_noise_distribution(&self) -> ShortintNoiseDistribution { + ShortintNoiseDistribution(self.0.lwe_noise_distribution) + } + + #[wasm_bindgen] + pub fn set_glwe_noise_distribution(&mut self, new_value: &ShortintNoiseDistribution) { + self.0.glwe_noise_distribution = new_value.0; + } + + #[wasm_bindgen] + pub fn pbs_base_log(&self) -> usize { + self.0.pbs_base_log.0 + } + + #[wasm_bindgen] + pub fn set_pbs_base_log(&mut self, new_value: usize) { + self.0.pbs_base_log.0 = new_value; + } + + #[wasm_bindgen] + pub fn pbs_level(&self) -> usize { + self.0.pbs_level.0 + } + + #[wasm_bindgen] + pub fn set_pbs_level(&mut self, new_value: usize) { + self.0.pbs_level.0 = new_value; + } + + #[wasm_bindgen] + pub fn ks_base_log(&self) -> usize { + self.0.ks_base_log.0 + } + + #[wasm_bindgen] + pub fn set_ks_base_log(&mut self, new_value: usize) { + self.0.ks_base_log.0 = new_value; + } + + #[wasm_bindgen] + pub fn ks_level(&self) -> usize { + self.0.ks_level.0 + } + + #[wasm_bindgen] + pub fn set_ks_level(&mut self, new_value: usize) { + self.0.ks_level.0 = new_value; + } + + #[wasm_bindgen] + pub fn message_modulus(&self) -> usize { + self.0.message_modulus.0 + } + + #[wasm_bindgen] + pub fn set_message_modulus(&mut self, new_value: usize) { + self.0.message_modulus.0 = new_value; + } + + #[wasm_bindgen] + pub fn carry_modulus(&self) -> usize { + self.0.carry_modulus.0 + } + + #[wasm_bindgen] + pub fn set_carry_modulus(&mut self, new_value: usize) { + self.0.carry_modulus.0 = new_value; + } + + #[wasm_bindgen] + pub fn encryption_key_choice(&self) -> ShortintEncryptionKeyChoice { + self.0.encryption_key_choice.into() + } + + #[wasm_bindgen] + pub fn set_encryption_key_choice(&mut self, new_value: ShortintEncryptionKeyChoice) { + self.0.encryption_key_choice = new_value.into(); + } +} + #[wasm_bindgen] pub enum ShortintEncryptionKeyChoice { Big, Small, } -impl From for crate::shortint::parameters::EncryptionKeyChoice { +impl From for EncryptionKeyChoice { fn from(value: ShortintEncryptionKeyChoice) -> Self { match value { ShortintEncryptionKeyChoice::Big => Self::Big, @@ -45,6 +168,15 @@ impl From for crate::shortint::parameters::Encrypti } } +impl From for ShortintEncryptionKeyChoice { + fn from(value: EncryptionKeyChoice) -> Self { + match value { + EncryptionKeyChoice::Big => Self::Big, + EncryptionKeyChoice::Small => Self::Small, + } + } +} + #[wasm_bindgen] pub struct ShortintNoiseDistribution( pub(crate) crate::core_crypto::commons::math::random::DynamicDistribution, diff --git a/tfhe/src/lib.rs b/tfhe/src/lib.rs index 57f4a9ee2d..6bfc6c5677 100644 --- a/tfhe/src/lib.rs +++ b/tfhe/src/lib.rs @@ -129,3 +129,10 @@ pub mod safe_deserialization; pub mod conformance; pub mod named; + +pub mod error; +#[cfg(feature = "zk-pok-experimental")] +pub mod zk; + +pub use error::{Error, ErrorKind}; +pub type Result = std::result::Result; diff --git a/tfhe/src/shortint/ciphertext/mod.rs b/tfhe/src/shortint/ciphertext/mod.rs index bd11653188..b39060e84b 100644 --- a/tfhe/src/shortint/ciphertext/mod.rs +++ b/tfhe/src/shortint/ciphertext/mod.rs @@ -9,3 +9,8 @@ pub use compact_list::*; pub use compressed::*; pub use compressed_modulus_switched_ciphertext::*; pub use standard::*; +#[cfg(feature = "zk-pok-experimental")] +pub use zk::*; + +#[cfg(feature = "zk-pok-experimental")] +mod zk; diff --git a/tfhe/src/shortint/ciphertext/zk.rs b/tfhe/src/shortint/ciphertext/zk.rs new file mode 100644 index 0000000000..da1de3fde8 --- /dev/null +++ b/tfhe/src/shortint/ciphertext/zk.rs @@ -0,0 +1,194 @@ +use crate::core_crypto::algorithms::verify_lwe_compact_ciphertext_list; +use crate::core_crypto::prelude::verify_lwe_ciphertext; +use crate::shortint::ciphertext::CompactCiphertextList; +use crate::shortint::{Ciphertext, CompactPublicKey, EncryptionKeyChoice}; +use crate::zk::{CompactPkeCrs, CompactPkeProof, CompactPkePublicParams, ZkVerificationOutCome}; +use rayon::prelude::*; +use serde::{Deserialize, Serialize}; + +impl CompactPkeCrs { + /// Construct the CRS that corresponds to the given parameters + /// + /// max_num_message is how many message a single proof can prove + pub fn from_shortint_params( + params: impl Into, + max_num_message: usize, + ) -> crate::Result { + let params = params.into(); + let (size, noise_distribution) = match params.encryption_key_choice() { + EncryptionKeyChoice::Big => { + let size = params + .glwe_dimension() + .to_equivalent_lwe_dimension(params.polynomial_size()); + (size, params.glwe_noise_distribution()) + } + EncryptionKeyChoice::Small => (params.lwe_dimension(), params.lwe_noise_distribution()), + }; + + let mut plaintext_modulus = (params.message_modulus().0 * params.carry_modulus().0) as u64; + // Our plaintext modulus does not take into account the bit of padding + plaintext_modulus *= 2; + + crate::shortint::engine::ShortintEngine::with_thread_local_mut(|engine| { + Self::new( + size, + max_num_message, + noise_distribution, + params.ciphertext_modulus(), + plaintext_modulus, + &mut engine.random_generator, + ) + }) + } +} + +/// A Ciphertext tied to a zero-knowledge proof +/// +/// The proof can only be generated during the encryption with a [CompactPublicKey] +pub struct ProvenCiphertext { + pub(crate) ciphertext: Ciphertext, + pub(crate) proof: CompactPkeProof, +} + +impl ProvenCiphertext { + pub fn ciphertext(&self) -> &Ciphertext { + &self.ciphertext + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + verify_lwe_ciphertext( + &self.ciphertext.ct, + &public_key.key, + &self.proof, + public_params, + ) + } +} + +/// A List of CompactCiphertext with their zero-knowledge proofs +/// +/// The proofs can only be generated during the encryption with a [CompactPublicKey] +#[derive(Clone, Serialize, Deserialize)] +pub struct ProvenCompactCiphertextList { + pub(crate) proved_lists: Vec<(CompactCiphertextList, CompactPkeProof)>, +} + +impl ProvenCompactCiphertextList { + pub fn ciphertext_count(&self) -> usize { + self.proved_lists + .iter() + .map(|(list, _)| list.ct_list.lwe_ciphertext_count().0) + .sum() + } + + pub fn verify_and_expand( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> crate::Result> { + let not_all_valid = self.proved_lists.par_iter().any(|(ct_list, proof)| { + verify_lwe_compact_ciphertext_list( + &ct_list.ct_list, + &public_key.key, + proof, + public_params, + ) + .is_invalid() + }); + + if not_all_valid { + return Err(crate::ErrorKind::InvalidZkProof.into()); + } + + let expanded = self + .proved_lists + .iter() + .flat_map(|(ct_list, _proof)| ct_list.expand()) + .collect(); + + Ok(expanded) + } + + pub fn verify( + &self, + public_params: &CompactPkePublicParams, + public_key: &CompactPublicKey, + ) -> ZkVerificationOutCome { + let all_valid = self.proved_lists.par_iter().all(|(ct_list, proof)| { + verify_lwe_compact_ciphertext_list( + &ct_list.ct_list, + &public_key.key, + proof, + public_params, + ) + .is_valid() + }); + + if all_valid { + ZkVerificationOutCome::Valid + } else { + ZkVerificationOutCome::Invalid + } + } +} + +#[cfg(test)] +mod tests { + use crate::shortint::parameters::DynamicDistribution; + use crate::shortint::prelude::PARAM_MESSAGE_2_CARRY_2_KS_PBS; + use crate::shortint::{ClientKey, CompactPublicKey}; + use crate::zk::{CompactPkeCrs, ZkComputeLoad}; + use rand::random; + + #[test] + fn test_zk_ciphertext_encryption_ci_run_filter() { + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::new_t_uniform(9); + + let crs = CompactPkeCrs::from_shortint_params(params, 4).unwrap(); + let cks = ClientKey::new(params); + let pk = CompactPublicKey::new(&cks); + + let msg = random::() % params.message_modulus.0 as u64; + + let proven_ct = pk + .encrypt_and_prove(msg, crs.public_params(), ZkComputeLoad::Proof) + .unwrap(); + assert!(proven_ct.verify(crs.public_params(), &pk).is_valid()); + + let decrypted = cks.decrypt(proven_ct.ciphertext()); + assert_eq!(msg, decrypted); + } + + #[test] + fn test_zk_compact_ciphertext_list_encryption_ci_run_filter() { + let mut params = PARAM_MESSAGE_2_CARRY_2_KS_PBS; + params.glwe_noise_distribution = DynamicDistribution::new_t_uniform(9); + + let crs = CompactPkeCrs::from_shortint_params(params, 512).unwrap(); + let cks = ClientKey::new(params); + let pk = CompactPublicKey::new(&cks); + + let msgs = (0..512) + .map(|_| random::() % params.message_modulus.0 as u64) + .collect::>(); + + let proven_ct = pk + .encrypt_and_prove_slice(&msgs, crs.public_params(), ZkComputeLoad::Proof) + .unwrap(); + assert!(proven_ct.verify(crs.public_params(), &pk).is_valid()); + + let expanded = proven_ct + .verify_and_expand(crs.public_params(), &pk) + .unwrap(); + let decrypted = expanded + .iter() + .map(|ciphertext| cks.decrypt(ciphertext)) + .collect::>(); + assert_eq!(msgs, decrypted); + } +} diff --git a/tfhe/src/shortint/engine/mod.rs b/tfhe/src/shortint/engine/mod.rs index 4cce4bc86f..196b1848f3 100644 --- a/tfhe/src/shortint/engine/mod.rs +++ b/tfhe/src/shortint/engine/mod.rs @@ -7,6 +7,8 @@ use crate::core_crypto::commons::computation_buffers::ComputationBuffers; use crate::core_crypto::commons::generators::{ DeterministicSeeder, EncryptionRandomGenerator, SecretRandomGenerator, }; +#[cfg(feature = "zk-pok-experimental")] +use crate::core_crypto::commons::math::random::RandomGenerator; use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder}; use crate::core_crypto::entities::*; use crate::core_crypto::prelude::ContainerMut; @@ -284,6 +286,8 @@ pub struct ShortintEngine { /// A seeder that can be called to generate 128 bits seeds, useful to create new /// [`EncryptionRandomGenerator`] to encrypt seeded types. pub(crate) seeder: DeterministicSeeder, + #[cfg(feature = "zk-pok-experimental")] + pub(crate) random_generator: RandomGenerator, pub(crate) computation_buffers: ComputationBuffers, ciphertext_buffers: Memory, } @@ -327,6 +331,8 @@ impl ShortintEngine { deterministic_seeder.seed(), &mut deterministic_seeder, ), + #[cfg(feature = "zk-pok-experimental")] + random_generator: RandomGenerator::new(deterministic_seeder.seed()), seeder: deterministic_seeder, computation_buffers: ComputationBuffers::default(), ciphertext_buffers: Memory::default(), diff --git a/tfhe/src/shortint/public_key/compact.rs b/tfhe/src/shortint/public_key/compact.rs index cfacad37a3..57125195d0 100644 --- a/tfhe/src/shortint/public_key/compact.rs +++ b/tfhe/src/shortint/public_key/compact.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "zk-pok-experimental")] +use crate::core_crypto::algorithms::encrypt_and_prove_lwe_ciphertext_with_compact_public_key; +#[cfg(feature = "zk-pok-experimental")] +use crate::core_crypto::entities::Cleartext; use crate::core_crypto::prelude::{ allocate_and_generate_new_seeded_lwe_compact_public_key, encrypt_lwe_ciphertext_with_compact_public_key, generate_lwe_compact_public_key, @@ -5,8 +9,12 @@ use crate::core_crypto::prelude::{ LweCompactPublicKeyOwned, Plaintext, PlaintextList, SeededLweCompactPublicKeyOwned, }; use crate::shortint::ciphertext::{CompactCiphertextList, Degree, NoiseLevel}; +#[cfg(feature = "zk-pok-experimental")] +use crate::shortint::ciphertext::{ProvenCiphertext, ProvenCompactCiphertextList}; use crate::shortint::engine::ShortintEngine; use crate::shortint::{Ciphertext, ClientKey, PBSOrder, ShortintParameterSet}; +#[cfg(feature = "zk-pok-experimental")] +use crate::zk::{CompactPkePublicParams, ZkComputeLoad}; use serde::{Deserialize, Serialize}; use std::iter::once; @@ -162,12 +170,8 @@ impl CompactPublicKey { ); let encryption_noise_distribution = match self.pbs_order { - crate::shortint::PBSOrder::KeyswitchBootstrap => { - self.parameters.glwe_noise_distribution() - } - crate::shortint::PBSOrder::BootstrapKeyswitch => { - self.parameters.lwe_noise_distribution() - } + PBSOrder::KeyswitchBootstrap => self.parameters.glwe_noise_distribution(), + PBSOrder::BootstrapKeyswitch => self.parameters.lwe_noise_distribution(), }; ShortintEngine::with_thread_local_mut(|engine| { @@ -193,6 +197,58 @@ impl CompactPublicKey { ) } + #[cfg(feature = "zk-pok-experimental")] + pub fn encrypt_and_prove( + &self, + message: u64, + public_params: &CompactPkePublicParams, + load: ZkComputeLoad, + ) -> crate::Result { + // This allocates the required ct + let mut encrypted_ct = LweCiphertextOwned::new( + 0u64, + self.key.lwe_dimension().to_lwe_size(), + self.parameters.ciphertext_modulus(), + ); + + let encryption_noise_distribution = match self.pbs_order { + PBSOrder::KeyswitchBootstrap => self.parameters.glwe_noise_distribution(), + PBSOrder::BootstrapKeyswitch => self.parameters.lwe_noise_distribution(), + }; + + let plaintext_modulus = + (self.parameters.message_modulus().0 * self.parameters.carry_modulus().0) as u64; + let delta = (1u64 << 63) / plaintext_modulus; + + let proof = ShortintEngine::with_thread_local_mut(|engine| { + encrypt_and_prove_lwe_ciphertext_with_compact_public_key( + &self.key, + &mut encrypted_ct, + Cleartext(message), + delta, + encryption_noise_distribution, + encryption_noise_distribution, + &mut engine.secret_generator, + &mut engine.encryption_generator, + &mut engine.random_generator, + public_params, + load, + ) + })?; + + let message_modulus = self.parameters.message_modulus(); + let ciphertext = Ciphertext::new( + encrypted_ct, + Degree::new(message_modulus.0 - 1), + NoiseLevel::NOMINAL, + message_modulus, + self.parameters.carry_modulus(), + self.pbs_order, + ); + + Ok(ProvenCiphertext { ciphertext, proof }) + } + pub fn encrypt_slice(&self, messages: &[u64]) -> CompactCiphertextList { self.encrypt_iter(messages.iter().copied()) } @@ -211,12 +267,8 @@ impl CompactPublicKey { ); let encryption_noise_distribution = match self.pbs_order { - crate::shortint::PBSOrder::KeyswitchBootstrap => { - self.parameters.glwe_noise_distribution() - } - crate::shortint::PBSOrder::BootstrapKeyswitch => { - self.parameters.lwe_noise_distribution() - } + PBSOrder::KeyswitchBootstrap => self.parameters.glwe_noise_distribution(), + PBSOrder::BootstrapKeyswitch => self.parameters.lwe_noise_distribution(), }; // No parallelism allowed @@ -264,6 +316,91 @@ impl CompactPublicKey { } } + #[cfg(feature = "zk-pok-experimental")] + pub fn encrypt_and_prove_slice( + &self, + messages: &[u64], + public_params: &CompactPkePublicParams, + load: ZkComputeLoad, + ) -> crate::Result { + let plaintext_modulus = + (self.parameters.message_modulus().0 * self.parameters.carry_modulus().0) as u64; + let delta = (1u64 << 63) / plaintext_modulus; + + let max_num_message = public_params.k; + let num_lists = messages.len().div_ceil(max_num_message); + let mut proved_lists = Vec::with_capacity(num_lists); + for message_chunk in messages.chunks(max_num_message) { + let mut ct_list = LweCompactCiphertextListOwned::new( + 0u64, + self.key.lwe_dimension().to_lwe_size(), + LweCiphertextCount(message_chunk.len()), + self.parameters.ciphertext_modulus(), + ); + + let encryption_noise_distribution = match self.pbs_order { + PBSOrder::KeyswitchBootstrap => self.parameters.glwe_noise_distribution(), + PBSOrder::BootstrapKeyswitch => self.parameters.lwe_noise_distribution(), + }; + + // No parallelism allowed + #[cfg(all(feature = "__wasm_api", not(feature = "parallel-wasm-api")))] + let proof = { + use crate::core_crypto::prelude::encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key; + ShortintEngine::with_thread_local_mut(|engine| { + encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( + &self.key, + &mut ct_list, + &message_chunk, + delta, + encryption_noise_distribution, + encryption_noise_distribution, + &mut engine.secret_generator, + &mut engine.encryption_generator, + &mut engine.random_generator, + public_params, + load, + ) + }) + }?; + + // Parallelism allowed / + #[cfg(any(not(feature = "__wasm_api"), feature = "parallel-wasm-api"))] + let proof = { + use crate::core_crypto::prelude::par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key; + ShortintEngine::with_thread_local_mut(|engine| { + par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key( + &self.key, + &mut ct_list, + &message_chunk, + delta, + encryption_noise_distribution, + encryption_noise_distribution, + &mut engine.secret_generator, + &mut engine.encryption_generator, + &mut engine.random_generator, + public_params, + load, + ) + }) + }?; + + let message_modulus = self.parameters.message_modulus(); + let ciphertext = CompactCiphertextList { + ct_list, + degree: Degree::new(message_modulus.0 - 1), + message_modulus, + carry_modulus: self.parameters.carry_modulus(), + pbs_order: self.pbs_order, + noise_level: NoiseLevel::NOMINAL, + }; + + proved_lists.push((ciphertext, proof)); + } + + Ok(ProvenCompactCiphertextList { proved_lists }) + } + pub fn size_elements(&self) -> usize { self.key.size_elements() } diff --git a/tfhe/src/zk.rs b/tfhe/src/zk.rs new file mode 100644 index 0000000000..839df34d74 --- /dev/null +++ b/tfhe/src/zk.rs @@ -0,0 +1,146 @@ +use crate::core_crypto::commons::math::random::{BoundedDistribution, Deserialize, Serialize}; +use crate::core_crypto::prelude::*; +use rand_core::RngCore; +use std::cmp::Ordering; +use std::collections::Bound; +use tfhe_zk_pok::proofs::pke::crs_gen; + +pub use tfhe_zk_pok::proofs::ComputeLoad as ZkComputeLoad; +type Curve = tfhe_zk_pok::curve_api::Bls12_446; +pub type CompactPkeProof = tfhe_zk_pok::proofs::pke::Proof; +pub type CompactPkePublicParams = tfhe_zk_pok::proofs::pke::PublicParams; +pub type CompactPkePrivateParams = tfhe_zk_pok::proofs::pke::PrivateParams; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum ZkVerificationOutCome { + /// The proof ands its entity were valid + Valid, + /// The proof ands its entity were not + Invalid, +} + +impl ZkVerificationOutCome { + pub fn is_valid(self) -> bool { + self == Self::Valid + } + + pub fn is_invalid(self) -> bool { + self == Self::Invalid + } +} + +#[derive(Serialize, Deserialize)] +pub struct CompactPkeCrs { + public_params: CompactPkePublicParams, + private_params: CompactPkePrivateParams, +} + +impl CompactPkeCrs { + pub fn new( + lwe_dim: LweDimension, + max_num_cleartext: usize, + noise_distribution: NoiseDistribution, + ciphertext_modulus: CiphertextModulus, + plaintext_modulus: Scalar, + rng: &mut impl RngCore, + ) -> crate::Result + where + Scalar: UnsignedInteger + CastInto, + NoiseDistribution: BoundedDistribution, + { + // The bound for the crs has to be a power of two, + // it is [-b, b) (non-inclusive for the high bound) + // so we may have to give a bound that is bigger than + // what the distribution generates + let high_bound = match noise_distribution.high_bound() { + Bound::Included(high_b) => { + if high_b.is_power_of_two() { + high_b * Scalar::TWO + } else { + high_b.next_power_of_two() + } + } + Bound::Excluded(high_b) => { + if high_b.is_power_of_two() { + high_b + } else { + high_b.next_power_of_two() + } + } + Bound::Unbounded => { + return Err("requires bounded distribution".into()); + } + }; + + let abs_low_bound = match noise_distribution.low_bound() { + Bound::Included(low_b) => { + let abs_low_bound = low_b.wrapping_abs().into_unsigned(); + if abs_low_bound.is_power_of_two() { + abs_low_bound * Scalar::TWO + } else { + abs_low_bound.next_power_of_two() + } + } + Bound::Excluded(low_b) => { + let abs_low_bound = low_b.wrapping_abs().into_unsigned(); + if abs_low_bound.is_power_of_two() { + abs_low_bound + } else { + abs_low_bound.next_power_of_two() + } + } + Bound::Unbounded => { + return Err("requires bounded distribution".into()); + } + }; + + let noise_bound = abs_low_bound.max(high_bound); + + if Scalar::BITS > 64 && noise_bound >= (Scalar::ONE << 64usize) { + return Err("noise bounds exceeds 64 bits modulus".into()); + } + + if Scalar::BITS > 64 && plaintext_modulus >= (Scalar::ONE << 64usize) { + return Err("Plaintext modulus exceeds 64 bits modulus".into()); + } + + let q = if ciphertext_modulus.is_native_modulus() { + match Scalar::BITS.cmp(&64) { + Ordering::Greater => Err( + "Zero Knowledge proof do not support ciphertext modulus > 64 bits".to_string(), + ), + Ordering::Equal => Ok(0u64), + Ordering::Less => Ok(1u64 << Scalar::BITS), + } + } else { + let custom_modulus = ciphertext_modulus.get_custom_modulus(); + if custom_modulus > (u64::MAX) as u128 { + Err("Zero Knowledge proof do not support ciphertext modulus > 64 bits".to_string()) + } else { + Ok(custom_modulus as u64) + } + }?; + + let (public_params, private_params) = crs_gen( + lwe_dim.0, + max_num_cleartext, + noise_bound.cast_into(), + q, + plaintext_modulus.cast_into(), + rng, + ); + + Ok(Self { + public_params, + private_params, + }) + } + + pub fn public_params(&self) -> &CompactPkePublicParams { + &self.public_params + } + + pub fn private_params(&self) -> &CompactPkePrivateParams { + &self.private_params + } +} diff --git a/tfhe/web_wasm_parallel_tests/index.html b/tfhe/web_wasm_parallel_tests/index.html index 1792a2fb95..70f907b75a 100644 --- a/tfhe/web_wasm_parallel_tests/index.html +++ b/tfhe/web_wasm_parallel_tests/index.html @@ -68,7 +68,12 @@ value="Compressed Compact Public Key Test 256 Bits Big" disabled /> - +
@@ -110,6 +115,12 @@ value="Compressed Server Key Bench 2_2" disabled /> +
diff --git a/tfhe/web_wasm_parallel_tests/index.js b/tfhe/web_wasm_parallel_tests/index.js index 3a951ba64b..8f903d8a30 100644 --- a/tfhe/web_wasm_parallel_tests/index.js +++ b/tfhe/web_wasm_parallel_tests/index.js @@ -31,12 +31,14 @@ async function setup() { "compactPublicKeyTest256BitSmall", "compressedCompactPublicKeyTest256BitBig", "compressedCompactPublicKeyTest256BitSmall", + "compactPublicKeyZeroKnowledge", "compactPublicKeyBench32BitBig", "compactPublicKeyBench32BitSmall", "compactPublicKeyBench256BitBig", "compactPublicKeyBench256BitSmall", "compressedServerKeyBenchMessage1Carry1", "compressedServerKeyBenchMessage2Carry2", + "compactPublicKeyZeroKnowledgeBench" ]; function setupBtn(id) { diff --git a/tfhe/web_wasm_parallel_tests/jest.config.js b/tfhe/web_wasm_parallel_tests/jest.config.js index 55398dd306..64d7391862 100644 --- a/tfhe/web_wasm_parallel_tests/jest.config.js +++ b/tfhe/web_wasm_parallel_tests/jest.config.js @@ -1,4 +1,4 @@ -const secs = 60; +const secs = 250; const config = { verbose: true, diff --git a/tfhe/web_wasm_parallel_tests/test/common.mjs b/tfhe/web_wasm_parallel_tests/test/common.mjs index f242e628b5..bd90bdf201 100644 --- a/tfhe/web_wasm_parallel_tests/test/common.mjs +++ b/tfhe/web_wasm_parallel_tests/test/common.mjs @@ -56,6 +56,7 @@ async function runTestAttachedToButton(buttonId) { browser = await puppeteer.launch({ headless: "new", args: ["--no-sandbox"], + protocolTimeout: 20*1000, }); } else { browser = await puppeteer.launch({ 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 index 46a63b85c4..038f426811 100644 --- a/tfhe/web_wasm_parallel_tests/test/compact-public-key.test.js +++ b/tfhe/web_wasm_parallel_tests/test/compact-public-key.test.js @@ -23,3 +23,7 @@ it("Compressed Compact Public Key Test Small 256 Bit", async () => { it("Compressed Compact Public Key Test Big 256 Bit", async () => { await runTestAttachedToButton("compressedCompactPublicKeyTest256BitBig"); }); + +it("Compact Public Key Test Big 64 Bit With Zero Knowledge", async () => { + await runTestAttachedToButton("compactPublicKeyZeroKnowledge"); +}); diff --git a/tfhe/web_wasm_parallel_tests/worker.js b/tfhe/web_wasm_parallel_tests/worker.js index 763f6dfabc..c70fe3695c 100644 --- a/tfhe/web_wasm_parallel_tests/worker.js +++ b/tfhe/web_wasm_parallel_tests/worker.js @@ -11,17 +11,15 @@ import init, { TfheCompressedCompactPublicKey, TfheCompactPublicKey, TfheConfigBuilder, - CompressedFheUint8, FheUint8, - FheUint32, - CompactFheUint32, CompactFheUint32List, - CompressedFheUint128, - FheUint128, - CompressedFheUint256, - FheUint256, - CompactFheUint256, CompactFheUint256List, + ZkComputeLoad, + ProvenCompactFheUint64, + ProvenCompactFheUint64List, + CompactPkeCrs, + Shortint, + CompactFheUint64, } from "./pkg/tfhe.js"; function assert(cond, text) { @@ -74,7 +72,7 @@ async function compressedPublicKeyTest() { } async function publicKeyTest() { - let config = TfheConfigBuilder.default().use_small_encryption().build(); + let config = TfheConfigBuilder.default_with_small_encryption().build(); console.time("ClientKey Gen"); let clientKey = TfheClientKey.generate(config); @@ -379,6 +377,75 @@ async function compressedCompactPublicKeyTest256BitOnConfig(config) { } } +function generateRandomBigInt(bitLen) { + let result = BigInt(0); + for (let i = 0; i