From 580f8dba5fb2f7e50ad0a75099a70526289eb245 Mon Sep 17 00:00:00 2001 From: Thomas Montaigu Date: Wed, 11 Mar 2026 12:02:31 +0100 Subject: [PATCH 1/3] chore: bump csprng to 0.9.0 --- tfhe-csprng/Cargo.toml | 2 +- tfhe/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/Cargo.toml b/tfhe/Cargo.toml index 9eeb6bb9e4..e90776efc5 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"] } From 7d08b16f2ce208b3c2e8acf07c9c3b1b1ca3f584 Mon Sep 17 00:00:00 2001 From: Thomas Montaigu Date: Wed, 11 Mar 2026 12:17:32 +0100 Subject: [PATCH 2/3] chore: bump zk-pok to 0.8.1 --- tfhe-zk-pok/Cargo.toml | 2 +- tfhe/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/Cargo.toml b/tfhe/Cargo.toml index e90776efc5..90050063d8 100644 --- a/tfhe/Cargo.toml +++ b/tfhe/Cargo.toml @@ -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 From 7ebf4890fe2fdb4e0b45410bc9a980579fc0dec9 Mon Sep 17 00:00:00 2001 From: Thomas Montaigu Date: Mon, 16 Mar 2026 16:04:48 +0100 Subject: [PATCH 3/3] feat(all): add seeded public encryption for compact lists This allows to create CompactCiphertextList and ProvenCompactCiphertextList using a seed, so that the encryption can be reproduced * Follows NIST submission: - Create XofSeed from some seed bytes - Then init a NoiseRandomGenerator from the XofSeed - Use the gnerator to do the public encryption - When a zk proof is needed, for each chunk create the seed for the zk-proof by taking the next 16 bytes of noise_random_generator. This is custom to tfhe-rs as NIST submission does not cover this case * JS API + tests included * Backward compatibility tests Backward compatibility tests are included, as since this produces seeded data, we need to be able to guarantee backward compatibility. --- .../backward_compatibility/high_level_api.rs | 213 ++++++++++++++++-- tfhe-zk-pok/src/curve_api.rs | 4 +- tfhe-zk-pok/src/proofs/pke.rs | 4 +- tfhe-zk-pok/src/proofs/pke_v2/hashes.rs | 2 +- tfhe-zk-pok/src/proofs/pke_v2/mod.rs | 4 +- tfhe/js_on_wasm_tests/test-hlapi-signed.js | 104 +++++++++ .../core_crypto/algorithms/lwe_encryption.rs | 27 +-- .../algorithms/test/lwe_encryption.rs | 3 - .../encryption/noise_random_generator.rs | 6 + tfhe/src/high_level_api/compact_list.rs | 168 +++++++++++++- tfhe/src/integer/ciphertext/compact_list.rs | 62 ++++- .../tests_noise_distribution/cpk_ks_ms.rs | 14 +- .../js_high_level_api/integers.rs | 34 +++ tfhe/src/shortint/ciphertext/zk.rs | 2 +- tfhe/src/shortint/public_key/compact.rs | 124 ++++++---- .../noise_distribution/br_rerand_dp_ks_ms.rs | 7 +- .../tests/noise_distribution/cpk_ks_ms.rs | 15 +- tfhe/src/zk/mod.rs | 15 +- .../crates/generate_0_11/src/lib.rs | 2 +- .../crates/generate_0_8/src/lib.rs | 6 +- .../crates/generate_1_3/src/lib.rs | 12 +- .../crates/generate_1_5/src/lib.rs | 2 +- .../crates/generate_1_6/src/lib.rs | 155 ++++++++++++- .../hl_seeded_compact_list.bcode | 3 + .../hl_seeded_compact_list.cbor | 3 + .../hl_seeded_proven_compact_list.bcode | 3 + .../hl_seeded_proven_compact_list.cbor | 3 + .../1_6/high_level_api/seeded_client_key.cbor | 3 + .../seeded_compact_public_key.cbor | 3 + .../seeded_proven_client_key.cbor | 3 + .../seeded_proven_compact_public_key.cbor | 3 + .../seeded_proven_zk_pke_crs.cbor | 3 + .../data/high_level_api.ron | 93 +++++++- utils/tfhe-backward-compat-data/src/lib.rs | 42 +++- 34 files changed, 991 insertions(+), 156 deletions(-) create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.bcode create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_compact_list.cbor create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.bcode create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/hl_seeded_proven_compact_list.cbor create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_client_key.cbor create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_compact_public_key.cbor create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_client_key.cbor create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_compact_public_key.cbor create mode 100644 utils/tfhe-backward-compat-data/data/1_6/high_level_api/seeded_proven_zk_pke_crs.cbor diff --git a/tests/backward_compatibility/high_level_api.rs b/tests/backward_compatibility/high_level_api.rs index 04c17905dc..014d11f88f 100644 --- a/tests/backward_compatibility/high_level_api.rs +++ b/tests/backward_compatibility/high_level_api.rs @@ -32,9 +32,10 @@ use tfhe_backward_compat_data::load::{ use tfhe_backward_compat_data::{ DataKind, HlBoolCiphertextTest, HlCiphertextTest, HlClientKeyTest, HlCompressedKVStoreTest, HlCompressedSquashedNoiseCiphertextListTest, HlCompressedXofKeySetTest, - HlHeterogeneousCiphertextListTest, HlPublicKeyTest, HlServerKeyTest, HlSignedCiphertextTest, - HlSquashedNoiseBoolCiphertextTest, HlSquashedNoiseSignedCiphertextTest, - HlSquashedNoiseUnsignedCiphertextTest, TestMetadata, TestType, Testcase, ZkPkePublicParamsTest, + HlHeterogeneousCiphertextListTest, HlPublicKeyTest, HlSeededCompactCiphertextListTest, + HlServerKeyTest, HlSignedCiphertextTest, HlSquashedNoiseBoolCiphertextTest, + HlSquashedNoiseSignedCiphertextTest, HlSquashedNoiseUnsignedCiphertextTest, TestMetadata, + TestType, Testcase, ZkPkePublicParamsTest, ZkProofAuxiliaryInfo, }; use tfhe_versionable::Unversionize; @@ -249,44 +250,206 @@ pub fn test_hl_heterogeneous_ciphertext_list_elements( key: &ClientKey, test: &HlHeterogeneousCiphertextListTest, ) -> Result<(), String> { - for idx in 0..(list.len()) { - match test.data_kinds[idx] { + verify_expanded_values(&list, &test.clear_values, &test.data_kinds, key) +} + +fn verify_expanded_values( + list: &CtList, + clear_values: &[i64], + data_kinds: &[DataKind], + key: &ClientKey, +) -> Result<(), String> +where + CtList: CiphertextList, +{ + if clear_values.len() != data_kinds.len() { + return Err(format!( + "Number of clear values ({}) is not the same as the number of data kind ({})", + clear_values.len(), + data_kinds.len(), + )); + } + + if clear_values.len() != list.len() { + return Err(format!( + "Number of clear values ({}) is not the same as the number of values in the expander({})", + clear_values.len(), + list.len(), + )); + } + + for (idx, (value, kind)) in clear_values.iter().zip(data_kinds.iter()).enumerate() { + match kind { DataKind::Bool => { - let ct: FheBool = list.get(idx).unwrap().unwrap(); + let ct: FheBool = list + .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 != (test.clear_values[idx] != 0) { + if clear != (*value != 0) { return Err(format!( - "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", - clear, test.clear_values[idx] + "Invalid decrypted bool at index {idx}: expected {:?}, got {clear:?}", + *value != 0 )); } } DataKind::Signed => { - let ct: FheInt8 = list.get(idx).unwrap().unwrap(); + let ct: FheInt8 = list + .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 != test.clear_values[idx] as i8 { + if clear != *value as i8 { return Err(format!( - "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", - clear, - (test.clear_values[idx] as i8) + "Invalid decrypted signed at index {idx}: expected {:?}, got {clear:?}", + *value as i8 )); } } DataKind::Unsigned => { - let ct: FheUint8 = list.get(idx).unwrap().unwrap(); + let ct: FheUint8 = list + .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 != test.clear_values[idx] as u8 { + if clear != *value as u8 { return Err(format!( - "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", - clear, test.clear_values[idx] + "Invalid decrypted unsigned at index {idx}: expected {:?}, got {clear:?}", + *value as u8 )); } } - }; + } } Ok(()) } +/// Shared core for seeded compact ciphertext list backward compat tests. +/// When `zk_proof_info` is `Some`, operates in ZK (proven) mode; otherwise plain mode. +#[allow(clippy::too_many_arguments)] +fn test_hl_seeded_compact_list_core( + dir: &Path, + test: &T, + format: DataFormat, + key_filename: &str, + public_key_filename: &str, + clear_values: &[i64], + data_kinds: &[DataKind], + seed: &[u8], + zk_proof_info: Option<&ZkProofAuxiliaryInfo>, +) -> Result { + #[cfg(not(feature = "zk-pok"))] + if zk_proof_info.is_some() { + return Ok(test.success(format)); + } + + let key_file = dir.join(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(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 mut builder = CompactCiphertextList::builder(&pubkey); + for (value, kind) in clear_values.iter().zip(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); + } + } + } + + if let Some(_proof_info) = zk_proof_info { + #[cfg(feature = "zk-pok")] + { + use tfhe::zk::ZkComputeLoad; + + let crs_file = dir.join(&*_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 rebuilt = builder + .build_with_proof_packed_seeded( + &crs, + _proof_info.metadata.as_bytes(), + ZkComputeLoad::Proof, + 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, _proof_info.metadata.as_bytes()) + .map_err(|e| test.failure(e, format))?; + verify_expanded_values(&expanded, clear_values, data_kinds, &key) + .map_err(|e| test.failure(e, format))?; + } + } else { + let pregenerated: CompactCiphertextList = load_and_unversionize(dir, test, format)?; + + let rebuilt = builder.build_packed_seeded(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, clear_values, data_kinds, &key) + .map_err(|e| test.failure(e, format))?; + } + + Ok(test.success(format)) +} + +/// 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 { + test_hl_seeded_compact_list_core( + dir, + test, + format, + &test.key_filename, + &test.public_key_filename, + &test.clear_values, + &test.data_kinds, + &test.seed, + test.proof_info.as_ref(), + ) +} + +/// Test seeded proven compact ciphertext list /// Test HL client key: loads the key and checks the parameters using the values stored in /// the test metadata. pub fn test_hl_clientkey( @@ -778,21 +941,20 @@ pub fn test_hl_compressed_squashed_noise_ciphertext_list( } for i in 0..list.len() { - let decrypted = match test.data_kinds[i] { + let decrypted: i64 = match test.data_kinds[i] { DataKind::Unsigned => { let ct: SquashedNoiseFheUint = list.get(i).unwrap().unwrap(); let clear: u64 = ct.decrypt(&key); - clear + clear as i64 } DataKind::Signed => { let ct: SquashedNoiseFheInt = list.get(i).unwrap().unwrap(); - let clear: i64 = ct.decrypt(&key); - clear as u64 + ct.decrypt(&key) } DataKind::Bool => { let ct: SquashedNoiseFheBool = list.get(i).unwrap().unwrap(); let clear: bool = ct.decrypt(&key); - clear as u64 + clear as i64 } }; @@ -953,6 +1115,9 @@ 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() + } _ => { println!("WARNING: missing test: {:?}", testcase.metadata); TestResult::Skipped(testcase.skip()) 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/js_on_wasm_tests/test-hlapi-signed.js b/tfhe/js_on_wasm_tests/test-hlapi-signed.js index 4c3fc8ef7e..cf3866f798 100644 --- a/tfhe/js_on_wasm_tests/test-hlapi-signed.js +++ b/tfhe/js_on_wasm_tests/test-hlapi-signed.js @@ -612,6 +612,110 @@ 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 = randomBytes(16); + let seed_b = randomBytes(16); + console.log( + `proven seeded test — seed_a: [${Array.from(seed_a).join(", ")}], seed_b: [${Array.from(seed_b).join(", ")}]`, + ); + + const buildProvenSeeded = (seed) => { + let builder = CompactCiphertextList.builder(publicKey); + builder.push_u2(3); + builder.push_i32(-3284); + builder.push_boolean(true); + return builder.build_with_proof_packed_seeded( + crs, + metadata, + ZkComputeLoad.Proof, + seed, + ); + }; + + let list_a1 = buildProvenSeeded(seed_a); + let list_a2 = buildProvenSeeded(seed_a); + let list_b = buildProvenSeeded(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 = randomBytes(16); + let seed_b = randomBytes(16); + console.log( + `seeded test — seed_a: [${Array.from(seed_a).join(", ")}], seed_b: [${Array.from(seed_b).join(", ")}]`, + ); + + const buildSeeded = (seed) => { + let builder = CompactCiphertextList.builder(publicKey); + builder.push_u32(17); + builder.push_i32(-1); + builder.push_boolean(false); + return builder.build_packed_seeded(seed); + }; + + let list_a1 = buildSeeded(seed_a); + let list_a2 = buildSeeded(seed_a); + let list_b = buildSeeded(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..1a80ed6ec0 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 @@ -120,6 +120,12 @@ impl NoiseRandomGenerator { } } + #[cfg(feature = "zk-pok")] + pub(crate) fn fill_bytes(&mut self, dest: &mut [u8]) { + use rand_core::RngCore; + 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..036de589bf 100644 --- a/tfhe/src/high_level_api/compact_list.rs +++ b/tfhe/src/high_level_api/compact_list.rs @@ -166,6 +166,12 @@ impl Clone for InnerCompactCiphertextList { } } +impl PartialEq for InnerCompactCiphertextList { + fn eq(&self, other: &Self) -> bool { + self.on_cpu() == other.on_cpu() + } +} + impl serde::Serialize for InnerCompactCiphertextList { fn serialize(&self, serializer: S) -> Result where @@ -273,7 +279,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 +504,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")] + _ => self.on_cpu() == other.on_cpu(), + } + } + } + + #[derive(Clone, PartialEq, Serialize, Deserialize, Versionize)] #[versionize(ProvenCompactCiphertextListVersions)] pub struct ProvenCompactCiphertextList { pub(crate) inner: InnerProvenCompactCiphertextList, @@ -1065,6 +1083,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 +1115,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")] @@ -1116,6 +1173,7 @@ mod tests { use crate::prelude::*; use crate::shortint::parameters::*; use crate::{set_server_key, FheBool, FheInt64, FheUint16, FheUint2, FheUint32}; + use rand::{thread_rng, Rng}; #[cfg(feature = "gpu")] use crate::CompressedServerKey; @@ -1173,6 +1231,55 @@ 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 mut rng = thread_rng(); + let seed_a: [u8; 16] = rng.gen(); + let seed_b: [u8; 16] = rng.gen(); + println!("seeded test — seed_a: {seed_a:?}, seed_b: {seed_b:?}"); + + 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 +1712,63 @@ 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 mut rng = thread_rng(); + let seed_a: [u8; 16] = rng.gen(); + let seed_b: [u8; 16] = rng.gen(); + println!("proven seeded test — seed_a: {seed_a:?}, seed_b: {seed_b:?}"); + + 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..307774de22 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, 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,19 @@ 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"); + Ok(NoiseRandomGenerator::new_from_seed(xof_seed)) +} + #[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_0_11/src/lib.rs b/utils/tfhe-backward-compat-data/crates/generate_0_11/src/lib.rs index ce8dc66511..a5bc46c7ba 100644 --- a/utils/tfhe-backward-compat-data/crates/generate_0_11/src/lib.rs +++ b/utils/tfhe-backward-compat-data/crates/generate_0_11/src/lib.rs @@ -44,7 +44,7 @@ const HL_PROVEN_COMPACTLIST_TEST_ZKV2: HlHeterogeneousCiphertextListTest = HlHeterogeneousCiphertextListTest { test_filename: Cow::Borrowed("hl_proven_heterogeneous_list_zkv2"), key_filename: HL_CLIENTKEY_TEST.test_filename, - clear_values: Cow::Borrowed(&[17u8 as u64, -12i8 as u64, false as u64, true as u64]), + clear_values: Cow::Borrowed(&[17u8 as i64, -12i8 as i64, false as i64, true as i64]), data_kinds: Cow::Borrowed(&[ DataKind::Unsigned, DataKind::Signed, diff --git a/utils/tfhe-backward-compat-data/crates/generate_0_8/src/lib.rs b/utils/tfhe-backward-compat-data/crates/generate_0_8/src/lib.rs index 4d001915b8..2cc23ca652 100644 --- a/utils/tfhe-backward-compat-data/crates/generate_0_8/src/lib.rs +++ b/utils/tfhe-backward-compat-data/crates/generate_0_8/src/lib.rs @@ -170,7 +170,7 @@ const HL_CLIENT_KEY_BATCH_2_FILENAME: &str = "batch_2_client_key"; const HL_COMPACTLIST_TEST: HlHeterogeneousCiphertextListTest = HlHeterogeneousCiphertextListTest { test_filename: Cow::Borrowed("hl_heterogeneous_list"), key_filename: Cow::Borrowed(HL_CLIENT_KEY_BATCH_2_FILENAME), - clear_values: Cow::Borrowed(&[17u8 as u64, -12i8 as u64, false as u64, true as u64]), + clear_values: Cow::Borrowed(&[17u8 as i64, -12i8 as i64, false as i64, true as i64]), data_kinds: Cow::Borrowed(&[ DataKind::Unsigned, DataKind::Signed, @@ -195,7 +195,7 @@ const HL_COMPRESSED_LIST_TEST: HlHeterogeneousCiphertextListTest = HlHeterogeneousCiphertextListTest { test_filename: Cow::Borrowed("hl_compressed_heterogeneous_list"), key_filename: Cow::Borrowed(HL_CLIENT_KEY_BATCH_2_FILENAME), - clear_values: Cow::Borrowed(&[17u8 as u64, -12i8 as u64, false as u64, true as u64]), + clear_values: Cow::Borrowed(&[17u8 as i64, -12i8 as i64, false as i64, true as i64]), data_kinds: Cow::Borrowed(&[ DataKind::Unsigned, DataKind::Signed, @@ -210,7 +210,7 @@ const HL_PROVEN_COMPACTLIST_TEST: HlHeterogeneousCiphertextListTest = HlHeterogeneousCiphertextListTest { test_filename: Cow::Borrowed("hl_proven_heterogeneous_list"), key_filename: Cow::Borrowed(HL_CLIENT_KEY_BATCH_2_FILENAME), - clear_values: Cow::Borrowed(&[17u8 as u64, -12i8 as u64, false as u64, true as u64]), + clear_values: Cow::Borrowed(&[17u8 as i64, -12i8 as i64, false as i64, true as i64]), data_kinds: Cow::Borrowed(&[ DataKind::Unsigned, DataKind::Signed, diff --git a/utils/tfhe-backward-compat-data/crates/generate_1_3/src/lib.rs b/utils/tfhe-backward-compat-data/crates/generate_1_3/src/lib.rs index 18692952d6..5fe7aa404d 100644 --- a/utils/tfhe-backward-compat-data/crates/generate_1_3/src/lib.rs +++ b/utils/tfhe-backward-compat-data/crates/generate_1_3/src/lib.rs @@ -42,7 +42,7 @@ const HL_PROVEN_COMPACTLIST_TEST_ZKV2_FASTHASH: HlHeterogeneousCiphertextListTes HlHeterogeneousCiphertextListTest { test_filename: Cow::Borrowed("hl_proven_heterogeneous_list_zkv2_fasthash"), key_filename: Cow::Borrowed("client_key"), - clear_values: Cow::Borrowed(&[17u8 as u64, -12i8 as u64, false as u64, true as u64]), + clear_values: Cow::Borrowed(&[17u8 as i64, -12i8 as i64, false as i64, true as i64]), data_kinds: Cow::Borrowed(&[ DataKind::Unsigned, DataKind::Signed, @@ -74,11 +74,11 @@ const HL_COMPRESSED_SQUASHED_NOISE_CIPHERTEXT_LIST: HlCompressedSquashedNoiseCip test_filename: Cow::Borrowed("hl_compressed_squashed_noise_ciphertext_list"), key_filename: Cow::Borrowed("client_key_with_noise_squashing"), clear_values: Cow::Borrowed(&[ - 54679568u32 as u64, - -12396372i32 as u64, - 12396372i32 as u64, - false as u64, - true as u64, + 54679568u32 as i64, + -12396372i32 as i64, + 12396372i32 as i64, + false as i64, + true as i64, ]), data_kinds: Cow::Borrowed(&[ DataKind::Unsigned, diff --git a/utils/tfhe-backward-compat-data/crates/generate_1_5/src/lib.rs b/utils/tfhe-backward-compat-data/crates/generate_1_5/src/lib.rs index d535f06375..8dac4c6beb 100644 --- a/utils/tfhe-backward-compat-data/crates/generate_1_5/src/lib.rs +++ b/utils/tfhe-backward-compat-data/crates/generate_1_5/src/lib.rs @@ -41,7 +41,7 @@ const HL_PROVEN_COMPACTLIST_TEST_ZKV2: HlHeterogeneousCiphertextListTest = HlHeterogeneousCiphertextListTest { test_filename: Cow::Borrowed("hl_proven_list_zkv2_1_5"), key_filename: Cow::Borrowed("client_key_for_zk"), - clear_values: Cow::Borrowed(&[17u8 as u64]), + clear_values: Cow::Borrowed(&[17u8 as i64]), data_kinds: Cow::Borrowed(&[DataKind::Unsigned]), compressed: false, proof_info: Some(PkeZkProofAuxiliaryInfo { 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..d1940c909e 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,32 @@ 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(); + +const HL_SEEDED_COMPACT_LIST_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"), + proof_info: None, + clear_values: Cow::Borrowed(&[17, -1, 0]), + data_kinds: Cow::Borrowed(&[DataKind::Unsigned, DataKind::Signed, DataKind::Bool]), + seed: Cow::Borrowed(SEEDED_COMPACT_LIST_SEED), + }; + +const HL_SEEDED_PROVEN_COMPACT_LIST_TEST: HlSeededCompactCiphertextListTest = + HlSeededCompactCiphertextListTest { + 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: Some(ZkProofAuxiliaryInfo { + params_filename: Cow::Borrowed("seeded_proven_zk_pke_crs"), + metadata: Cow::Borrowed("backward_compat_seeded"), + }), + clear_values: Cow::Borrowed(&[17, -1, 0]), + data_kinds: Cow::Borrowed(&[DataKind::Unsigned, DataKind::Signed, DataKind::Bool]), + seed: Cow::Borrowed(SEEDED_COMPACT_LIST_SEED), + }; pub struct V1_6; @@ -91,9 +121,132 @@ impl TfhersVersion for V1_6 { ); } + // Generate seeded compact ciphertext list (no proof) + { + let config = tfhe::ConfigBuilder::with_custom_parameters( + INSECURE_SMALL_TEST_PARAMS_MS_MEAN_COMPENSATION_LWE_DIM_64.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, + &HL_SEEDED_COMPACT_LIST_TEST.key_filename, + ); + store_versioned_auxiliary( + &compact_pub_key, + &dir, + &HL_SEEDED_COMPACT_LIST_TEST.public_key_filename, + ); + + let mut builder = CompactCiphertextList::builder(&compact_pub_key); + for (value, kind) in HL_SEEDED_COMPACT_LIST_TEST + .clear_values + .iter() + .zip(HL_SEEDED_COMPACT_LIST_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(&HL_SEEDED_COMPACT_LIST_TEST.seed) + .unwrap(); + store_versioned_test(&list, &dir, &HL_SEEDED_COMPACT_LIST_TEST.test_filename()); + } + + // Generate seeded proven compact ciphertext list + { + let config = tfhe::ConfigBuilder::with_custom_parameters( + INSECURE_SMALL_TEST_PARAMS_MS_MEAN_COMPENSATION_LWE_DIM_64.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, + &HL_SEEDED_PROVEN_COMPACT_LIST_TEST.key_filename, + ); + store_versioned_auxiliary( + &compact_pub_key, + &dir, + &HL_SEEDED_PROVEN_COMPACT_LIST_TEST.public_key_filename, + ); + store_versioned_auxiliary( + &crs, + &dir, + &HL_SEEDED_PROVEN_COMPACT_LIST_TEST + .proof_info + .unwrap() + .params_filename, + ); + + let mut proven_builder = ProvenCompactCiphertextList::builder(&compact_pub_key); + for (value, kind) in HL_SEEDED_PROVEN_COMPACT_LIST_TEST + .clear_values + .iter() + .zip(HL_SEEDED_PROVEN_COMPACT_LIST_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, + HL_SEEDED_PROVEN_COMPACT_LIST_TEST + .proof_info + .unwrap() + .metadata + .as_bytes(), + ZkComputeLoad::Proof, + &HL_SEEDED_PROVEN_COMPACT_LIST_TEST.seed, + ) + .unwrap(); + + store_versioned_test( + &proven_list, + &dir, + &HL_SEEDED_PROVEN_COMPACT_LIST_TEST.test_filename(), + ); + } + vec![ TestMetadata::HlCompressedXofKeySet(HL_COMPRESSED_XOF_KEY_SET_TEST), TestMetadata::HlServerKey(HL_SERVER_KEY_TEST), + TestMetadata::HlSeededCompactCiphertextList(HL_SEEDED_COMPACT_LIST_TEST), + TestMetadata::HlSeededCompactCiphertextList(HL_SEEDED_PROVEN_COMPACT_LIST_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..d3015f30e4 --- /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:6ef3d23dfc45a65b215215aeca0e1dfcff733cb11fa0cc56fc93cafa60297332 +size 16620 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..c13f026c15 --- /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:55335a4e93bd75a5ead5bc64f383421dbdb3cdeaa864aab01c8fcf937d43c3a1 +size 18797 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..c44b01b415 --- /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:97399fff3b12a4d873817abe3453862ba654df8363107e4f537516acd49226ba +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..cc8b135c51 --- /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:2a50f3c66ebd9abd8aa81f475d982fd060f8163da1fed654d859f1a908f4ce1a +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..bfa4605235 --- /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:85fd7b91b4329d0b7457a7e6cff2b883d37e93bd3a986fcc26fe1463e2624707 +size 2954 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..9b7929c96a --- /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:1e2af27ee443956de89a0793e996db46539057510a4861ecccae35bf140186c2 +size 37324 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..2895dba6d3 --- /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:5d74f0e08e3dfaa9b5428e74e48ac37faec800b3b899e52b4296f435998ecf9a +size 3338 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..b53d1fed2d --- /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:2f2005c4af8a277e8252a834084a7aaafa7a5d450423d14967142773c5c4c592 +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..25d88a9a95 100644 --- a/utils/tfhe-backward-compat-data/data/high_level_api.ron +++ b/utils/tfhe-backward-compat-data/data/high_level_api.ron @@ -198,7 +198,7 @@ proof_info: None, clear_values: [ 17, - 18446744073709551604, + -12, 0, 1, ], @@ -220,7 +220,7 @@ proof_info: None, clear_values: [ 17, - 18446744073709551604, + -12, 0, 1, ], @@ -242,7 +242,7 @@ proof_info: None, clear_values: [ 17, - 18446744073709551604, + -12, 0, 1, ], @@ -268,7 +268,7 @@ )), clear_values: [ 17, - 18446744073709551604, + -12, 0, 1, ], @@ -398,7 +398,7 @@ )), clear_values: [ 17, - 18446744073709551604, + -12, 0, 1, ], @@ -571,7 +571,7 @@ )), clear_values: [ 17, - 18446744073709551604, + -12, 0, 1, ], @@ -630,7 +630,7 @@ key_filename: "client_key_with_noise_squashing", clear_values: [ 54679568, - 18446744073697155244, + -12396372, 12396372, 0, 1, @@ -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", + proof_info: None, + clear_values: [ + 17, + -1, + 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: HlSeededCompactCiphertextList(( + test_filename: "hl_seeded_proven_compact_list", + key_filename: "seeded_proven_client_key", + public_key_filename: "seeded_proven_compact_public_key", + proof_info: Some(( + params_filename: "seeded_proven_zk_pke_crs", + metadata: "backward_compat_seeded", + )), + clear_values: [ + 17, + -1, + 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..ee2fe358e3 100644 --- a/utils/tfhe-backward-compat-data/src/lib.rs +++ b/utils/tfhe-backward-compat-data/src/lib.rs @@ -533,13 +533,21 @@ pub struct PkeZkProofAuxiliaryInfo { pub metadata: Cow<'static, str>, } +/// Same as [PkeZkProofAuxiliaryInfo] without the Pke in case the Pke +/// is already part of the non auxiliary data +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ZkProofAuxiliaryInfo { + pub params_filename: Cow<'static, str>, + pub metadata: Cow<'static, str>, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct HlHeterogeneousCiphertextListTest { pub test_filename: Cow<'static, str>, pub key_filename: Cow<'static, str>, pub compressed: bool, pub proof_info: Option, - pub clear_values: Cow<'static, [u64]>, + pub clear_values: Cow<'static, [i64]>, pub data_kinds: Cow<'static, [DataKind]>, } @@ -557,11 +565,40 @@ 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 proof_info: Option, + pub clear_values: Cow<'static, [i64]>, + pub data_kinds: Cow<'static, [DataKind]>, + pub seed: Cow<'static, [u8]>, +} + +impl TestType for HlSeededCompactCiphertextListTest { + fn module(&self) -> String { + HL_MODULE_NAME.to_string() + } + + fn target_type(&self) -> String { + if self.proof_info.is_none() { + "CompactCiphertextList".to_string() + } else { + "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>, pub key_filename: Cow<'static, str>, - pub clear_values: Cow<'static, [u64]>, + pub clear_values: Cow<'static, [i64]>, pub data_kinds: Cow<'static, [DataKind]>, } @@ -731,6 +768,7 @@ pub enum TestMetadata { HlCompressedSquashedNoiseCiphertextList(HlCompressedSquashedNoiseCiphertextListTest), HlCompressedKVStoreTest(HlCompressedKVStoreTest), HlCompressedXofKeySet(HlCompressedXofKeySetTest), + HlSeededCompactCiphertextList(HlSeededCompactCiphertextListTest), } #[derive(Serialize, Deserialize, Clone, Debug)]