tfhe/shortint/ciphertext/
zk.rs

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    /// Construct the CRS that corresponds to the given parameters
24    ///
25    /// max_num_message is how many message a single proof can prove.
26    /// The version of the zk scheme is based on the
27    /// [`CompactPkeZkScheme`](crate::zk::CompactPkeZkScheme) value in the params.
28    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        // Our plaintext modulus does not take into account the bit of padding
44        plaintext_modulus *= 2;
45
46        // 1 padding bit for the PBS
47        // Note that if we want to we can prove carry bits are 0 should we need it
48        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/// A List of CompactCiphertext with their zero-knowledge proofs
77///
78/// The proofs can only be generated during the encryption with a [CompactPublicKey]
79#[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        // We can call the function as we have verified the proofs
116        self.expand_without_verification(casting_mode)
117    }
118
119    #[doc(hidden)]
120    /// This function allows to expand a ciphertext without verifying the associated proof.
121    ///
122    /// If you are here you were probably looking for it: use at your own risks.
123    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                    // For how many ciphertexts we have functions
134                    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    /// Forbid proofs coming with the provided [`ZkComputeLoad`]
234    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    /// Forbid proofs coming with the provided [`ZkPkeV2HashMode`]. This has no effect on PkeV1
244    /// proofs
245    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(&params) {
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        // No packing
357        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        // Check that we can reject specific proof types at the conformance level
496        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        // By default, zk proofs use compact hash mode.
502        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}