1use super::Degree;
2use crate::conformance::{ListSizeConstraint, ParameterSetConformant};
3use crate::core_crypto::algorithms::verify_lwe_compact_ciphertext_list;
4use crate::core_crypto::prelude::{LweCiphertextCount, LweCiphertextListConformanceParams};
5use crate::shortint::backward_compatibility::ciphertext::ProvenCompactCiphertextListVersions;
6use crate::shortint::ciphertext::CompactCiphertextList;
7use crate::shortint::parameters::{
8 CarryModulus, CiphertextListConformanceParams, CiphertextModulus,
9 CompactCiphertextListExpansionKind, CompactPublicKeyEncryptionParameters, LweDimension,
10 MessageModulus, ShortintCompactCiphertextListCastingMode, SupportedCompactPkeZkScheme,
11};
12use crate::shortint::{Ciphertext, CompactPublicKey};
13use crate::zk::{
14 CompactPkeCrs, CompactPkeProof, CompactPkeProofConformanceParams, ZkComputeLoad,
15 ZkMSBZeroPaddingBitCount, ZkPkeV2HashMode, ZkVerificationOutcome,
16};
17
18use rayon::prelude::*;
19use serde::{Deserialize, Serialize};
20use tfhe_versionable::Versionize;
21
22impl CompactPkeCrs {
23 pub fn from_shortint_params<P, E>(
29 params: P,
30 max_num_message: LweCiphertextCount,
31 ) -> crate::Result<Self>
32 where
33 P: TryInto<CompactPublicKeyEncryptionParameters, Error = E>,
34 crate::Error: From<E>,
35 {
36 let params: CompactPublicKeyEncryptionParameters = params.try_into()?;
37 let (size, noise_distribution) = (
38 params.encryption_lwe_dimension,
39 params.encryption_noise_distribution,
40 );
41
42 let mut plaintext_modulus = params.message_modulus.0 * params.carry_modulus.0;
43 plaintext_modulus *= 2;
45
46 crate::shortint::engine::ShortintEngine::with_thread_local_mut(|engine| {
49 match params.zk_scheme {
50 SupportedCompactPkeZkScheme::V1 => Self::new_legacy_v1(
51 size,
52 max_num_message,
53 noise_distribution,
54 params.ciphertext_modulus,
55 plaintext_modulus,
56 ZkMSBZeroPaddingBitCount(1),
57 &mut engine.random_generator,
58 ),
59 SupportedCompactPkeZkScheme::V2 => Self::new(
60 size,
61 max_num_message,
62 noise_distribution,
63 params.ciphertext_modulus,
64 plaintext_modulus,
65 ZkMSBZeroPaddingBitCount(1),
66 &mut engine.random_generator,
67 ),
68 SupportedCompactPkeZkScheme::ZkNotSupported => {
69 Err("Zk proof of encryption is not supported by the provided parameters".into())
70 }
71 }
72 })
73 }
74}
75
76#[derive(Clone, Serialize, Deserialize, Versionize)]
80#[versionize(ProvenCompactCiphertextListVersions)]
81pub struct ProvenCompactCiphertextList {
82 pub(crate) proved_lists: Vec<(CompactCiphertextList, CompactPkeProof)>,
83}
84
85impl ProvenCompactCiphertextList {
86 pub fn ciphertext_count(&self) -> usize {
87 self.proved_lists
88 .iter()
89 .map(|(list, _)| list.ct_list.lwe_ciphertext_count().0)
90 .sum()
91 }
92
93 pub fn verify_and_expand(
94 &self,
95 crs: &CompactPkeCrs,
96 public_key: &CompactPublicKey,
97 metadata: &[u8],
98 casting_mode: ShortintCompactCiphertextListCastingMode<'_>,
99 ) -> crate::Result<Vec<Ciphertext>> {
100 let not_all_valid = self.proved_lists.par_iter().any(|(ct_list, proof)| {
101 verify_lwe_compact_ciphertext_list(
102 &ct_list.ct_list,
103 &public_key.key,
104 proof,
105 crs,
106 metadata,
107 )
108 .is_invalid()
109 });
110
111 if not_all_valid {
112 return Err(crate::ErrorKind::InvalidZkProof.into());
113 }
114
115 self.expand_without_verification(casting_mode)
117 }
118
119 #[doc(hidden)]
120 pub fn expand_without_verification(
124 &self,
125 casting_mode: ShortintCompactCiphertextListCastingMode<'_>,
126 ) -> crate::Result<Vec<Ciphertext>> {
127 let per_list_casting_mode: Vec<_> = match casting_mode {
128 ShortintCompactCiphertextListCastingMode::CastIfNecessary {
129 casting_key,
130 functions,
131 } => match functions {
132 Some(functions) => {
133 let functions_sets_count = functions.len();
135 let total_ciphertext_count: usize = self
136 .proved_lists
137 .iter()
138 .map(|list| list.0.ct_list.lwe_ciphertext_count().0)
139 .sum();
140
141 if functions_sets_count != total_ciphertext_count {
142 return Err(crate::Error::new(format!(
143 "Cannot expand a CompactCiphertextList: got {functions_sets_count} \
144 sets of functions for casting, expected {total_ciphertext_count}"
145 )));
146 }
147
148 let mut modes = vec![];
149 let mut functions_used_so_far = 0;
150 for list in self.proved_lists.iter() {
151 let blocks_in_list = list.0.ct_list.lwe_ciphertext_count().0;
152
153 let functions_to_use = &functions
154 [functions_used_so_far..functions_used_so_far + blocks_in_list];
155
156 modes.push(ShortintCompactCiphertextListCastingMode::CastIfNecessary {
157 casting_key,
158 functions: Some(functions_to_use),
159 });
160
161 functions_used_so_far += blocks_in_list;
162 }
163 modes
164 }
165 None => vec![
166 ShortintCompactCiphertextListCastingMode::NoCasting;
167 self.proved_lists.len()
168 ],
169 },
170 ShortintCompactCiphertextListCastingMode::NoCasting => {
171 vec![ShortintCompactCiphertextListCastingMode::NoCasting; self.proved_lists.len()]
172 }
173 };
174 let expanded = self
175 .proved_lists
176 .iter()
177 .zip(per_list_casting_mode.into_iter())
178 .map(|((ct_list, _proof), casting_mode)| ct_list.expand(casting_mode))
179 .collect::<Result<Vec<Vec<_>>, _>>()?
180 .into_iter()
181 .flatten()
182 .collect();
183
184 Ok(expanded)
185 }
186
187 pub fn verify(
188 &self,
189 crs: &CompactPkeCrs,
190 public_key: &CompactPublicKey,
191 metadata: &[u8],
192 ) -> ZkVerificationOutcome {
193 let all_valid = self.proved_lists.par_iter().all(|(ct_list, proof)| {
194 verify_lwe_compact_ciphertext_list(
195 &ct_list.ct_list,
196 &public_key.key,
197 proof,
198 crs,
199 metadata,
200 )
201 .is_valid()
202 });
203
204 if all_valid {
205 ZkVerificationOutcome::Valid
206 } else {
207 ZkVerificationOutcome::Invalid
208 }
209 }
210
211 pub fn proof_size(&self) -> usize {
212 self.proved_lists.len() * core::mem::size_of::<CompactPkeProof>()
213 }
214
215 pub fn message_modulus(&self) -> MessageModulus {
216 self.proved_lists[0].0.message_modulus
217 }
218}
219
220#[derive(Copy, Clone)]
221pub struct ProvenCompactCiphertextListConformanceParams {
222 pub encryption_lwe_dimension: LweDimension,
223 pub message_modulus: MessageModulus,
224 pub carry_modulus: CarryModulus,
225 pub ciphertext_modulus: CiphertextModulus,
226 pub expansion_kind: CompactCiphertextListExpansionKind,
227 pub max_lwe_count_per_compact_list: usize,
228 pub total_expected_lwe_count: usize,
229 pub zk_conformance_params: CompactPkeProofConformanceParams,
230}
231
232impl ProvenCompactCiphertextListConformanceParams {
233 pub fn forbid_compute_load(self, forbidden_compute_load: ZkComputeLoad) -> Self {
235 Self {
236 zk_conformance_params: self
237 .zk_conformance_params
238 .forbid_compute_load(forbidden_compute_load),
239 ..self
240 }
241 }
242
243 pub fn forbid_hash_mode(self, forbidden_hash_mode: ZkPkeV2HashMode) -> Self {
246 Self {
247 zk_conformance_params: self
248 .zk_conformance_params
249 .forbid_hash_mode(forbidden_hash_mode),
250 ..self
251 }
252 }
253}
254
255impl ParameterSetConformant for ProvenCompactCiphertextList {
256 type ParameterSet = ProvenCompactCiphertextListConformanceParams;
257
258 fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
259 let Self { proved_lists } = self;
260
261 let ProvenCompactCiphertextListConformanceParams {
262 max_lwe_count_per_compact_list,
263 total_expected_lwe_count,
264 expansion_kind,
265 encryption_lwe_dimension,
266 message_modulus,
267 carry_modulus,
268 ciphertext_modulus,
269 zk_conformance_params,
270 } = parameter_set;
271
272 let max_elements_per_compact_list = *max_lwe_count_per_compact_list;
273
274 let mut remaining_len = *total_expected_lwe_count;
275
276 for (compact_ct_list, proof) in proved_lists {
277 if !proof.is_conformant(zk_conformance_params) {
278 return false;
279 }
280
281 if remaining_len == 0 {
282 return false;
283 }
284
285 let expected_len;
286
287 if remaining_len > max_elements_per_compact_list {
288 remaining_len -= max_elements_per_compact_list;
289
290 expected_len = max_elements_per_compact_list;
291 } else {
292 expected_len = remaining_len;
293 remaining_len = 0;
294 }
295
296 let params = CiphertextListConformanceParams {
297 ct_list_params: LweCiphertextListConformanceParams {
298 lwe_dim: *encryption_lwe_dimension,
299 lwe_ciphertext_count_constraint: ListSizeConstraint::exact_size(expected_len),
300 ct_modulus: *ciphertext_modulus,
301 },
302 message_modulus: *message_modulus,
303 carry_modulus: *carry_modulus,
304 degree: Degree::new(message_modulus.0 * message_modulus.0 - 1),
305 expansion_kind: *expansion_kind,
306 };
307
308 if !compact_ct_list.is_conformant(¶ms) {
309 return false;
310 }
311 }
312
313 if remaining_len != 0 {
314 return false;
315 }
316
317 true
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use crate::conformance::ParameterSetConformant;
324 use crate::core_crypto::prelude::LweCiphertextCount;
325 use crate::shortint::ciphertext::ProvenCompactCiphertextListConformanceParams;
326 use crate::shortint::parameters::*;
327 use crate::shortint::{
328 ClientKey, CompactPrivateKey, CompactPublicKey, KeySwitchingKey, ServerKey,
329 };
330 use crate::zk::{
331 CompactPkeCrs, CompactPkeProofConformanceParams, ZkComputeLoad, ZkPkeV2HashMode,
332 };
333 use rand::random;
334
335 #[test]
336 fn test_zk_ciphertext_encryption_ci_run_filter() {
337 let params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
338 let pke_params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
339 let ksk_params = PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
340
341 let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(4)).unwrap();
342 let priv_key = CompactPrivateKey::new(pke_params);
343 let pub_key = CompactPublicKey::new(&priv_key);
344 let ck = ClientKey::new(params);
345 let sk = ServerKey::new(&ck);
346 let ksk = KeySwitchingKey::new((&priv_key, None), (&ck, &sk), ksk_params);
347
348 let id = |x: u64| x;
349 let dyn_id: &(dyn Fn(u64) -> u64 + Sync) = &id;
350
351 let functions = vec![Some(vec![dyn_id; 1]); 1];
352
353 let metadata = [b's', b'h', b'o', b'r', b't', b'i', b'n', b't'];
354
355 let msg = random::<u64>() % pke_params.message_modulus.0;
356 let encryption_modulus = pke_params.message_modulus.0;
358
359 let proven_ct = pub_key
360 .encrypt_and_prove(
361 msg,
362 &crs,
363 &metadata,
364 ZkComputeLoad::Proof,
365 encryption_modulus,
366 )
367 .unwrap();
368
369 {
370 let unproven_ct = proven_ct.expand_without_verification(
371 ShortintCompactCiphertextListCastingMode::CastIfNecessary {
372 casting_key: ksk.as_view(),
373 functions: Some(functions.as_slice()),
374 },
375 );
376 let unproven_ct = unproven_ct.unwrap();
377
378 let decrypted = ck.decrypt(&unproven_ct[0]);
379 assert_eq!(msg, decrypted);
380 }
381
382 let proven_ct = proven_ct.verify_and_expand(
383 &crs,
384 &pub_key,
385 &metadata,
386 ShortintCompactCiphertextListCastingMode::CastIfNecessary {
387 casting_key: ksk.as_view(),
388 functions: Some(functions.as_slice()),
389 },
390 );
391 let proven_ct = proven_ct.unwrap();
392
393 let decrypted = ck.decrypt(&proven_ct[0]);
394 assert_eq!(msg, decrypted);
395 }
396
397 #[test]
398 fn test_zk_compact_ciphertext_list_encryption_ci_run_filter() {
399 let params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
400 let pke_params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
401 let ksk_params = PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
402
403 let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(4)).unwrap();
404 let priv_key = CompactPrivateKey::new(pke_params);
405 let pub_key = CompactPublicKey::new(&priv_key);
406 let ck = ClientKey::new(params);
407 let sk = ServerKey::new(&ck);
408 let ksk = KeySwitchingKey::new((&priv_key, None), (&ck, &sk), ksk_params);
409
410 let id = |x: u64| x;
411 let dyn_id: &(dyn Fn(u64) -> u64 + Sync) = &id;
412
413 let functions = vec![Some(vec![dyn_id; 1]); 512];
414
415 let metadata = [b's', b'h', b'o', b'r', b't', b'i', b'n', b't'];
416
417 let msgs = (0..512)
418 .map(|_| random::<u64>() % params.message_modulus.0)
419 .collect::<Vec<_>>();
420
421 let proven_ct = pub_key
422 .encrypt_and_prove_slice(
423 &msgs,
424 &crs,
425 &metadata,
426 ZkComputeLoad::Proof,
427 params.message_modulus.0,
428 )
429 .unwrap();
430 assert!(proven_ct.verify(&crs, &pub_key, &metadata).is_valid());
431
432 let expanded = proven_ct
433 .verify_and_expand(
434 &crs,
435 &pub_key,
436 &metadata,
437 ShortintCompactCiphertextListCastingMode::CastIfNecessary {
438 casting_key: ksk.as_view(),
439 functions: Some(functions.as_slice()),
440 },
441 )
442 .unwrap();
443 let decrypted = expanded
444 .iter()
445 .map(|ciphertext| ck.decrypt(ciphertext))
446 .collect::<Vec<_>>();
447 assert_eq!(msgs, decrypted);
448 }
449
450 #[test]
451 fn test_zk_proof_conformance_ci_run_filter() {
452 let params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
453 let pke_params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
454
455 let max_lwe_count_per_compact_list = LweCiphertextCount(320);
456 let total_lwe_count = 512;
457
458 let crs = CompactPkeCrs::from_shortint_params(pke_params, max_lwe_count_per_compact_list)
459 .unwrap();
460 let priv_key = CompactPrivateKey::new(pke_params);
461 let pub_key = CompactPublicKey::new(&priv_key);
462
463 let metadata = [b's', b'h', b'o', b'r', b't', b'i', b'n', b't'];
464
465 let msgs = (0..total_lwe_count)
466 .map(|_| random::<u64>() % params.message_modulus.0)
467 .collect::<Vec<_>>();
468
469 let proven_ct = pub_key
470 .encrypt_and_prove_slice(
471 &msgs,
472 &crs,
473 &metadata,
474 ZkComputeLoad::Verify,
475 params.message_modulus.0 * params.carry_modulus.0,
476 )
477 .unwrap();
478 assert!(proven_ct.verify(&crs, &pub_key, &metadata).is_valid());
479
480 let zk_conformance_params = CompactPkeProofConformanceParams::new(crs.scheme_version());
481
482 let conformance_params = ProvenCompactCiphertextListConformanceParams {
483 encryption_lwe_dimension: pke_params.encryption_lwe_dimension,
484 message_modulus: pke_params.message_modulus,
485 carry_modulus: pke_params.carry_modulus,
486 ciphertext_modulus: pke_params.ciphertext_modulus,
487 expansion_kind: pke_params.expansion_kind,
488 max_lwe_count_per_compact_list: max_lwe_count_per_compact_list.0,
489 total_expected_lwe_count: total_lwe_count,
490 zk_conformance_params,
491 };
492
493 assert!(proven_ct.is_conformant(&conformance_params));
494
495 let no_cl_verif_conformance_params =
497 conformance_params.forbid_compute_load(ZkComputeLoad::Verify);
498
499 assert!(!proven_ct.is_conformant(&no_cl_verif_conformance_params));
500
501 let no_compact_hash_conformance_params =
503 conformance_params.forbid_hash_mode(ZkPkeV2HashMode::Compact);
504
505 assert!(!proven_ct.is_conformant(&no_compact_hash_conformance_params));
506 }
507}