1pub mod backward_compatibility;
2
3use crate::conformance::ParameterSetConformant;
4use crate::core_crypto::commons::math::random::{
5 BoundedDistribution, ByteRandomGenerator, RandomGenerator,
6};
7use crate::core_crypto::prelude::*;
8use crate::named::Named;
9#[cfg(feature = "shortint")]
10use crate::shortint::parameters::CompactPublicKeyEncryptionParameters;
11use backward_compatibility::*;
12use rand_core::RngCore;
13use serde::{Deserialize, Serialize};
14use std::cmp::Ordering;
15use std::collections::Bound;
16use std::fmt::Debug;
17use tfhe_versionable::Versionize;
18
19use tfhe_zk_pok::proofs::pke::{
20 commit as commit_v1, crs_gen as crs_gen_v1, prove as prove_v1, verify as verify_v1,
21 Proof as ProofV1, PublicCommit as PublicCommitV1,
22};
23use tfhe_zk_pok::proofs::pke_v2::{
24 commit as commit_v2, crs_gen as crs_gen_v2, prove as prove_v2, verify as verify_v2,
25 Proof as ProofV2, PublicCommit as PublicCommitV2,
26};
27
28pub use tfhe_zk_pok::curve_api::Compressible;
29pub use tfhe_zk_pok::proofs::ComputeLoad as ZkComputeLoad;
30type Curve = tfhe_zk_pok::curve_api::Bls12_446;
31
32#[derive(Clone, Debug, Serialize, Deserialize, Versionize)]
33#[versionize(CompactPkeProofVersions)]
34#[allow(clippy::large_enum_variant)]
35pub enum CompactPkeProof {
36 PkeV1(ProofV1<Curve>),
37 PkeV2(ProofV2<Curve>),
38}
39
40impl Named for CompactPkeProof {
41 const NAME: &'static str = "zk::CompactPkeProof";
42}
43
44impl ParameterSetConformant for CompactPkeProof {
45 type ParameterSet = CompactPkeZkScheme;
46
47 fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
48 match (self, parameter_set) {
49 (Self::PkeV1(proof), CompactPkeZkScheme::V1) => proof.is_usable(),
50 (Self::PkeV2(proof), CompactPkeZkScheme::V2) => proof.is_usable(),
51 (Self::PkeV1(_), CompactPkeZkScheme::V2) | (Self::PkeV2(_), CompactPkeZkScheme::V1) => {
52 false
53 }
54 }
55 }
56}
57
58pub type ZkCompactPkeV1PublicParams = tfhe_zk_pok::proofs::pke::PublicParams<Curve>;
59pub type ZkCompactPkeV2PublicParams = tfhe_zk_pok::proofs::pke_v2::PublicParams<Curve>;
60
61pub type SerializableCompactPkePublicParams =
64 tfhe_zk_pok::serialization::SerializablePKEv1PublicParams;
65
66impl Named for ZkCompactPkeV1PublicParams {
67 const NAME: &'static str = "zk::CompactPkePublicParams";
68}
69
70pub struct CompactPkeCrsConformanceParams {
71 lwe_dim: LweDimension,
72 max_num_message: LweCiphertextCount,
73 noise_bound: u64,
74 ciphertext_modulus: u64,
75 plaintext_modulus: u64,
76 msbs_zero_padding_bit_count: ZkMSBZeroPaddingBitCount,
77}
78
79#[cfg(feature = "shortint")]
80impl CompactPkeCrsConformanceParams {
81 pub fn new<E, P: TryInto<CompactPublicKeyEncryptionParameters, Error = E>>(
82 value: P,
83 max_num_message: LweCiphertextCount,
84 ) -> Result<Self, crate::Error>
85 where
86 E: Into<crate::Error>,
87 {
88 let params: CompactPublicKeyEncryptionParameters =
89 value.try_into().map_err(|e| e.into())?;
90
91 let mut plaintext_modulus = params.message_modulus.0 * params.carry_modulus.0;
92 plaintext_modulus *= 2;
94
95 let (lwe_dim, max_num_message, noise_bound, ciphertext_modulus, plaintext_modulus) =
96 CompactPkeCrs::prepare_crs_parameters(
97 params.encryption_lwe_dimension,
98 max_num_message,
99 params.encryption_noise_distribution,
100 params.ciphertext_modulus,
101 plaintext_modulus,
102 CompactPkeZkScheme::V2,
103 )?;
104
105 Ok(Self {
106 lwe_dim,
107 max_num_message,
108 noise_bound,
109 ciphertext_modulus,
110 plaintext_modulus,
111 msbs_zero_padding_bit_count: ZkMSBZeroPaddingBitCount(1),
113 })
114 }
115}
116
117impl ParameterSetConformant for ZkCompactPkeV1PublicParams {
118 type ParameterSet = CompactPkeCrsConformanceParams;
119
120 fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
121 self.k <= self.d
122 && self.d == parameter_set.lwe_dim.0
123 && self.k == parameter_set.max_num_message.0
124 && self.b == parameter_set.noise_bound
125 && self.q == parameter_set.ciphertext_modulus
126 && self.t == parameter_set.plaintext_modulus
127 && self.msbs_zero_padding_bit_count == parameter_set.msbs_zero_padding_bit_count.0
128 && self.is_usable()
129 }
130}
131
132impl ParameterSetConformant for ZkCompactPkeV2PublicParams {
133 type ParameterSet = CompactPkeCrsConformanceParams;
134
135 fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
136 self.k <= self.d
137 && self.d == parameter_set.lwe_dim.0
138 && self.k == parameter_set.max_num_message.0
139 && self.B_inf == parameter_set.noise_bound
140 && self.q == parameter_set.ciphertext_modulus
141 && self.t == parameter_set.plaintext_modulus
142 && self.msbs_zero_padding_bit_count == parameter_set.msbs_zero_padding_bit_count.0
143 && self.is_usable()
144 }
145}
146
147impl Named for SerializableCompactPkePublicParams {
153 const NAME: &'static str = ZkCompactPkeV1PublicParams::NAME;
154}
155
156#[derive(Copy, Clone, Eq, PartialEq)]
157pub enum ZkVerificationOutcome {
158 Valid,
160 Invalid,
162}
163
164impl ZkVerificationOutcome {
165 pub fn is_valid(self) -> bool {
166 self == Self::Valid
167 }
168
169 pub fn is_invalid(self) -> bool {
170 self == Self::Invalid
171 }
172}
173
174#[derive(Clone, Copy)]
177pub enum CompactPkeZkScheme {
178 V1,
179 V2,
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub struct ZkMSBZeroPaddingBitCount(pub u64);
184
185#[derive(Clone, Debug, Serialize, Deserialize, Versionize)]
190#[versionize(CompactPkeCrsVersions)]
191#[allow(clippy::large_enum_variant)]
192pub enum CompactPkeCrs {
193 PkeV1(ZkCompactPkeV1PublicParams),
194 PkeV2(ZkCompactPkeV2PublicParams),
195}
196
197impl Named for CompactPkeCrs {
198 const NAME: &'static str = "zk::CompactPkeCrs";
199
200 const BACKWARD_COMPATIBILITY_ALIASES: &'static [&'static str] = &["zk::CompactPkePublicParams"];
201}
202
203impl From<ZkCompactPkeV1PublicParams> for CompactPkeCrs {
204 fn from(value: ZkCompactPkeV1PublicParams) -> Self {
205 Self::PkeV1(value)
206 }
207}
208
209impl From<ZkCompactPkeV2PublicParams> for CompactPkeCrs {
210 fn from(value: ZkCompactPkeV2PublicParams) -> Self {
211 Self::PkeV2(value)
212 }
213}
214
215impl CompactPkeCrs {
216 fn compute_bound_v1<Scalar, NoiseDistribution>(
218 noise_distribution: NoiseDistribution,
219 ) -> Result<Scalar, String>
220 where
221 Scalar: UnsignedInteger + CastInto<u64> + Debug,
222 NoiseDistribution: BoundedDistribution<Scalar::Signed>,
223 {
224 let high_bound = match noise_distribution.high_bound() {
225 Bound::Included(high_b) => high_b,
226 Bound::Excluded(high_b) => high_b - Scalar::Signed::ONE,
227 Bound::Unbounded => {
228 return Err("requires bounded distribution".into());
229 }
230 };
231
232 let low_bound = match noise_distribution.low_bound() {
233 Bound::Included(low_b) => low_b,
234 Bound::Excluded(low_b) => low_b + Scalar::Signed::ONE,
235 Bound::Unbounded => {
236 return Err("requires bounded distribution".into());
237 }
238 };
239
240 if high_bound != -low_bound {
241 return Err("requires a distribution centered around 0".into());
242 }
243
244 let high_bound = high_bound.wrapping_abs().into_unsigned();
249 if high_bound.is_power_of_two() {
250 Ok(high_bound * Scalar::TWO)
251 } else {
252 Ok(high_bound.next_power_of_two())
253 }
254 }
255
256 fn compute_bound_v2<Scalar, NoiseDistribution>(
258 noise_distribution: NoiseDistribution,
259 ) -> Result<Scalar, String>
260 where
261 Scalar: UnsignedInteger + CastInto<u64> + Debug,
262 NoiseDistribution: BoundedDistribution<Scalar::Signed>,
263 {
264 let high_bound = match noise_distribution.high_bound() {
266 Bound::Included(high_b) => high_b,
267 Bound::Excluded(high_b) => high_b - Scalar::Signed::ONE,
268 Bound::Unbounded => {
269 return Err("requires bounded distribution".into());
270 }
271 };
272
273 let low_bound = match noise_distribution.low_bound() {
274 Bound::Included(low_b) => low_b,
275 Bound::Excluded(low_b) => low_b + Scalar::Signed::ONE,
276 Bound::Unbounded => {
277 return Err("requires bounded distribution".into());
278 }
279 };
280
281 if high_bound != -low_bound {
282 return Err("requires a distribution centered around 0".into());
283 }
284
285 Ok(high_bound.wrapping_abs().into_unsigned())
286 }
287
288 pub fn prepare_crs_parameters<Scalar, NoiseDistribution>(
292 lwe_dim: LweDimension,
293 max_num_cleartext: LweCiphertextCount,
294 noise_distribution: NoiseDistribution,
295 ciphertext_modulus: CiphertextModulus<Scalar>,
296 plaintext_modulus: Scalar,
297 zk_scheme: CompactPkeZkScheme,
298 ) -> crate::Result<(LweDimension, LweCiphertextCount, Scalar, u64, Scalar)>
299 where
300 Scalar: UnsignedInteger + CastInto<u64> + Debug,
301 NoiseDistribution: BoundedDistribution<Scalar::Signed>,
302 {
303 if max_num_cleartext.0 > lwe_dim.0 {
304 return Err("Maximum number of cleartexts is greater than the lwe dimension".into());
305 }
306
307 let noise_bound = match zk_scheme {
308 CompactPkeZkScheme::V1 => Self::compute_bound_v1(noise_distribution)?,
309 CompactPkeZkScheme::V2 => Self::compute_bound_v2(noise_distribution)?,
310 };
311
312 if Scalar::BITS > 64 && noise_bound >= (Scalar::ONE << 64usize) {
313 return Err("noise bounds exceeds 64 bits modulus".into());
314 }
315
316 if Scalar::BITS > 64 && plaintext_modulus >= (Scalar::ONE << 64usize) {
317 return Err("Plaintext modulus exceeds 64 bits modulus".into());
318 }
319
320 let q = if ciphertext_modulus.is_native_modulus() {
321 match Scalar::BITS.cmp(&64) {
322 Ordering::Greater => Err(
323 "Zero Knowledge proof do not support ciphertext modulus > 64 bits".to_string(),
324 ),
325 Ordering::Equal => Ok(0u64),
326 Ordering::Less => Ok(1u64 << Scalar::BITS),
327 }
328 } else {
329 let custom_modulus = ciphertext_modulus.get_custom_modulus();
330 if custom_modulus > (u64::MAX) as u128 {
331 Err("Zero Knowledge proof do not support ciphertext modulus > 64 bits".to_string())
332 } else {
333 Ok(custom_modulus as u64)
334 }
335 }?;
336
337 Ok((
338 lwe_dim,
339 max_num_cleartext,
340 noise_bound,
341 q,
342 plaintext_modulus,
343 ))
344 }
345
346 pub fn new_legacy_v1<Scalar, NoiseDistribution>(
349 lwe_dim: LweDimension,
350 max_num_cleartext: LweCiphertextCount,
351 noise_distribution: NoiseDistribution,
352 ciphertext_modulus: CiphertextModulus<Scalar>,
353 plaintext_modulus: Scalar,
354 msbs_zero_padding_bit_count: ZkMSBZeroPaddingBitCount,
355 rng: &mut impl RngCore,
356 ) -> crate::Result<Self>
357 where
358 Scalar: UnsignedInteger + CastInto<u64> + Debug,
359 NoiseDistribution: BoundedDistribution<Scalar::Signed>,
360 {
361 let (d, k, b, q, t) = Self::prepare_crs_parameters(
362 lwe_dim,
363 max_num_cleartext,
364 noise_distribution,
365 ciphertext_modulus,
366 plaintext_modulus,
367 CompactPkeZkScheme::V1,
368 )?;
369 let public_params = crs_gen_v1(
370 d.0,
371 k.0,
372 b.cast_into(),
373 q,
374 t.cast_into(),
375 msbs_zero_padding_bit_count.0,
376 rng,
377 );
378
379 Ok(Self::PkeV1(public_params))
380 }
381
382 pub fn new<Scalar, NoiseDistribution>(
384 lwe_dim: LweDimension,
385 max_num_cleartext: LweCiphertextCount,
386 noise_distribution: NoiseDistribution,
387 ciphertext_modulus: CiphertextModulus<Scalar>,
388 plaintext_modulus: Scalar,
389 msbs_zero_padding_bit_count: ZkMSBZeroPaddingBitCount,
390 rng: &mut impl RngCore,
391 ) -> crate::Result<Self>
392 where
393 Scalar: UnsignedInteger + CastInto<u64> + Debug,
394 NoiseDistribution: BoundedDistribution<Scalar::Signed>,
395 {
396 let (d, k, b, q, t) = Self::prepare_crs_parameters(
397 lwe_dim,
398 max_num_cleartext,
399 noise_distribution,
400 ciphertext_modulus,
401 plaintext_modulus,
402 CompactPkeZkScheme::V2,
403 )?;
404 let public_params = crs_gen_v2(
405 d.0,
406 k.0,
407 b.cast_into(),
408 q,
409 t.cast_into(),
410 msbs_zero_padding_bit_count.0,
411 rng,
412 );
413
414 Ok(Self::PkeV2(public_params))
415 }
416
417 pub fn max_num_messages(&self) -> LweCiphertextCount {
419 match self {
420 Self::PkeV1(public_params) => LweCiphertextCount(public_params.k),
421 Self::PkeV2(public_params) => LweCiphertextCount(public_params.k),
422 }
423 }
424
425 pub fn lwe_dimension(&self) -> LweDimension {
427 match self {
428 Self::PkeV1(public_params) => LweDimension(public_params.d),
429 Self::PkeV2(public_params) => LweDimension(public_params.d),
430 }
431 }
432
433 pub fn ciphertext_modulus<Scalar: UnsignedInteger>(&self) -> CiphertextModulus<Scalar> {
435 match self {
436 Self::PkeV1(public_params) => CiphertextModulus::new(public_params.q as u128),
437 Self::PkeV2(public_params) => CiphertextModulus::new(public_params.q as u128),
438 }
439 }
440
441 pub fn plaintext_modulus(&self) -> u64 {
443 match self {
444 Self::PkeV1(public_params) => public_params.t,
445 Self::PkeV2(public_params) => public_params.t,
446 }
447 }
448
449 pub fn exclusive_max_noise(&self) -> u64 {
451 match self {
452 Self::PkeV1(public_params) => public_params.exclusive_max_noise(),
453 Self::PkeV2(public_params) => public_params.exclusive_max_noise(),
454 }
455 }
456
457 pub fn scheme_version(&self) -> CompactPkeZkScheme {
459 match self {
460 Self::PkeV1(_) => CompactPkeZkScheme::V1,
461 Self::PkeV2(_) => CompactPkeZkScheme::V2,
462 }
463 }
464
465 #[allow(clippy::too_many_arguments)]
467 pub fn prove<Scalar, KeyCont, InputCont, ListCont, G>(
468 &self,
469 compact_public_key: &LweCompactPublicKey<KeyCont>,
470 messages: &InputCont,
471 lwe_compact_list: &LweCompactCiphertextList<ListCont>,
472 binary_random_vector: &[Scalar],
473 mask_noise: &[Scalar],
474 body_noise: &[Scalar],
475 metadata: &[u8],
476 load: ZkComputeLoad,
477 random_generator: &mut RandomGenerator<G>,
478 ) -> CompactPkeProof
479 where
480 Scalar: UnsignedInteger,
481 i64: CastFrom<Scalar>,
482 KeyCont: Container<Element = Scalar>,
483 InputCont: Container<Element = Scalar>,
484 ListCont: Container<Element = Scalar>,
485 G: ByteRandomGenerator,
486 {
487 let key_mask = compact_public_key
488 .get_mask()
489 .as_ref()
490 .iter()
491 .copied()
492 .map(|x| i64::cast_from(x))
493 .collect();
494 let key_body = compact_public_key
495 .get_body()
496 .as_ref()
497 .iter()
498 .copied()
499 .map(|x| i64::cast_from(x))
500 .collect();
501
502 let ct_mask = lwe_compact_list
503 .get_mask_list()
504 .as_ref()
505 .iter()
506 .copied()
507 .map(|x| i64::cast_from(x))
508 .collect();
509 let ct_body = lwe_compact_list
510 .get_body_list()
511 .as_ref()
512 .iter()
513 .copied()
514 .map(|x| i64::cast_from(x))
515 .collect();
516
517 let binary_random_vector = binary_random_vector
518 .iter()
519 .copied()
520 .map(CastFrom::cast_from)
521 .collect::<Vec<_>>();
522
523 let mask_noise = mask_noise
524 .iter()
525 .copied()
526 .map(CastFrom::cast_from)
527 .collect::<Vec<_>>();
528
529 let messages = messages
530 .as_ref()
531 .iter()
532 .copied()
533 .map(CastFrom::cast_from)
534 .collect::<Vec<_>>();
535
536 let body_noise = body_noise
537 .iter()
538 .copied()
539 .map(CastFrom::cast_from)
540 .collect::<Vec<_>>();
541
542 match self {
543 Self::PkeV1(public_params) => {
544 let (public_commit, private_commit) = commit_v1(
545 key_mask,
546 key_body,
547 ct_mask,
548 ct_body,
549 binary_random_vector,
550 mask_noise,
551 messages,
552 body_noise,
553 public_params,
554 random_generator,
555 );
556
557 let proof = prove_v1(
558 (public_params, &public_commit),
559 &private_commit,
560 metadata,
561 load,
562 random_generator,
563 );
564
565 CompactPkeProof::PkeV1(proof)
566 }
567 Self::PkeV2(public_params) => {
568 let (public_commit, private_commit) = commit_v2(
569 key_mask,
570 key_body,
571 ct_mask,
572 ct_body,
573 binary_random_vector,
574 mask_noise,
575 messages,
576 body_noise,
577 public_params,
578 random_generator,
579 );
580
581 let proof = prove_v2(
582 (public_params, &public_commit),
583 &private_commit,
584 metadata,
585 load,
586 random_generator,
587 );
588
589 CompactPkeProof::PkeV2(proof)
590 }
591 }
592 }
593
594 pub fn verify<Scalar, ListCont, KeyCont>(
596 &self,
597 lwe_compact_list: &LweCompactCiphertextList<ListCont>,
598 compact_public_key: &LweCompactPublicKey<KeyCont>,
599 proof: &CompactPkeProof,
600 metadata: &[u8],
601 ) -> ZkVerificationOutcome
602 where
603 Scalar: UnsignedInteger,
604 i64: CastFrom<Scalar>,
605 ListCont: Container<Element = Scalar>,
606 KeyCont: Container<Element = Scalar>,
607 {
608 if Scalar::BITS > 64 {
609 return ZkVerificationOutcome::Invalid;
610 }
611
612 let key_mask = compact_public_key
613 .get_mask()
614 .as_ref()
615 .iter()
616 .copied()
617 .map(|x| i64::cast_from(x))
618 .collect();
619 let key_body = compact_public_key
620 .get_body()
621 .as_ref()
622 .iter()
623 .copied()
624 .map(|x| i64::cast_from(x))
625 .collect();
626
627 let ct_mask = lwe_compact_list
628 .get_mask_list()
629 .as_ref()
630 .iter()
631 .copied()
632 .map(|x| i64::cast_from(x))
633 .collect();
634 let ct_body = lwe_compact_list
635 .get_body_list()
636 .as_ref()
637 .iter()
638 .copied()
639 .map(|x| i64::cast_from(x))
640 .collect();
641
642 let res = match (self, proof) {
643 (Self::PkeV1(public_params), CompactPkeProof::PkeV1(proof)) => {
644 let public_commit = PublicCommitV1::new(key_mask, key_body, ct_mask, ct_body);
645 verify_v1(proof, (public_params, &public_commit), metadata)
646 }
647 (Self::PkeV2(public_params), CompactPkeProof::PkeV2(proof)) => {
648 let public_commit = PublicCommitV2::new(key_mask, key_body, ct_mask, ct_body);
649 verify_v2(proof, (public_params, &public_commit), metadata)
650 }
651
652 (Self::PkeV1(_), CompactPkeProof::PkeV2(_))
653 | (Self::PkeV2(_), CompactPkeProof::PkeV1(_)) => {
654 Err(())
656 }
657 };
658
659 match res {
660 Ok(_) => ZkVerificationOutcome::Valid,
661 Err(_) => ZkVerificationOutcome::Invalid,
662 }
663 }
664}
665
666impl ParameterSetConformant for CompactPkeCrs {
667 type ParameterSet = CompactPkeCrsConformanceParams;
668
669 fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
670 match self {
671 Self::PkeV1(public_params) => public_params.is_conformant(parameter_set),
672 Self::PkeV2(public_params) => public_params.is_conformant(parameter_set),
673 }
674 }
675}
676
677#[derive(Serialize, Deserialize, Versionize)]
679#[versionize(CompressedCompactPkeCrsVersions)]
680pub enum CompressedCompactPkeCrs {
681 PkeV1(<ZkCompactPkeV1PublicParams as Compressible>::Compressed),
682 PkeV2(<ZkCompactPkeV2PublicParams as Compressible>::Compressed),
683}
684
685impl Named for CompressedCompactPkeCrs {
688 const NAME: &'static str = CompactPkeCrs::NAME;
689}
690
691impl Compressible for CompactPkeCrs {
692 type Compressed = CompressedCompactPkeCrs;
693
694 type UncompressError = <ZkCompactPkeV1PublicParams as Compressible>::UncompressError;
695
696 fn compress(&self) -> Self::Compressed {
697 match self {
698 Self::PkeV1(public_params) => CompressedCompactPkeCrs::PkeV1(public_params.compress()),
699 Self::PkeV2(public_params) => CompressedCompactPkeCrs::PkeV2(public_params.compress()),
700 }
701 }
702
703 fn uncompress(compressed: Self::Compressed) -> Result<Self, Self::UncompressError> {
704 Ok(match compressed {
705 CompressedCompactPkeCrs::PkeV1(compressed_params) => {
706 Self::PkeV1(Compressible::uncompress(compressed_params)?)
707 }
708 CompressedCompactPkeCrs::PkeV2(compressed_params) => {
709 Self::PkeV2(Compressible::uncompress(compressed_params)?)
710 }
711 })
712 }
713}
714
715#[cfg(all(test, feature = "shortint"))]
716mod test {
717 use super::*;
718 use crate::safe_serialization::{safe_deserialize_conformant, safe_serialize};
719 use crate::shortint::parameters::*;
720 use crate::shortint::{CarryModulus, MessageModulus};
721
722 #[test]
723 fn test_crs_conformance() {
724 let params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
725 let mut bad_params = params;
726 bad_params.carry_modulus = CarryModulus(8);
727 bad_params.message_modulus = MessageModulus(8);
728
729 let mut rng = rand::thread_rng();
730
731 let crs = CompactPkeCrs::new(
732 params.encryption_lwe_dimension,
733 LweCiphertextCount(4),
734 params.encryption_noise_distribution,
735 params.ciphertext_modulus,
736 params.message_modulus.0 * params.carry_modulus.0 * 2,
737 ZkMSBZeroPaddingBitCount(1),
738 &mut rng,
739 )
740 .unwrap();
741
742 let conformance_params =
743 CompactPkeCrsConformanceParams::new(params, LweCiphertextCount(4)).unwrap();
744
745 assert!(crs.is_conformant(&conformance_params));
746
747 let conformance_params =
748 CompactPkeCrsConformanceParams::new(bad_params, LweCiphertextCount(4)).unwrap();
749
750 assert!(!crs.is_conformant(&conformance_params));
751
752 let conformance_params =
753 CompactPkeCrsConformanceParams::new(params, LweCiphertextCount(2)).unwrap();
754
755 assert!(!crs.is_conformant(&conformance_params));
756 }
757
758 #[test]
759 fn test_crs_serialization() {
760 let params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
761
762 let mut rng = rand::thread_rng();
763
764 let crs = CompactPkeCrs::new(
765 params.encryption_lwe_dimension,
766 LweCiphertextCount(4),
767 params.encryption_noise_distribution,
768 params.ciphertext_modulus,
769 params.message_modulus.0 * params.carry_modulus.0 * 2,
770 ZkMSBZeroPaddingBitCount(1),
771 &mut rng,
772 )
773 .unwrap();
774
775 let conformance_params =
776 CompactPkeCrsConformanceParams::new(params, LweCiphertextCount(4)).unwrap();
777
778 let mut serialized = Vec::new();
779 safe_serialize(&crs, &mut serialized, 1 << 30).unwrap();
780
781 let _crs_deser: CompactPkeCrs =
782 safe_deserialize_conformant(serialized.as_slice(), 1 << 30, &conformance_params)
783 .unwrap();
784
785 let mut serialized = Vec::new();
787 safe_serialize(&crs.compress(), &mut serialized, 1 << 30).unwrap();
788
789 let _crs_deser: CompactPkeCrs =
790 safe_deserialize_conformant(serialized.as_slice(), 1 << 30, &conformance_params)
791 .unwrap();
792 }
793}