diff --git a/tests/backward_compatibility/high_level_api.rs b/tests/backward_compatibility/high_level_api.rs index 04c17905dc..917fdf613f 100644 --- a/tests/backward_compatibility/high_level_api.rs +++ b/tests/backward_compatibility/high_level_api.rs @@ -18,13 +18,13 @@ use tfhe::zk::{CompactPkeCrs, CompactPkeCrsConformanceParams}; use tfhe::ProvenCompactCiphertextList; use tfhe::{ set_server_key, ClientKey, CompactCiphertextList, CompactCiphertextListBuilder, - CompactPublicKey, CompressedCiphertextList, CompressedCiphertextListBuilder, - CompressedCompactPublicKey, CompressedFheBool, CompressedFheInt8, CompressedFheUint8, - CompressedKVStore, CompressedPublicKey, CompressedServerKey, - CompressedSquashedNoiseCiphertextList, CompressedSquashedNoiseCiphertextListBuilder, FheBool, - FheInt8, FheUint32, FheUint64, FheUint8, ReRandomizationContext, ReRandomizationMode, - ReRandomizationSupport, ServerKey, SquashedNoiseFheBool, SquashedNoiseFheInt, - SquashedNoiseFheUint, + CompactCiphertextListExpander, CompactPublicKey, CompressedCiphertextList, + CompressedCiphertextListBuilder, CompressedCompactPublicKey, CompressedFheBool, + CompressedFheInt8, CompressedFheUint8, CompressedKVStore, CompressedPublicKey, + CompressedServerKey, CompressedSquashedNoiseCiphertextList, + CompressedSquashedNoiseCiphertextListBuilder, FheBool, FheInt8, FheUint32, FheUint64, FheUint8, + ReRandomizationContext, ReRandomizationMode, ReRandomizationSupport, ServerKey, + SquashedNoiseFheBool, SquashedNoiseFheInt, SquashedNoiseFheUint, }; use tfhe_backward_compat_data::load::{ load_versioned_auxiliary, DataFormat, TestFailure, TestResult, TestSuccess, @@ -32,7 +32,8 @@ use tfhe_backward_compat_data::load::{ use tfhe_backward_compat_data::{ DataKind, HlBoolCiphertextTest, HlCiphertextTest, HlClientKeyTest, HlCompressedKVStoreTest, HlCompressedSquashedNoiseCiphertextListTest, HlCompressedXofKeySetTest, - HlHeterogeneousCiphertextListTest, HlPublicKeyTest, HlServerKeyTest, HlSignedCiphertextTest, + HlHeterogeneousCiphertextListTest, HlPublicKeyTest, HlSeededCompactCiphertextListTest, + HlSeededProvenCompactCiphertextListTest, HlServerKeyTest, HlSignedCiphertextTest, HlSquashedNoiseBoolCiphertextTest, HlSquashedNoiseSignedCiphertextTest, HlSquashedNoiseUnsignedCiphertextTest, TestMetadata, TestType, Testcase, ZkPkePublicParamsTest, }; @@ -287,6 +288,189 @@ pub fn test_hl_heterogeneous_ciphertext_list_elements( Ok(()) } +fn verify_expanded_values( + expander: &CompactCiphertextListExpander, + clear_values: &[u64], + data_kinds: &[DataKind], + key: &ClientKey, +) -> Result<(), String> { + for (idx, (value, kind)) in clear_values.iter().zip(data_kinds.iter()).enumerate() { + match kind { + DataKind::Bool => { + let ct: FheBool = expander + .get(idx) + .map_err(|e| format!("Failed to get bool at index {idx}: {e}"))? + .ok_or_else(|| format!("No value at index {idx}"))?; + let clear = ct.decrypt(key); + if clear != (*value != 0) { + return Err(format!( + "Invalid decrypted bool at index {idx}: expected {:?}, got {clear:?}", + *value != 0 + )); + } + } + DataKind::Signed => { + let ct: FheInt8 = expander + .get(idx) + .map_err(|e| format!("Failed to get signed at index {idx}: {e}"))? + .ok_or_else(|| format!("No value at index {idx}"))?; + let clear: i8 = ct.decrypt(key); + if clear != *value as i8 { + return Err(format!( + "Invalid decrypted signed at index {idx}: expected {:?}, got {clear:?}", + *value as i8 + )); + } + } + DataKind::Unsigned => { + let ct: FheUint8 = expander + .get(idx) + .map_err(|e| format!("Failed to get unsigned at index {idx}: {e}"))? + .ok_or_else(|| format!("No value at index {idx}"))?; + let clear: u8 = ct.decrypt(key); + if clear != *value as u8 { + return Err(format!( + "Invalid decrypted unsigned at index {idx}: expected {:?}, got {clear:?}", + *value as u8 + )); + } + } + } + } + Ok(()) +} + +/// Test seeded compact ciphertext list: loads pregenerated list, rebuilds from +/// stored values/seed, asserts they match via PartialEq. +pub fn test_hl_seeded_compact_ciphertext_list( + dir: &Path, + test: &HlSeededCompactCiphertextListTest, + format: DataFormat, +) -> Result { + let key_file = dir.join(&*test.key_filename); + let key = ClientKey::unversionize( + load_versioned_auxiliary(key_file).map_err(|e| test.failure(e, format))?, + ) + .map_err(|e| test.failure(e, format))?; + + let server_key = key.generate_server_key(); + set_server_key(server_key); + + let pubkey_file = dir.join(&*test.public_key_filename); + let pubkey = CompactPublicKey::unversionize( + load_versioned_auxiliary(pubkey_file).map_err(|e| test.failure(e, format))?, + ) + .map_err(|e| test.failure(e, format))?; + + let pregenerated: CompactCiphertextList = load_and_unversionize(dir, test, format)?; + + let mut builder = CompactCiphertextList::builder(&pubkey); + for (value, kind) in test.clear_values.iter().zip(test.data_kinds.iter()) { + match kind { + DataKind::Unsigned => { + builder.push(*value as u8); + } + DataKind::Signed => { + builder.push(*value as i8); + } + DataKind::Bool => { + builder.push(*value != 0); + } + } + } + let rebuilt = builder.build_packed_seeded(&test.seed).unwrap(); + + if pregenerated != rebuilt { + return Err(test.failure( + "Seeded compact list rebuilt from values/seed does not match pregenerated", + format, + )); + } + + let expanded = pregenerated.expand().map_err(|e| test.failure(e, format))?; + verify_expanded_values(&expanded, &test.clear_values, &test.data_kinds, &key) + .map_err(|e| test.failure(e, format))?; + + Ok(test.success(format)) +} + +/// Test seeded proven compact ciphertext list: loads pregenerated list, rebuilds +/// from stored values/seed/proof params, asserts they match via PartialEq. +pub fn test_hl_seeded_proven_compact_ciphertext_list( + dir: &Path, + test: &HlSeededProvenCompactCiphertextListTest, + format: DataFormat, +) -> Result { + #[cfg(feature = "zk-pok")] + { + use tfhe::zk::ZkComputeLoad; + + let key_file = dir.join(&*test.key_filename); + let key = ClientKey::unversionize( + load_versioned_auxiliary(key_file).map_err(|e| test.failure(e, format))?, + ) + .map_err(|e| test.failure(e, format))?; + + let server_key = key.generate_server_key(); + set_server_key(server_key); + + let pubkey_file = dir.join(&*test.public_key_filename); + let pubkey = CompactPublicKey::unversionize( + load_versioned_auxiliary(pubkey_file).map_err(|e| test.failure(e, format))?, + ) + .map_err(|e| test.failure(e, format))?; + + let crs_file = dir.join(&*test.proof_info.params_filename); + let crs = CompactPkeCrs::unversionize( + load_versioned_auxiliary(crs_file).map_err(|e| test.failure(e, format))?, + ) + .map_err(|e| test.failure(e, format))?; + + let pregenerated: ProvenCompactCiphertextList = load_and_unversionize(dir, test, format)?; + + let mut builder = ProvenCompactCiphertextList::builder(&pubkey); + for (value, kind) in test.clear_values.iter().zip(test.data_kinds.iter()) { + match kind { + DataKind::Unsigned => { + builder.push(*value as u8); + } + DataKind::Signed => { + builder.push(*value as i8); + } + DataKind::Bool => { + builder.push(*value != 0); + } + } + } + let rebuilt = builder + .build_with_proof_packed_seeded( + &crs, + test.proof_info.metadata.as_bytes(), + ZkComputeLoad::Proof, + &test.seed, + ) + .map_err(|e| test.failure(e, format))?; + + if pregenerated != rebuilt { + return Err(test.failure( + "Seeded proven compact list rebuilt from values/seed does not match pregenerated", + format, + )); + } + + let expanded = pregenerated + .verify_and_expand(&crs, &pubkey, test.proof_info.metadata.as_bytes()) + .map_err(|e| test.failure(e, format))?; + verify_expanded_values(&expanded, &test.clear_values, &test.data_kinds, &key) + .map_err(|e| test.failure(e, format))?; + } + + #[cfg(not(feature = "zk-pok"))] + let _ = dir; + + Ok(test.success(format)) +} + /// Test HL client key: loads the key and checks the parameters using the values stored in /// the test metadata. pub fn test_hl_clientkey( @@ -953,6 +1137,13 @@ impl TestedModule for Hl { TestMetadata::HlCompressedXofKeySet(test) => { test_hl_compressed_xof_key_set_test(test_dir.as_ref(), test, format).into() } + TestMetadata::HlSeededCompactCiphertextList(test) => { + test_hl_seeded_compact_ciphertext_list(test_dir.as_ref(), test, format).into() + } + TestMetadata::HlSeededProvenCompactCiphertextList(test) => { + test_hl_seeded_proven_compact_ciphertext_list(test_dir.as_ref(), test, format) + .into() + } _ => { println!("WARNING: missing test: {:?}", testcase.metadata); TestResult::Skipped(testcase.skip()) diff --git a/tfhe-csprng/Cargo.toml b/tfhe-csprng/Cargo.toml index 4da15a7cd2..366df26f7b 100644 --- a/tfhe-csprng/Cargo.toml +++ b/tfhe-csprng/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tfhe-csprng" -version = "0.8.0" +version = "0.9.0" edition = "2021" license = "BSD-3-Clause-Clear" description = "Cryptographically Secure PRNG used in the TFHE-rs library." diff --git a/tfhe-zk-pok/Cargo.toml b/tfhe-zk-pok/Cargo.toml index 3a642124a5..c4d4721a6c 100644 --- a/tfhe-zk-pok/Cargo.toml +++ b/tfhe-zk-pok/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tfhe-zk-pok" -version = "0.8.0" +version = "0.8.1" edition = "2021" keywords = ["zero", "knowledge", "proof", "vector-commitments"] homepage = "https://zama.org/" diff --git a/tfhe-zk-pok/src/curve_api.rs b/tfhe-zk-pok/src/curve_api.rs index f2cf197d40..69bba05537 100644 --- a/tfhe-zk-pok/src/curve_api.rs +++ b/tfhe-zk-pok/src/curve_api.rs @@ -446,9 +446,9 @@ impl PairingGroupOps for bls12_446: // These are just ZSTs that are not actually produced and are only used for their // associated types. So it's ok to derive "NotVersioned" for them. -#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, NotVersioned)] +#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize, NotVersioned)] pub struct Bls12_381; -#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, NotVersioned)] +#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize, NotVersioned)] pub struct Bls12_446; impl Curve for Bls12_381 { diff --git a/tfhe-zk-pok/src/proofs/pke.rs b/tfhe-zk-pok/src/proofs/pke.rs index 871f58e65f..00eb787551 100644 --- a/tfhe-zk-pok/src/proofs/pke.rs +++ b/tfhe-zk-pok/src/proofs/pke.rs @@ -272,7 +272,7 @@ impl PublicParams { } } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Versionize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Versionize)] #[serde(bound( deserialize = "G: Curve, G::G1: serde::Deserialize<'de>, G::G2: serde::Deserialize<'de>", serialize = "G: Curve, G::G1: serde::Serialize, G::G2: serde::Serialize" @@ -325,7 +325,7 @@ impl Proof { /// These fields can be pre-computed on the prover side in the faster Verifier scheme. If that's the /// case, they should be included in the proof. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Versionize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Versionize)] #[serde(bound( deserialize = "G: Curve, G::G1: serde::Deserialize<'de>, G::G2: serde::Deserialize<'de>", serialize = "G: Curve, G::G1: serde::Serialize, G::G2: serde::Serialize" diff --git a/tfhe-zk-pok/src/proofs/pke_v2/hashes.rs b/tfhe-zk-pok/src/proofs/pke_v2/hashes.rs index 7e293833b6..1162c17ca9 100644 --- a/tfhe-zk-pok/src/proofs/pke_v2/hashes.rs +++ b/tfhe-zk-pok/src/proofs/pke_v2/hashes.rs @@ -105,7 +105,7 @@ impl PkeV2HashConfig { /// List of hash config that were used for a given version of this crate /// /// This is stored in the proof so that we only support a specific subset of all possible config. -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, Versionize)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(PkeV2SupportedHashConfigVersions)] pub enum PkeV2SupportedHashConfig { V0_4_0 = 0, diff --git a/tfhe-zk-pok/src/proofs/pke_v2/mod.rs b/tfhe-zk-pok/src/proofs/pke_v2/mod.rs index 7216efb670..514337856e 100644 --- a/tfhe-zk-pok/src/proofs/pke_v2/mod.rs +++ b/tfhe-zk-pok/src/proofs/pke_v2/mod.rs @@ -351,7 +351,7 @@ impl PublicParams { /// This represents a proof that the given ciphertext is a valid encryptions of the input messages /// with the provided public key. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Versionize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Versionize)] #[serde(bound( deserialize = "G: Curve, G::G1: serde::Deserialize<'de>, G::G2: serde::Deserialize<'de>", serialize = "G: Curve, G::G1: serde::Serialize, G::G2: serde::Serialize" @@ -428,7 +428,7 @@ impl Proof { /// These fields can be pre-computed on the prover side in the faster Verifier scheme. If that's the /// case, they should be included in the proof. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Versionize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Versionize)] #[versionize(ComputeLoadProofFieldsVersions)] pub(crate) struct ComputeLoadProofFields { pub(crate) C_hat_h3: G::G2, diff --git a/tfhe/Cargo.toml b/tfhe/Cargo.toml index 9eeb6bb9e4..90050063d8 100644 --- a/tfhe/Cargo.toml +++ b/tfhe/Cargo.toml @@ -54,7 +54,7 @@ clap-num = { version = "1.1.1" } cbindgen = { version = "0.28", optional = true } [dependencies] -tfhe-csprng = { version = "0.8.0", path = "../tfhe-csprng", features = [ +tfhe-csprng = { version = "0.9.0", path = "../tfhe-csprng", features = [ "parallel", ] } serde = { workspace = true, features = ["default", "derive"] } @@ -78,7 +78,7 @@ blake3 = { version = "1.8", optional = true } itertools = { workspace = true } rand_core = { version = "0.6.4", features = ["std"] } strum = { version = "0.27", features = ["derive"], optional = true } -tfhe-zk-pok = { version = "0.8.0", path = "../tfhe-zk-pok", optional = true } +tfhe-zk-pok = { version = "0.8.1", path = "../tfhe-zk-pok", optional = true } tfhe-versionable = { version = "0.7.0", path = "../utils/tfhe-versionable" } # wasm deps diff --git a/tfhe/js_on_wasm_tests/test-hlapi-signed.js b/tfhe/js_on_wasm_tests/test-hlapi-signed.js index 4c3fc8ef7e..ff402caf80 100644 --- a/tfhe/js_on_wasm_tests/test-hlapi-signed.js +++ b/tfhe/js_on_wasm_tests/test-hlapi-signed.js @@ -612,6 +612,130 @@ test("hlapi_compact_ciphertext_list_with_proof", (t) => { // Verifying and expanding is too slow for single threaded node tests. }); +test("hlapi_proven_compact_ciphertext_list_seeded", (t) => { + const block_params = new ShortintParameters( + ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, + ); + let publicKeyParams = new ShortintCompactPublicKeyEncryptionParameters( + ShortintCompactPublicKeyEncryptionParametersName.PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, + ); + + let config = TfheConfigBuilder.default() + .use_custom_parameters(block_params) + .use_dedicated_compact_public_key_parameters(publicKeyParams) + .build(); + + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let crs = CompactPkeCrs.from_config(config, 2 + 32 + 1); + let metadata = new Uint8Array([0x73, 0x65, 0x65, 0x64]); + + let seed_a = new Uint8Array(16); + seed_a[0] = 42; + let seed_b = new Uint8Array(16); + seed_b[0] = 137; + + let builder_a1 = CompactCiphertextList.builder(publicKey); + builder_a1.push_u2(3); + builder_a1.push_i32(-3284); + builder_a1.push_boolean(true); + let list_a1 = builder_a1.build_with_proof_packed_seeded( + crs, + metadata, + ZkComputeLoad.Proof, + seed_a, + ); + + let builder_a2 = CompactCiphertextList.builder(publicKey); + builder_a2.push_u2(3); + builder_a2.push_i32(-3284); + builder_a2.push_boolean(true); + let list_a2 = builder_a2.build_with_proof_packed_seeded( + crs, + metadata, + ZkComputeLoad.Proof, + seed_a, + ); + + let builder_b = CompactCiphertextList.builder(publicKey); + builder_b.push_u2(3); + builder_b.push_i32(-3284); + builder_b.push_boolean(true); + let list_b = builder_b.build_with_proof_packed_seeded( + crs, + metadata, + ZkComputeLoad.Proof, + seed_b, + ); + + // JS === is reference equality, not structural — always false for distinct WASM objects + assert.strictEqual(list_a1 === list_a2, false); + + // Use the custom .eq() method for structural comparison + assert.strictEqual( + list_a1.eq(list_a2), + true, + "same seed must produce identical output", + ); + assert.strictEqual( + list_a1.eq(list_b), + false, + "different seeds must produce different output", + ); +}); + +test("hlapi_compact_ciphertext_list_seeded", (t) => { + const block_params = new ShortintParameters( + ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, + ); + + let config = TfheConfigBuilder.default() + .use_custom_parameters(block_params) + .build(); + + let clientKey = TfheClientKey.generate(config); + let publicKey = TfheCompactPublicKey.new(clientKey); + + let seed_a = new Uint8Array(16); + seed_a[0] = 42; + let seed_b = new Uint8Array(16); + seed_b[0] = 137; + + let builder_a1 = CompactCiphertextList.builder(publicKey); + builder_a1.push_u32(17); + builder_a1.push_i32(-1); + builder_a1.push_boolean(false); + let list_a1 = builder_a1.build_packed_seeded(seed_a); + + let builder_a2 = CompactCiphertextList.builder(publicKey); + builder_a2.push_u32(17); + builder_a2.push_i32(-1); + builder_a2.push_boolean(false); + let list_a2 = builder_a2.build_packed_seeded(seed_a); + + let builder_b = CompactCiphertextList.builder(publicKey); + builder_b.push_u32(17); + builder_b.push_i32(-1); + builder_b.push_boolean(false); + let list_b = builder_b.build_packed_seeded(seed_b); + + // JS === is reference equality, not structural — always false for distinct WASM objects + assert.strictEqual(list_a1 === list_a2, false); + + // Use the custom .eq() method for structural comparison + assert.strictEqual( + list_a1.eq(list_a2), + true, + "same seed must produce identical output", + ); + assert.strictEqual( + list_a1.eq(list_b), + false, + "different seeds must produce different output", + ); +}); + test("hlapi_compact_pk_conformance", (t) => { const limit = BigInt(1 << 20); diff --git a/tfhe/src/core_crypto/algorithms/lwe_encryption.rs b/tfhe/src/core_crypto/algorithms/lwe_encryption.rs index 1a2ac965b6..6b3eb3097e 100644 --- a/tfhe/src/core_crypto/algorithms/lwe_encryption.rs +++ b/tfhe/src/core_crypto/algorithms/lwe_encryption.rs @@ -2169,7 +2169,6 @@ pub fn encrypt_lwe_ciphertext_with_compact_public_key< /// glwe_noise_distribution, /// glwe_noise_distribution, /// encryption_generator.noise_generator_mut(), -/// &mut random_generator, /// &crs, /// &metadata, /// ZkComputeLoad::Proof, @@ -2207,7 +2206,6 @@ pub fn encrypt_and_prove_lwe_ciphertext_with_compact_public_key< MaskDistribution, NoiseDistribution, EncryptionGen, - G, >( lwe_compact_public_key: &LweCompactPublicKey, output: &mut LweCiphertext, @@ -2216,7 +2214,6 @@ pub fn encrypt_and_prove_lwe_ciphertext_with_compact_public_key< mask_noise_distribution: MaskDistribution, body_noise_distribution: NoiseDistribution, noise_generator: &mut NoiseRandomGenerator, - random_generator: &mut RandomGenerator, crs: &CompactPkeCrs, metadata: &[u8], load: ZkComputeLoad, @@ -2233,7 +2230,6 @@ where MaskDistribution: BoundedDistribution, NoiseDistribution: BoundedDistribution, EncryptionGen: ByteRandomGenerator, - G: ByteRandomGenerator, { verify_zero_knowledge_preconditions( lwe_compact_public_key, @@ -2258,6 +2254,9 @@ where noise_generator, ); + let mut zk_seed = [0u8; 16]; + noise_generator.fill_bytes(&mut zk_seed); + Ok(crs.prove( lwe_compact_public_key, &vec![message.0], @@ -2272,7 +2271,7 @@ where &body_noise, metadata, load, - random_generator, + zk_seed, )) } @@ -2623,7 +2622,6 @@ pub fn encrypt_lwe_compact_ciphertext_list_with_compact_public_key< /// glwe_noise_distribution, /// glwe_noise_distribution, /// encryption_generator.noise_generator_mut(), -/// &mut random_generator, /// &crs, /// &metadata, /// ZkComputeLoad::Proof, @@ -2674,7 +2672,6 @@ pub fn encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key< MaskDistribution, NoiseDistribution, EncryptionGen, - G, >( lwe_compact_public_key: &LweCompactPublicKey, output: &mut LweCompactCiphertextList, @@ -2683,7 +2680,6 @@ pub fn encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key< mask_noise_distribution: MaskDistribution, body_noise_distribution: NoiseDistribution, noise_generator: &mut NoiseRandomGenerator, - random_generator: &mut RandomGenerator, crs: &CompactPkeCrs, metadata: &[u8], load: ZkComputeLoad, @@ -2701,7 +2697,6 @@ where InputCont: Container, OutputCont: ContainerMut, EncryptionGen: ByteRandomGenerator, - G: ByteRandomGenerator, { verify_zero_knowledge_preconditions( lwe_compact_public_key, @@ -2738,6 +2733,9 @@ where slice_semi_reverse_negacyclic_convolution, ); + let mut zk_seed = [0u8; 16]; + noise_generator.fill_bytes(&mut zk_seed); + Ok(crs.prove( lwe_compact_public_key, messages, @@ -2747,7 +2745,7 @@ where &body_noise, metadata, load, - random_generator, + zk_seed, )) } @@ -3097,7 +3095,6 @@ pub fn par_encrypt_lwe_compact_ciphertext_list_with_compact_public_key< /// glwe_noise_distribution, /// glwe_noise_distribution, /// encryption_generator.noise_generator_mut(), -/// &mut random_generator, /// &crs, /// &metadata, /// ZkComputeLoad::Proof, @@ -3148,7 +3145,6 @@ pub fn par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key MaskDistribution, NoiseDistribution, EncryptionGen, - G, >( lwe_compact_public_key: &LweCompactPublicKey, output: &mut LweCompactCiphertextList, @@ -3157,7 +3153,6 @@ pub fn par_encrypt_and_prove_lwe_compact_ciphertext_list_with_compact_public_key mask_noise_distribution: MaskDistribution, body_noise_distribution: NoiseDistribution, noise_generator: &mut NoiseRandomGenerator, - random_generator: &mut RandomGenerator, crs: &CompactPkeCrs, metadata: &[u8], load: ZkComputeLoad, @@ -3175,7 +3170,6 @@ where InputCont: Container, OutputCont: ContainerMut, EncryptionGen: ByteRandomGenerator, - G: ByteRandomGenerator, { verify_zero_knowledge_preconditions( lwe_compact_public_key, @@ -3209,6 +3203,9 @@ where noise_generator, ); + let mut zk_seed = [0u8; 16]; + noise_generator.fill_bytes(&mut zk_seed); + Ok(crs.prove( lwe_compact_public_key, messages, @@ -3218,7 +3215,7 @@ where &body_noise, metadata, load, - random_generator, + zk_seed, )) } diff --git a/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs b/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs index 315d51d71b..500c981603 100644 --- a/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs +++ b/tfhe/src/core_crypto/algorithms/test/lwe_encryption.rs @@ -1072,7 +1072,6 @@ fn lwe_compact_public_encrypt_prove_verify_decrypt_custom_mod( glwe_noise_distribution, glwe_noise_distribution, rsc.encryption_random_generator.noise_generator_mut(), - &mut random_generator, crs, &metadata, ZkComputeLoad::Proof, @@ -1195,7 +1194,6 @@ fn test_par_compact_lwe_list_public_key_encryption_and_proof() { glwe_noise_distribution, glwe_noise_distribution, encryption_random_generator.noise_generator_mut(), - &mut random_generator, &crs, &metadata, ZkComputeLoad::Proof, @@ -1285,7 +1283,6 @@ fn test_par_compact_lwe_list_public_key_encryption_and_proof() { glwe_noise_distribution, glwe_noise_distribution, encryption_random_generator.noise_generator_mut(), - &mut random_generator, &crs, &metadata, ZkComputeLoad::Proof, diff --git a/tfhe/src/core_crypto/commons/generators/encryption/noise_random_generator.rs b/tfhe/src/core_crypto/commons/generators/encryption/noise_random_generator.rs index 4eea498d0c..16f350601c 100644 --- a/tfhe/src/core_crypto/commons/generators/encryption/noise_random_generator.rs +++ b/tfhe/src/core_crypto/commons/generators/encryption/noise_random_generator.rs @@ -9,6 +9,8 @@ use crate::core_crypto::commons::numeric::UnsignedInteger; use crate::core_crypto::commons::parameters::{ CiphertextModulus, EncryptionNoiseByteCount, EncryptionNoiseSampleCount, }; +#[cfg(feature = "zk-pok")] +use rand_core::RngCore; use rayon::prelude::*; use tfhe_csprng::generators::ForkError; use tfhe_csprng::seeders::SeedKind; @@ -120,6 +122,11 @@ impl NoiseRandomGenerator { } } + #[cfg(feature = "zk-pok")] + pub(crate) fn fill_bytes(&mut self, dest: &mut [u8]) { + self.gen.fill_bytes(dest); + } + pub fn remaining_bytes(&self) -> Option { self.gen.remaining_bytes() } diff --git a/tfhe/src/high_level_api/compact_list.rs b/tfhe/src/high_level_api/compact_list.rs index bedcf0f808..5ced859266 100644 --- a/tfhe/src/high_level_api/compact_list.rs +++ b/tfhe/src/high_level_api/compact_list.rs @@ -166,6 +166,23 @@ impl Clone for InnerCompactCiphertextList { } } +impl PartialEq for InnerCompactCiphertextList { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Cpu(a), Self::Cpu(b)) => a == b, + #[cfg(feature = "gpu")] + (Self::Cuda(a), Self::Cuda(b)) => with_cuda_internal_keys(|keys| { + let streams = &keys.streams; + let a_cpu = a.to_integer_compact_ciphertext_list(streams).unwrap(); + let b_cpu = b.to_integer_compact_ciphertext_list(streams).unwrap(); + a_cpu == b_cpu + }), + #[cfg(feature = "gpu")] + _ => false, + } + } +} + impl serde::Serialize for InnerCompactCiphertextList { fn serialize(&self, serializer: S) -> Result where @@ -273,7 +290,7 @@ impl Unversionize for InnerCompactCiphertextList { } } -#[derive(Clone, Serialize, Deserialize, Versionize)] +#[derive(Clone, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(CompactCiphertextListVersions)] pub struct CompactCiphertextList { pub(crate) inner: InnerCompactCiphertextList, @@ -498,7 +515,19 @@ mod zk { } } - #[derive(Clone, Serialize, Deserialize, Versionize)] + impl PartialEq for InnerProvenCompactCiphertextList { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Cpu(a), Self::Cpu(b)) => a == b, + #[cfg(feature = "gpu")] + (Self::Cuda(a), Self::Cuda(b)) => a.h_proved_lists == b.h_proved_lists, + #[cfg(feature = "gpu")] + _ => false, + } + } + } + + #[derive(Clone, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(ProvenCompactCiphertextListVersions)] pub struct ProvenCompactCiphertextList { pub(crate) inner: InnerProvenCompactCiphertextList, @@ -1065,6 +1094,21 @@ impl CompactCiphertextListBuilder { }) .expect("Internal error, invalid parameters should not have been allowed") } + + /// Builds the list using the given seed, useful to have determinism for encryption + /// + /// # Warning + /// + /// The seed must be kept secure, if attackers gets the seed they can break the encryption + pub fn build_packed_seeded(&self, seed: &[u8]) -> crate::Result { + self.inner + .build_packed_seeded(seed) + .map(|list| CompactCiphertextList { + inner: crate::high_level_api::compact_list::InnerCompactCiphertextList::Cpu(list), + tag: self.tag.clone(), + }) + } + #[cfg(feature = "zk-pok")] pub fn build_with_proof_packed( &self, @@ -1082,6 +1126,30 @@ impl CompactCiphertextListBuilder { tag: self.tag.clone(), }) } + + /// Builds a proved list using the given seed, useful to have determinism for encryption + /// + /// # Warning + /// + /// The seed must be kept secure, if attackers gets the seed they can break the encryption + #[cfg(feature = "zk-pok")] + pub fn build_with_proof_packed_seeded( + &self, + crs: &CompactPkeCrs, + metadata: &[u8], + compute_load: ZkComputeLoad, + seed: &[u8], + ) -> crate::Result { + self.inner + .build_with_proof_packed_seeded(crs, metadata, compute_load, seed) + .map(|proved_list| ProvenCompactCiphertextList { + inner: + crate::high_level_api::compact_list::zk::InnerProvenCompactCiphertextList::Cpu( + proved_list, + ), + tag: self.tag.clone(), + }) + } } #[cfg(feature = "strings")] @@ -1173,6 +1241,53 @@ mod tests { } } + #[test] + fn test_seeded_compact_list() { + let config = crate::ConfigBuilder::default().build(); + + let ck = crate::ClientKey::generate(config); + let sk = crate::ServerKey::new(&ck); + let pk = crate::CompactPublicKey::new(&ck); + + set_server_key(sk); + + let seed_a = 42u128.to_le_bytes(); + let seed_b = 1337u128.to_le_bytes(); + + let build = |seed: &[u8]| { + CompactCiphertextList::builder(&pk) + .push(17u32) + .push(-1i64) + .push(false) + .build_packed_seeded(seed) + .unwrap() + }; + + let list_a1 = build(&seed_a); + let list_a2 = build(&seed_a); + let list_b = build(&seed_b); + + assert!( + list_a1 == list_a2, + "same seed must produce identical output" + ); + assert!( + list_a1 != list_b, + "different seeds must produce different output" + ); + + let expander = list_a1.expand().unwrap(); + let a: FheUint32 = expander.get(0).unwrap().unwrap(); + let b: FheInt64 = expander.get(1).unwrap().unwrap(); + let c: FheBool = expander.get(2).unwrap().unwrap(); + + let da: u32 = a.decrypt(&ck); + let db: i64 = b.decrypt(&ck); + assert_eq!(da, 17u32); + assert_eq!(db, -1i64); + assert!(!c.decrypt(&ck)); + } + #[test] fn test_empty_list() { let config = crate::ConfigBuilder::default().build(); @@ -1605,6 +1720,61 @@ mod tests { } } + #[cfg(feature = "zk-pok")] + #[test] + fn test_seeded_proven_compact_list() { + let params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let config = crate::ConfigBuilder::with_custom_parameters(params) + .use_dedicated_compact_public_key_parameters(( + PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, + PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, + )) + .build(); + + let ck = crate::ClientKey::generate(config); + let pk = crate::CompactPublicKey::new(&ck); + let sks = crate::ServerKey::new(&ck); + set_server_key(sks); + + let crs = CompactPkeCrs::from_config(config, 32).unwrap(); + let metadata = [b's', b'e', b'e', b'd']; + + let seed_a = 42u128.to_le_bytes(); + let seed_b = 1337u128.to_le_bytes(); + + let build = |seed: &[u8]| { + ProvenCompactCiphertextList::builder(&pk) + .push(17u32) + .push(-1i64) + .push(false) + .build_with_proof_packed_seeded(&crs, &metadata, ZkComputeLoad::Proof, seed) + .unwrap() + }; + + let list_a1 = build(&seed_a); + let list_a2 = build(&seed_a); + let list_b = build(&seed_b); + + assert!( + list_a1 == list_a2, + "same seed must produce identical output" + ); + assert!( + list_a1 != list_b, + "different seeds must produce different output" + ); + + let expander = list_a1.verify_and_expand(&crs, &pk, &metadata).unwrap(); + let a: FheUint32 = expander.get(0).unwrap().unwrap(); + let b: FheInt64 = expander.get(1).unwrap().unwrap(); + let c: FheBool = expander.get(2).unwrap().unwrap(); + let da: u32 = a.decrypt(&ck); + let db: i64 = b.decrypt(&ck); + assert_eq!(da, 17u32); + assert_eq!(db, -1i64); + assert!(!c.decrypt(&ck)); + } + #[cfg(feature = "zk-pok")] #[test] fn test_empty_proven_list() { diff --git a/tfhe/src/integer/ciphertext/compact_list.rs b/tfhe/src/integer/ciphertext/compact_list.rs index ff0be60174..336e93838e 100644 --- a/tfhe/src/integer/ciphertext/compact_list.rs +++ b/tfhe/src/integer/ciphertext/compact_list.rs @@ -282,6 +282,28 @@ impl CompactCiphertextListBuilder { }) } + pub fn build_packed_seeded(&self, seed: &[u8]) -> crate::Result { + if self.pk.key.parameters.carry_modulus.0 < self.pk.key.parameters.message_modulus.0 { + return Err(crate::Error::new("In order to build a packed compact ciphertext list, parameters must have CarryModulus >= MessageModulus".to_string())); + } + + let msg_mod = self.pk.key.message_modulus().0; + let packed_messaged_iter = self + .messages + .chunks(2) + .map(|two_values| (two_values.get(1).copied().unwrap_or(0) * msg_mod) + two_values[0]); + let ct_list = self.pk.key.encrypt_iter_with_modulus_seeded( + packed_messaged_iter, + msg_mod * msg_mod, + seed, + )?; + + Ok(CompactCiphertextList { + ct_list, + info: self.info.clone(), + }) + } + #[cfg(feature = "zk-pok")] pub fn build_with_proof( &self, @@ -335,6 +357,42 @@ impl CompactCiphertextListBuilder { info: self.info.clone(), }) } + + #[cfg(feature = "zk-pok")] + pub fn build_with_proof_packed_seeded( + &self, + crs: &CompactPkeCrs, + metadata: &[u8], + load: ZkComputeLoad, + seed: &[u8], + ) -> crate::Result { + if self.pk.key.parameters.carry_modulus.0 < self.pk.key.parameters.message_modulus.0 { + return Err(crate::Error::new( + "In order to build a packed ProvenCompactCiphertextList, \ + parameters must have CarryModulus >= MessageModulus" + .to_string(), + )); + } + + let msg_mod = self.pk.key.parameters.message_modulus.0; + let packed_messages = self + .messages + .chunks(2) + .map(|two_values| (two_values.get(1).copied().unwrap_or(0) * msg_mod) + two_values[0]) + .collect::>(); + let ct_list = self.pk.key.encrypt_and_prove_slice_seeded( + packed_messages.as_slice(), + crs, + metadata, + load, + msg_mod * msg_mod, + seed, + )?; + Ok(ProvenCompactCiphertextList { + ct_list, + info: self.info.clone(), + }) + } } pub struct CompactCiphertextListExpander { @@ -393,7 +451,7 @@ impl CompactCiphertextListExpander { } } -#[derive(Clone, Serialize, Deserialize, Versionize)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Versionize)] #[versionize(CompactCiphertextListVersions)] pub struct CompactCiphertextList { pub(crate) ct_list: crate::shortint::ciphertext::CompactCiphertextList, @@ -1015,7 +1073,7 @@ impl CompactCiphertextList { } #[cfg(feature = "zk-pok")] -#[derive(Clone, Serialize, Deserialize, Versionize)] +#[derive(Clone, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(ProvenCompactCiphertextListVersions)] pub struct ProvenCompactCiphertextList { pub(crate) ct_list: crate::shortint::ciphertext::ProvenCompactCiphertextList, diff --git a/tfhe/src/integer/gpu/server_key/radix/tests_noise_distribution/cpk_ks_ms.rs b/tfhe/src/integer/gpu/server_key/radix/tests_noise_distribution/cpk_ks_ms.rs index 3c8d68765a..11df7cfed2 100644 --- a/tfhe/src/integer/gpu/server_key/radix/tests_noise_distribution/cpk_ks_ms.rs +++ b/tfhe/src/integer/gpu/server_key/radix/tests_noise_distribution/cpk_ks_ms.rs @@ -2,7 +2,6 @@ use crate::integer::gpu::ciphertext::compact_list::CudaFlattenedVecCompactCipher use crate::core_crypto::commons::parameters::CiphertextModulusLog; use crate::integer::gpu::server_key::radix::tests_unsigned::create_gpu_parameterized_stringified_test; -use crate::shortint::engine::ShortintEngine; use crate::shortint::parameters::test_params::TEST_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128; use crate::shortint::parameters::{ AtomicPatternParameters, CarryModulus, CompactCiphertextListExpansionKind, @@ -66,7 +65,6 @@ fn cpk_ks_any_ms_inner_helper_gpu( DecryptionAndNoiseResult, DecryptionAndNoiseResult, ) { - let mut engine = ShortintEngine::new(); let thread_cpk_private_key; let thread_cpk; let thread_cuda_ksk; @@ -124,11 +122,9 @@ fn cpk_ks_any_ms_inner_helper_gpu( }; let mut cuda_side_resources = CudaSideResources::new(streams, cuda_block_info); let ct = { - let compact_list = cpk.key.encrypt_iter_with_modulus_with_engine( - core::iter::once(msg), - cpk.key.parameters.message_modulus.0, - &mut engine, - ); + let compact_list = cpk + .key + .encrypt_iter_with_modulus(core::iter::once(msg), cpk.key.parameters.message_modulus.0); let num_blocks = 1usize; @@ -648,11 +644,9 @@ fn sanity_check_encrypt_cpk_ks_ms_pbs_gpu(meta_params: MetaParameters, filename_ for _ in 0..10 { let (gpu_sample_input, shortint_res) = { - let mut engine = ShortintEngine::new(); - let no_casting_compact_list = cpk.key.encrypt_iter_with_modulus_with_engine( + let no_casting_compact_list = cpk.key.encrypt_iter_with_modulus( core::iter::once(0), cpk.key.parameters.message_modulus.0, - &mut engine, ); let num_blocks = 1usize; 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 4ae4d57f17..b42520ab9e 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 @@ -1032,6 +1032,11 @@ pub struct ProvenCompactCiphertextList(crate::high_level_api::ProvenCompactCiphe #[wasm_bindgen] impl CompactCiphertextList { + #[wasm_bindgen] + pub fn eq(&self, other: &CompactCiphertextList) -> bool { + self.0 == other.0 + } + #[wasm_bindgen] pub fn builder( public_key: &TfheCompactPublicKey, @@ -1121,6 +1126,11 @@ impl ProvenCompactCiphertextList { }) } + #[wasm_bindgen] + pub fn eq(&self, other: &ProvenCompactCiphertextList) -> bool { + self.0 == other.0 + } + #[wasm_bindgen] pub fn len(&self) -> usize { self.0.len() @@ -1425,6 +1435,14 @@ impl CompactCiphertextListBuilder { }) } + #[wasm_bindgen] + pub fn build_packed_seeded(&self, seed: &[u8]) -> Result { + catch_panic_result(|| { + let inner = self.0.build_packed_seeded(seed).map_err(into_js_error)?; + Ok(CompactCiphertextList(inner)) + }) + } + #[cfg(feature = "zk-pok")] pub fn build_with_proof_packed( &self, @@ -1439,6 +1457,22 @@ impl CompactCiphertextListBuilder { .map(ProvenCompactCiphertextList) }) } + + #[cfg(feature = "zk-pok")] + pub fn build_with_proof_packed_seeded( + &self, + crs: &CompactPkeCrs, + metadata: &[u8], + compute_load: ZkComputeLoad, + seed: &[u8], + ) -> Result { + catch_panic_result(|| { + self.0 + .build_with_proof_packed_seeded(&crs.0, metadata, compute_load.into(), seed) + .map_err(into_js_error) + .map(ProvenCompactCiphertextList) + }) + } } #[cfg(feature = "extended-types")] diff --git a/tfhe/src/shortint/ciphertext/zk.rs b/tfhe/src/shortint/ciphertext/zk.rs index e415979dec..b721ef79bb 100644 --- a/tfhe/src/shortint/ciphertext/zk.rs +++ b/tfhe/src/shortint/ciphertext/zk.rs @@ -76,7 +76,7 @@ impl CompactPkeCrs { /// 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, Versionize)] +#[derive(Clone, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(ProvenCompactCiphertextListVersions)] pub struct ProvenCompactCiphertextList { pub(crate) proved_lists: Vec<(CompactCiphertextList, CompactPkeProof)>, diff --git a/tfhe/src/shortint/public_key/compact.rs b/tfhe/src/shortint/public_key/compact.rs index 5da5264f94..953b18a8ca 100644 --- a/tfhe/src/shortint/public_key/compact.rs +++ b/tfhe/src/shortint/public_key/compact.rs @@ -1,8 +1,10 @@ use crate::conformance::ParameterSetConformant; +use crate::core_crypto::commons::generators::NoiseRandomGenerator; +use crate::core_crypto::commons::math::random::{DefaultRandomGenerator, RandomGenerator, XofSeed}; use crate::core_crypto::prelude::{ allocate_and_generate_new_binary_lwe_secret_key, allocate_and_generate_new_seeded_lwe_compact_public_key, generate_lwe_compact_public_key, - Cleartext, Container, LweCiphertextCount, LweCompactCiphertextListOwned, + new_seeder, Cleartext, Container, LweCiphertextCount, LweCompactCiphertextListOwned, LweCompactPublicKeyConformanceParams, LweCompactPublicKeyOwned, LweSecretKey, Plaintext, PlaintextList, SeededLweCompactPublicKeyOwned, }; @@ -23,7 +25,6 @@ use crate::zk::{CompactPkeCrs, ZkComputeLoad}; use crate::Error; use serde::{Deserialize, Serialize}; use tfhe_versionable::Versionize; - /// Private key from which a [`CompactPublicKey`] can be built. #[derive(Clone, Debug, Serialize, Deserialize, Versionize)] #[versionize(CompactPrivateKeyVersions)] @@ -326,17 +327,18 @@ impl CompactPublicKey { messages: impl Iterator, encryption_modulus: u64, ) -> CompactCiphertextList { - ShortintEngine::with_thread_local_mut(|engine| { - self.encrypt_iter_with_modulus_with_engine(messages, encryption_modulus, engine) - }) + let seed = new_seeder().seed(); + self.encrypt_iter_with_modulus_seeded(messages, encryption_modulus, &seed.0.to_le_bytes()) + .expect("internal error: seed from seeder is always 16 bytes") } - pub fn encrypt_iter_with_modulus_with_engine( + pub fn encrypt_iter_with_modulus_seeded( &self, messages: impl Iterator, encryption_modulus: u64, - engine: &mut ShortintEngine, - ) -> CompactCiphertextList { + seed: &[u8], + ) -> crate::Result { + let mut noise_generator = noise_generator_from_seed(seed)?; let plaintext_container = to_plaintext_iterator(messages, encryption_modulus, &self.parameters) .map(|plaintext| plaintext.0) @@ -362,7 +364,7 @@ impl CompactPublicKey { &plaintext_list, encryption_noise_distribution, encryption_noise_distribution, - engine.encryption_generator.noise_generator_mut(), + &mut noise_generator, ); } @@ -376,18 +378,18 @@ impl CompactPublicKey { &plaintext_list, encryption_noise_distribution, encryption_noise_distribution, - engine.encryption_generator.noise_generator_mut(), + &mut noise_generator, ); } let message_modulus = self.parameters.message_modulus; - CompactCiphertextList { + Ok(CompactCiphertextList { ct_list, degree: Degree::new(encryption_modulus - 1), message_modulus, carry_modulus: self.parameters.carry_modulus, expansion_kind: self.parameters.expansion_kind, - } + }) } #[cfg(feature = "zk-pok")] @@ -398,10 +400,37 @@ impl CompactPublicKey { metadata: &[u8], load: ZkComputeLoad, encryption_modulus: u64, + ) -> crate::Result { + let seed = new_seeder().seed(); + self.encrypt_and_prove_slice_seeded( + messages, + crs, + metadata, + load, + encryption_modulus, + &seed.0.to_le_bytes(), + ) + } + + #[cfg(feature = "zk-pok")] + pub fn encrypt_and_prove_slice_seeded( + &self, + messages: &[u64], + crs: &CompactPkeCrs, + metadata: &[u8], + load: ZkComputeLoad, + encryption_modulus: u64, + seed: &[u8], ) -> crate::Result { let plaintext_modulus = self.parameters.message_modulus.0 * self.parameters.carry_modulus.0; - assert!(encryption_modulus <= plaintext_modulus); + if encryption_modulus > plaintext_modulus { + return Err(crate::Error::new(format!( + "encryption_modulus ({encryption_modulus}) must be <= \ + message_modulus * carry_modulus ({plaintext_modulus})" + ))); + } let delta = self.encoding().delta(); + let mut noise_generator = noise_generator_from_seed(seed)?; // This is the maximum number of lwe that can share the same mask in lwe compact pk // encryption @@ -427,42 +456,36 @@ impl CompactPublicKey { #[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, - engine.encryption_generator.noise_generator_mut(), - &mut engine.random_generator, - crs, - metadata, - load, - ) - }) + 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 noise_generator, + crs, + metadata, + load, + ) }?; - // Parallelism allowed / + // 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, - engine.encryption_generator.noise_generator_mut(), - &mut engine.random_generator, - crs, - metadata, - load, - ) - }) + 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 noise_generator, + crs, + metadata, + load, + ) }?; let message_modulus = self.parameters.message_modulus; @@ -498,6 +521,20 @@ impl CompactPublicKey { } } +fn noise_generator_from_seed( + seed: &[u8], +) -> crate::Result> { + if seed.len() < 16 { + return Err(crate::Error::new(format!( + "seed must be at least 16 bytes, got {}", + seed.len() + ))); + } + let xof_seed = XofSeed::new(seed.to_vec(), *b"TFHE_Enc"); + let random_generator = RandomGenerator::::new(xof_seed); + Ok(NoiseRandomGenerator::from_raw_parts(random_generator)) +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(CompressedCompactPublicKeyVersions)] pub struct CompressedCompactPublicKey { diff --git a/tfhe/src/shortint/server_key/tests/noise_distribution/br_rerand_dp_ks_ms.rs b/tfhe/src/shortint/server_key/tests/noise_distribution/br_rerand_dp_ks_ms.rs index a8a452ae13..5df32fa57d 100644 --- a/tfhe/src/shortint/server_key/tests/noise_distribution/br_rerand_dp_ks_ms.rs +++ b/tfhe/src/shortint/server_key/tests/noise_distribution/br_rerand_dp_ks_ms.rs @@ -274,11 +274,8 @@ where let ct = comp_private_key.encrypt_noiseless_decompression_input_dyn_lwe(cks, msg, &mut engine); let cpk_ct_zero_rerand = { - let compact_list = cpk.encrypt_iter_with_modulus_with_engine( - core::iter::once(0), - cpk.parameters.message_modulus.0, - &mut engine, - ); + let compact_list = + cpk.encrypt_iter_with_modulus(core::iter::once(0), cpk.parameters.message_modulus.0); let mut expanded = compact_list .expand(ShortintCompactCiphertextListCastingMode::NoCasting) .unwrap(); diff --git a/tfhe/src/shortint/server_key/tests/noise_distribution/cpk_ks_ms.rs b/tfhe/src/shortint/server_key/tests/noise_distribution/cpk_ks_ms.rs index b99025e390..6c5e470f7f 100644 --- a/tfhe/src/shortint/server_key/tests/noise_distribution/cpk_ks_ms.rs +++ b/tfhe/src/shortint/server_key/tests/noise_distribution/cpk_ks_ms.rs @@ -144,11 +144,8 @@ fn cpk_ks_any_ms_inner_helper( let modulus_switch_config = sks.noise_simulation_modulus_switch_config(); let ct = { - let compact_list = cpk.encrypt_iter_with_modulus_with_engine( - core::iter::once(msg), - cpk.parameters.message_modulus.0, - &mut engine, - ); + let compact_list = + cpk.encrypt_iter_with_modulus(core::iter::once(msg), cpk.parameters.message_modulus.0); let mut expanded = compact_list .expand(ShortintCompactCiphertextListCastingMode::NoCasting) .unwrap(); @@ -574,12 +571,8 @@ fn sanity_check_encrypt_cpk_ks_ms_pbs(meta_params: MetaParameters, filename_suff for _ in 0..10 { let (sample_input, shortint_res) = { - let mut engine = ShortintEngine::new(); - let no_casting_compact_list = cpk.encrypt_iter_with_modulus_with_engine( - core::iter::once(0), - cpk.parameters.message_modulus.0, - &mut engine, - ); + let no_casting_compact_list = cpk + .encrypt_iter_with_modulus(core::iter::once(0), cpk.parameters.message_modulus.0); let mut shortint_casting_compact_list = no_casting_compact_list.clone(); shortint_casting_compact_list.expansion_kind = orig_cast_mode; diff --git a/tfhe/src/zk/mod.rs b/tfhe/src/zk/mod.rs index 9daf4db773..541834ba82 100644 --- a/tfhe/src/zk/mod.rs +++ b/tfhe/src/zk/mod.rs @@ -1,9 +1,7 @@ pub mod backward_compatibility; use crate::conformance::{EnumSet, ParameterSetConformant}; -use crate::core_crypto::commons::math::random::{ - BoundedDistribution, ByteRandomGenerator, RandomGenerator, -}; +use crate::core_crypto::commons::math::random::BoundedDistribution; use crate::core_crypto::prelude::*; use crate::named::Named; #[cfg(feature = "shortint")] @@ -31,7 +29,7 @@ pub use tfhe_zk_pok::proofs::pke_v2::PkeV2SupportedHashConfig as ZkPkeV2Supporte pub use tfhe_zk_pok::proofs::ComputeLoad as ZkComputeLoad; type Curve = tfhe_zk_pok::curve_api::Bls12_446; -#[derive(Clone, Debug, Serialize, Deserialize, Versionize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(CompactPkeProofVersions)] #[allow(clippy::large_enum_variant)] pub enum CompactPkeProof { @@ -632,7 +630,7 @@ impl CompactPkeCrs { /// Prove a ciphertext list encryption using this CRS #[allow(clippy::too_many_arguments)] - pub fn prove( + pub fn prove( &self, compact_public_key: &LweCompactPublicKey, messages: &InputCont, @@ -642,7 +640,7 @@ impl CompactPkeCrs { body_noise: &[Scalar], metadata: &[u8], load: ZkComputeLoad, - random_generator: &mut RandomGenerator, + seed: [u8; 16], ) -> CompactPkeProof where Scalar: UnsignedInteger, @@ -650,7 +648,6 @@ impl CompactPkeCrs { KeyCont: Container, InputCont: Container, ListCont: Container, - G: ByteRandomGenerator, { let key_mask = compact_public_key .get_mask() @@ -707,10 +704,6 @@ impl CompactPkeCrs { .map(CastFrom::cast_from) .collect::>(); - // 128bits seed as defined in the NIST document - let mut seed = [0u8; 16]; - random_generator.fill_bytes(&mut seed); - match self { Self::PkeV1(public_params) => { let (public_commit, private_commit) = commit_v1( diff --git a/utils/tfhe-backward-compat-data/crates/generate_1_6/src/lib.rs b/utils/tfhe-backward-compat-data/crates/generate_1_6/src/lib.rs index 26d7a9a9a5..e4eb85608c 100644 --- a/utils/tfhe-backward-compat-data/crates/generate_1_6/src/lib.rs +++ b/utils/tfhe-backward-compat-data/crates/generate_1_6/src/lib.rs @@ -10,7 +10,11 @@ use tfhe::core_crypto::commons::generators::DeterministicSeeder; use tfhe::core_crypto::prelude::{DefaultRandomGenerator, NormalizedHammingWeightBound}; use tfhe::shortint::engine::ShortintEngine; use tfhe::xof_key_set::CompressedXofKeySet; -use tfhe::{ClientKey, CompressedCompactPublicKey, CompressedServerKey, Seed, Tag}; +use tfhe::zk::{CompactPkeCrs, ZkComputeLoad}; +use tfhe::{ + ClientKey, CompactCiphertextList, CompactPublicKey, CompressedCompactPublicKey, + CompressedServerKey, ProvenCompactCiphertextList, Seed, ServerKey, Tag, set_server_key, +}; use tfhe_backward_compat_data::generate::*; use tfhe_backward_compat_data::*; @@ -25,6 +29,34 @@ const HL_SERVER_KEY_TEST: HlServerKeyTest = HlServerKeyTest { rerand_cpk_filename: Some(Cow::Borrowed("cpk_rerand_complete")), compressed: true, }; +const SEEDED_COMPACT_LIST_SEED: &[u8] = &167644343036794213320094654445260117732u128.to_le_bytes(); + +fn hl_seeded_compactlist_test() -> HlSeededCompactCiphertextListTest { + HlSeededCompactCiphertextListTest { + test_filename: Cow::Borrowed("hl_seeded_compact_list"), + key_filename: Cow::Borrowed("seeded_client_key"), + public_key_filename: Cow::Borrowed("seeded_compact_public_key"), + clear_values: Cow::Borrowed(&[17u64, 255u64, 0u64]), + data_kinds: Cow::Borrowed(&[DataKind::Unsigned, DataKind::Signed, DataKind::Bool]), + seed: SEEDED_COMPACT_LIST_SEED.to_vec(), + } +} + +fn hl_seeded_proven_compactlist_test() -> HlSeededProvenCompactCiphertextListTest { + HlSeededProvenCompactCiphertextListTest { + test_filename: Cow::Borrowed("hl_seeded_proven_compact_list"), + key_filename: Cow::Borrowed("seeded_proven_client_key"), + public_key_filename: Cow::Borrowed("seeded_proven_compact_public_key"), + proof_info: PkeZkProofAuxiliaryInfo { + public_key_filename: Cow::Borrowed("seeded_proven_compact_public_key"), + params_filename: Cow::Borrowed("seeded_proven_zk_pke_crs"), + metadata: Cow::Borrowed("backward_compat_seeded"), + }, + clear_values: Cow::Borrowed(&[17u64, 255u64, 0u64]), + data_kinds: Cow::Borrowed(&[DataKind::Unsigned, DataKind::Signed, DataKind::Bool]), + seed: SEEDED_COMPACT_LIST_SEED.to_vec(), + } +} pub struct V1_6; @@ -91,9 +123,106 @@ impl TfhersVersion for V1_6 { ); } + let seeded_test = hl_seeded_compactlist_test(); + let seeded_proven_test = hl_seeded_proven_compactlist_test(); + + // Generate seeded compact ciphertext list (no proof) + { + let config = tfhe::ConfigBuilder::with_custom_parameters( + INSECURE_SMALL_TEST_PARAMS_KS32.convert(), + ) + .build(); + let hl_client_key = ClientKey::generate(config); + let hl_server_key = ServerKey::new(&hl_client_key); + set_server_key(hl_server_key); + let compact_pub_key = CompactPublicKey::new(&hl_client_key); + + store_versioned_auxiliary(&hl_client_key, &dir, &seeded_test.key_filename); + store_versioned_auxiliary(&compact_pub_key, &dir, &seeded_test.public_key_filename); + + let mut builder = CompactCiphertextList::builder(&compact_pub_key); + for (value, kind) in seeded_test + .clear_values + .iter() + .zip(seeded_test.data_kinds.iter()) + { + match kind { + DataKind::Unsigned => { + builder.push(*value as u8); + } + DataKind::Signed => { + builder.push(*value as i8); + } + DataKind::Bool => { + builder.push(*value != 0); + } + } + } + + let list = builder.build_packed_seeded(&seeded_test.seed).unwrap(); + store_versioned_test(&list, &dir, &seeded_test.test_filename()); + } + + // Generate seeded proven compact ciphertext list + { + let config = tfhe::ConfigBuilder::with_custom_parameters( + INSECURE_SMALL_TEST_PARAMS_KS32.convert(), + ) + .use_dedicated_compact_public_key_parameters(( + INSECURE_DEDICATED_CPK_TEST_PARAMS.convert(), + KS_TO_SMALL_TEST_PARAMS.convert(), + )) + .build(); + let hl_client_key = ClientKey::generate(config); + let hl_server_key = ServerKey::new(&hl_client_key); + set_server_key(hl_server_key); + let compact_pub_key = CompactPublicKey::new(&hl_client_key); + let crs = CompactPkeCrs::from_config(config, 64).unwrap(); + + store_versioned_auxiliary(&hl_client_key, &dir, &seeded_proven_test.key_filename); + store_versioned_auxiliary( + &compact_pub_key, + &dir, + &seeded_proven_test.public_key_filename, + ); + store_versioned_auxiliary(&crs, &dir, &seeded_proven_test.proof_info.params_filename); + + let mut proven_builder = ProvenCompactCiphertextList::builder(&compact_pub_key); + for (value, kind) in seeded_proven_test + .clear_values + .iter() + .zip(seeded_proven_test.data_kinds.iter()) + { + match kind { + DataKind::Unsigned => { + proven_builder.push(*value as u8); + } + DataKind::Signed => { + proven_builder.push(*value as i8); + } + DataKind::Bool => { + proven_builder.push(*value != 0); + } + } + } + + let proven_list = proven_builder + .build_with_proof_packed_seeded( + &crs, + seeded_proven_test.proof_info.metadata.as_bytes(), + ZkComputeLoad::Proof, + &seeded_proven_test.seed, + ) + .unwrap(); + + store_versioned_test(&proven_list, &dir, &seeded_proven_test.test_filename()); + } + vec![ TestMetadata::HlCompressedXofKeySet(HL_COMPRESSED_XOF_KEY_SET_TEST), TestMetadata::HlServerKey(HL_SERVER_KEY_TEST), + TestMetadata::HlSeededCompactCiphertextList(seeded_test), + TestMetadata::HlSeededProvenCompactCiphertextList(seeded_proven_test), ] } } diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.bcode b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.bcode new file mode 100644 index 0000000000..7afd1e32f5 --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.bcode @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:066c3644bad4d3c29921884d48b2852687bd0ff3bc6789e6c25005193f63a333 +size 16612 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.cbor new file mode 100644 index 0000000000..90c7305f17 --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b413e3e4211226c57e835b49df42aa30fcf7fb42b4a47f40d0e22e0d98e0395c +size 18776 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.bcode b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.bcode new file mode 100644 index 0000000000..0dd78b3c47 --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.bcode @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31142630dd29bc37e0bbf3d18ff1a7ac5aea857d0767d310543453de2114ab7b +size 3105 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.cbor new file mode 100644 index 0000000000..40f9a4cd8a --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0af8cbd0ffdf5dc11d7648c4c356d7fd5f6fb97c0e0c841f02b9783d31bf92e9 +size 3832 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_client_key.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_client_key.cbor new file mode 100644 index 0000000000..efb723865d --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_client_key.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8f796def1a0f839a441a328fa9ecc80eb91bd49f67dcb98a130482fbfc6b508 +size 2898 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_compact_public_key.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_compact_public_key.cbor new file mode 100644 index 0000000000..b5b7f36468 --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_compact_public_key.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d516db8e497f0d66cc6f2572622dca0d3d7ae4b66f826086cf5445476e6ba36c +size 37303 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_client_key.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_client_key.cbor new file mode 100644 index 0000000000..136af156b7 --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_client_key.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e96858f37afc9ca23948a5f590c21e6e9f9b4ad70a5d5e6a505273993ec9d86e +size 3282 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_compact_public_key.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_compact_public_key.cbor new file mode 100644 index 0000000000..30aa1f4d4b --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_compact_public_key.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc3336c6ae239f8f522f99e1f0b07ab13c34499645c7fb5a9d8e91abc634e6e3 +size 990 diff --git a/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_zk_pke_crs.cbor b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_zk_pke_crs.cbor new file mode 100644 index 0000000000..bcf89445ab --- /dev/null +++ b/utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_zk_pke_crs.cbor @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f3ab84245fff69d7ee2886c7c2dd25b1c8810a3a35b57895add8c254e07ed7e +size 4385122 diff --git a/utils/tfhe-backward-compat-data/data/high_level_api.ron b/utils/tfhe-backward-compat-data/data/high_level_api.ron index cc4f82ae93..79fe36151b 100644 --- a/utils/tfhe-backward-compat-data/data/high_level_api.ron +++ b/utils/tfhe-backward-compat-data/data/high_level_api.ron @@ -883,4 +883,83 @@ compressed: true, )), ), + ( + tfhe_version_min: "1.6", + tfhe_module: "high_level_api", + metadata: HlSeededCompactCiphertextList(( + test_filename: "hl_seeded_compact_list", + key_filename: "seeded_client_key", + public_key_filename: "seeded_compact_public_key", + clear_values: [ + 17, + 255, + 0, + ], + data_kinds: [ + Unsigned, + Signed, + Bool, + ], + seed: [ + 228, + 22, + 68, + 156, + 244, + 108, + 36, + 247, + 77, + 86, + 65, + 64, + 67, + 32, + 31, + 126, + ], + )), + ), + ( + tfhe_version_min: "1.6", + tfhe_module: "high_level_api", + metadata: HlSeededProvenCompactCiphertextList(( + test_filename: "hl_seeded_proven_compact_list", + key_filename: "seeded_proven_client_key", + public_key_filename: "seeded_proven_compact_public_key", + proof_info: ( + public_key_filename: "seeded_proven_compact_public_key", + params_filename: "seeded_proven_zk_pke_crs", + metadata: "backward_compat_seeded", + ), + clear_values: [ + 17, + 255, + 0, + ], + data_kinds: [ + Unsigned, + Signed, + Bool, + ], + seed: [ + 228, + 22, + 68, + 156, + 244, + 108, + 36, + 247, + 77, + 86, + 65, + 64, + 67, + 32, + 31, + 126, + ], + )), + ), ] \ No newline at end of file diff --git a/utils/tfhe-backward-compat-data/src/lib.rs b/utils/tfhe-backward-compat-data/src/lib.rs index 97c625246b..1af9f8e9a5 100644 --- a/utils/tfhe-backward-compat-data/src/lib.rs +++ b/utils/tfhe-backward-compat-data/src/lib.rs @@ -557,6 +557,55 @@ impl TestType for HlHeterogeneousCiphertextListTest { } } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct HlSeededCompactCiphertextListTest { + pub test_filename: Cow<'static, str>, + pub key_filename: Cow<'static, str>, + pub public_key_filename: Cow<'static, str>, + pub clear_values: Cow<'static, [u64]>, + pub data_kinds: Cow<'static, [DataKind]>, + pub seed: Vec, +} + +impl TestType for HlSeededCompactCiphertextListTest { + fn module(&self) -> String { + HL_MODULE_NAME.to_string() + } + + fn target_type(&self) -> String { + "CompactCiphertextList".to_string() + } + + fn test_filename(&self) -> String { + self.test_filename.to_string() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct HlSeededProvenCompactCiphertextListTest { + pub test_filename: Cow<'static, str>, + pub key_filename: Cow<'static, str>, + pub public_key_filename: Cow<'static, str>, + pub proof_info: PkeZkProofAuxiliaryInfo, + pub clear_values: Cow<'static, [u64]>, + pub data_kinds: Cow<'static, [DataKind]>, + pub seed: Vec, +} + +impl TestType for HlSeededProvenCompactCiphertextListTest { + fn module(&self) -> String { + HL_MODULE_NAME.to_string() + } + + fn target_type(&self) -> String { + "ProvenCompactCiphertextList".to_string() + } + + fn test_filename(&self) -> String { + self.test_filename.to_string() + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct HlCompressedSquashedNoiseCiphertextListTest { pub test_filename: Cow<'static, str>, @@ -731,6 +780,8 @@ pub enum TestMetadata { HlCompressedSquashedNoiseCiphertextList(HlCompressedSquashedNoiseCiphertextListTest), HlCompressedKVStoreTest(HlCompressedKVStoreTest), HlCompressedXofKeySet(HlCompressedXofKeySetTest), + HlSeededCompactCiphertextList(HlSeededCompactCiphertextListTest), + HlSeededProvenCompactCiphertextList(HlSeededProvenCompactCiphertextListTest), } #[derive(Serialize, Deserialize, Clone, Debug)]