tfhe/zk/
mod.rs

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
61// Keep this to be able to deserialize CRS that were serialized as "CompactPkePublicParams" (TFHE-rs
62// 0.10 and before)
63pub 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        // Add 1 bit of modulus for the padding bit
93        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            // CRS created from shortint params have 1 MSB 0bit
112            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
147// If we call `CompactPkePublicParams::compress` we end up with a
148// `SerializableCompactPkePublicParams` that should also impl Named to be serializable with
149// `safe_serialization`. Since the `CompactPkePublicParams` is transformed into a
150// `SerializableCompactPkePublicParams` anyways before serialization, their impl of `Named` should
151// return the same string.
152impl Named for SerializableCompactPkePublicParams {
153    const NAME: &'static str = ZkCompactPkeV1PublicParams::NAME;
154}
155
156#[derive(Copy, Clone, Eq, PartialEq)]
157pub enum ZkVerificationOutcome {
158    /// The proof and its entity were valid
159    Valid,
160    /// The proof and its entity were not
161    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/// The Zk Scheme for compact private key encryption is available in 2 versions. In case of doubt,
175/// you should prefer the V2 which is more efficient.
176#[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/// The CRS (Common Reference String) of a ZK scheme is a set of values shared between the prover
186/// and the verifier.
187///
188/// The same CRS should be used at the prove and verify steps.
189#[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    /// Compute the bound used by the V1 Scheme from the noise distribution
217    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        // The bound for the crs has to be a power of two,
245        // it is [-b, b) (non-inclusive for the high bound)
246        // so we may have to give a bound that is bigger than
247        // what the distribution generates
248        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    /// Compute the bound used by the V2 Scheme from the noise distribution
257    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        // For zk v2 scheme, the proof is valid for an inclusive range on the noise bound
265        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    /// Prepare and check the CRS parameters.
289    ///
290    /// The output of this function can be used in [tfhe_zk_pok::proofs::pke::compute_crs_params].
291    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    /// Generates a new zk CRS from the tfhe parameters. This the v1 Zk PKE scheme which is less
347    /// efficient.
348    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    /// Generates a new zk CRS from the tfhe parameters.
383    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    /// Maximum number of messages that can be proven in a single list using this CRS
418    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    /// Lwe dimension supported by this CRS
426    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    /// Modulus of the ciphertexts supported by this CRS
434    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    /// Modulus of the plaintexts supported by this CRS
442    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    /// Upper bound on the noise accepted by this CRS
450    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    /// Return the version of the zk scheme used by this CRS
458    pub fn scheme_version(&self) -> CompactPkeZkScheme {
459        match self {
460            Self::PkeV1(_) => CompactPkeZkScheme::V1,
461            Self::PkeV2(_) => CompactPkeZkScheme::V2,
462        }
463    }
464
465    /// Prove a ciphertext list encryption using this CRS
466    #[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    /// Verify the validity of a proof using this CRS
595    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                // Proof is not compatible with the CRS, so we refuse it right there
655                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/// The CRS can be compressed by only storing the `x` part of the elliptic curve coordinates.
678#[derive(Serialize, Deserialize, Versionize)]
679#[versionize(CompressedCompactPkeCrsVersions)]
680pub enum CompressedCompactPkeCrs {
681    PkeV1(<ZkCompactPkeV1PublicParams as Compressible>::Compressed),
682    PkeV2(<ZkCompactPkeV2PublicParams as Compressible>::Compressed),
683}
684
685// The NAME impl is the same as CompactPkeCrs because once serialized they are represented with the
686// same object. Decompression is done automatically during deserialization.
687impl 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        // Check with compression
786        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}