Skip to main content

tfhe/shortint/parameters/
meta.rs

1use std::ops::RangeInclusive;
2
3use serde::{Deserialize, Serialize};
4use tfhe_versionable::Versionize;
5
6use crate::conformance::EnumSet;
7use crate::core_crypto::prelude::{CastInto, UnsignedInteger};
8use crate::named::Named;
9use crate::shortint::backward_compatibility::parameters::{
10    DedicatedCompactPublicKeyParametersVersions, MetaParametersVersions,
11};
12use crate::shortint::parameters::{
13    Backend, CompactPublicKeyEncryptionParameters, CompressionParameters,
14    MetaNoiseSquashingParameters, ShortintKeySwitchingParameters, SupportedCompactPkeZkScheme,
15};
16use crate::shortint::{
17    AtomicPatternParameters, CarryModulus, EncryptionKeyChoice, MessageModulus,
18    MultiBitPBSParameters, PBSParameters,
19};
20
21#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Versionize)]
22#[versionize(DedicatedCompactPublicKeyParametersVersions)]
23pub struct DedicatedCompactPublicKeyParameters {
24    /// Parameters used by the dedicated compact public key
25    pub pke_params: CompactPublicKeyEncryptionParameters,
26    /// Parameters used to key switch from the compact public key
27    /// parameters to compute parameters
28    pub ksk_params: ShortintKeySwitchingParameters,
29    /// Parameters to key switch from the compact public key
30    /// to rerand state
31    pub re_randomization_parameters: Option<ShortintKeySwitchingParameters>,
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Versionize)]
35#[versionize(MetaParametersVersions)]
36pub struct MetaParameters {
37    pub backend: Backend,
38    /// The parameters used by ciphertext when doing computations
39    pub compute_parameters: AtomicPatternParameters,
40    /// Parameters when using a dedicated compact public key
41    /// (For smaller and more efficient CompactCiphertextList)
42    pub dedicated_compact_public_key_parameters: Option<DedicatedCompactPublicKeyParameters>,
43    /// Parameters for compression CompressedCiphertextList
44    pub compression_parameters: Option<CompressionParameters>,
45    /// Parameters for noise squashing
46    pub noise_squashing_parameters: Option<MetaNoiseSquashingParameters>,
47}
48
49impl Named for MetaParameters {
50    const NAME: &str = "shortint::MetaParameters";
51}
52
53impl MetaParameters {
54    pub fn noise_distribution_kind(&self) -> NoiseDistributionKind {
55        match self.compute_parameters {
56            AtomicPatternParameters::Standard(pbsparameters) => match pbsparameters {
57                PBSParameters::PBS(pbs) => pbs.lwe_noise_distribution.kind(),
58                PBSParameters::MultiBitPBS(multi_bit_pbs) => {
59                    multi_bit_pbs.lwe_noise_distribution.kind()
60                }
61            },
62            AtomicPatternParameters::KeySwitch32(key_switch32_pbsparameters) => {
63                key_switch32_pbsparameters.lwe_noise_distribution.kind()
64            }
65        }
66    }
67
68    pub fn failure_probability(&self) -> Log2PFail {
69        Log2PFail(self.compute_parameters.log2_p_fail())
70    }
71}
72
73/// Represents the type of noise distribution used in cryptographic operations.
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub enum NoiseDistributionKind {
76    Gaussian = 0,
77    TUniform = 1,
78}
79
80impl CastInto<usize> for NoiseDistributionKind {
81    fn cast_into(self) -> usize {
82        self as usize
83    }
84}
85
86/// Struct to express the allowed noise distribution kinds for parameter selection.
87///
88/// This struct is used to specify which [`NoiseDistributionKind`]s are acceptable
89/// when searching for compatible cryptographic parameters.
90#[derive(Debug, Copy, Clone)]
91pub struct NoiseDistributionChoice(EnumSet<NoiseDistributionKind>);
92
93impl NoiseDistributionChoice {
94    /// Creates a new empty choice, i.e. no distributions are allowed.
95    ///
96    /// Starting point for building a custom choice by then [Self::allow] noise distributions.
97    pub fn new() -> Self {
98        Self(EnumSet::new())
99    }
100
101    /// Creates new choice that allows all noise distributions.
102    pub fn allow_all() -> Self {
103        Self::new()
104            .allow(NoiseDistributionKind::Gaussian)
105            .allow(NoiseDistributionKind::TUniform)
106    }
107
108    /// Adds a noise distribution kind to the choice.
109    ///
110    /// `kind` will be allowed
111    pub fn allow(mut self, kind: NoiseDistributionKind) -> Self {
112        self.0.insert(kind);
113        self
114    }
115
116    /// Removes a noise distribution kind from the choice.
117    ///
118    /// `kind` won't be allowed
119    pub fn deny(mut self, kind: NoiseDistributionKind) -> Self {
120        self.0.remove(kind);
121        self
122    }
123
124    fn is_compatible(&self, params: &MetaParameters) -> bool {
125        self.0.contains(params.noise_distribution_kind())
126    }
127}
128
129impl Default for NoiseDistributionChoice {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl<T: UnsignedInteger> super::DynamicDistribution<T> {
136    fn kind(&self) -> NoiseDistributionKind {
137        match self {
138            Self::Gaussian(_) => NoiseDistributionKind::Gaussian,
139            Self::TUniform(_) => NoiseDistributionKind::TUniform,
140        }
141    }
142}
143
144/// Represents a constraint on a value
145#[derive(Debug)]
146pub enum Constraint<T> {
147    /// The value must be less than (<) the specified constraint
148    LessThan(T),
149    /// The value must be less than or equal (<=) tothe specified constraint
150    LessThanOrEqual(T),
151    /// The value must be greater than (>) the specified constraint
152    GreaterThan(T),
153    /// The value must be greater than or equal (>=) tothe specified constraint
154    GreaterThanOrEqual(T),
155    /// The value must be equal to the specified constraint
156    Equal(T),
157    /// The value must be within the given range (min..=max)
158    Within(RangeInclusive<T>),
159}
160
161impl<T> Constraint<T>
162where
163    T: PartialOrd + PartialEq,
164{
165    fn is_compatible(&self, value: &T) -> bool {
166        match self {
167            Self::LessThan(v) => value < v,
168            Self::LessThanOrEqual(v) => value <= v,
169            Self::GreaterThan(v) => value > v,
170            Self::GreaterThanOrEqual(v) => value >= v,
171            Self::Equal(v) => value == v,
172            Self::Within(range) => range.contains(value),
173        }
174    }
175}
176
177/// Represents the failure probability in logarithmic scale.
178///
179/// Log2PFail(-x) = failure probability of 2^-128
180#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
181pub struct Log2PFail(pub f64);
182
183#[derive(Debug, Copy, Clone, PartialEq, Eq)]
184pub struct Version(pub u8, pub u8);
185
186impl Version {
187    pub fn major(self) -> u8 {
188        self.0
189    }
190    pub fn minor(self) -> u8 {
191        self.1
192    }
193
194    pub fn current() -> Self {
195        let major = env!("CARGO_PKG_VERSION_MAJOR")
196            .parse::<u8>()
197            .expect("Failed to parse major version");
198        let minor = env!("CARGO_PKG_VERSION_MINOR")
199            .parse::<u8>()
200            .expect("Failed to parse minor version");
201        Self(major, minor)
202    }
203}
204
205/// Allows specification of constraints for the multi-bit PBS
206pub struct MultiBitPBSChoice {
207    pub(crate) grouping_factor: Constraint<usize>,
208}
209
210impl MultiBitPBSChoice {
211    /// Creates a new choice with the given `grouping_factor`
212    pub fn new(grouping_factor: Constraint<usize>) -> Self {
213        Self { grouping_factor }
214    }
215
216    fn is_compatible(&self, params: &MultiBitPBSParameters) -> bool {
217        self.grouping_factor
218            .is_compatible(&params.grouping_factor.0)
219    }
220}
221
222/// Choices for the AtomicPattern
223///
224/// 3 Atomic Patterns are available
225///
226/// * Classic PBS
227/// * Multi-Bit PBS
228/// * Keyswitch 32-bit
229pub struct AtomicPatternChoice {
230    classical: bool,
231    multibit: Option<MultiBitPBSChoice>,
232    keyswitch32: bool,
233}
234
235impl AtomicPatternChoice {
236    /// Creates a choice which will not allow any atomic pattern
237    pub fn new() -> Self {
238        Self {
239            classical: false,
240            multibit: None,
241            keyswitch32: false,
242        }
243    }
244
245    /// Sets the possible choice for classic PBS
246    pub fn classic_pbs(mut self, allowed: bool) -> Self {
247        self.classical = allowed;
248        self
249    }
250
251    /// Sets the possible choice for multi-bit PBS
252    pub fn multi_bit_pbs(mut self, constraints: Option<MultiBitPBSChoice>) -> Self {
253        self.multibit = constraints;
254        self
255    }
256
257    /// Sets the choice for Keyswitch32
258    pub fn keyswitch32(mut self, allowed: bool) -> Self {
259        self.keyswitch32 = allowed;
260        self
261    }
262
263    fn is_compatible(&self, ap: &AtomicPatternParameters) -> bool {
264        match ap {
265            AtomicPatternParameters::Standard(pbs_params) => match pbs_params {
266                PBSParameters::PBS(_) => self.classical,
267                PBSParameters::MultiBitPBS(multi_bit_params) => self
268                    .multibit
269                    .as_ref()
270                    .is_some_and(|constraints| constraints.is_compatible(multi_bit_params)),
271            },
272            AtomicPatternParameters::KeySwitch32(_) => self.keyswitch32,
273        }
274    }
275
276    fn default_for_device(device: Backend) -> Self {
277        match device {
278            Backend::Cpu => Self::default_cpu(),
279            Backend::CudaGpu => Self::default_gpu(),
280        }
281    }
282
283    fn default_cpu() -> Self {
284        Self {
285            classical: true,
286            multibit: None,
287            keyswitch32: false,
288        }
289    }
290
291    fn default_gpu() -> Self {
292        Self {
293            classical: false,
294            multibit: Some(MultiBitPBSChoice::new(Constraint::LessThanOrEqual(4))),
295            keyswitch32: false,
296        }
297    }
298}
299
300impl Default for AtomicPatternChoice {
301    fn default() -> Self {
302        Self::new()
303    }
304}
305
306/// Constraints for the Zero-Knowledge proofs used within Compact Public Encryption
307#[derive(Copy, Clone, Debug)]
308pub struct CompactPkeZkSchemeChoice(EnumSet<SupportedCompactPkeZkScheme>);
309
310impl CastInto<usize> for SupportedCompactPkeZkScheme {
311    fn cast_into(self) -> usize {
312        self as usize
313    }
314}
315
316impl CompactPkeZkSchemeChoice {
317    pub fn new() -> Self {
318        Self(EnumSet::new())
319    }
320
321    pub fn not_used() -> Self {
322        Self::new().allow(SupportedCompactPkeZkScheme::ZkNotSupported)
323    }
324
325    pub fn allow_all() -> Self {
326        Self::new()
327            .allow(SupportedCompactPkeZkScheme::V1)
328            .allow(SupportedCompactPkeZkScheme::V2)
329    }
330
331    pub fn allow(mut self, v: SupportedCompactPkeZkScheme) -> Self {
332        self.0.insert(v);
333        self
334    }
335
336    pub fn deny(mut self, v: SupportedCompactPkeZkScheme) -> Self {
337        self.0.remove(v);
338        self
339    }
340
341    fn is_compatible(&self, v: SupportedCompactPkeZkScheme) -> bool {
342        if self.0.contains(SupportedCompactPkeZkScheme::ZkNotSupported) {
343            true
344        } else {
345            self.0.contains(v)
346        }
347    }
348}
349
350impl Default for CompactPkeZkSchemeChoice {
351    fn default() -> Self {
352        Self::new()
353    }
354}
355
356#[derive(Copy, Clone, Debug)]
357pub struct PkeKeyswitchTargetChoice(EnumSet<EncryptionKeyChoice>);
358
359impl CastInto<usize> for EncryptionKeyChoice {
360    fn cast_into(self) -> usize {
361        self as usize
362    }
363}
364
365impl PkeKeyswitchTargetChoice {
366    pub fn new() -> Self {
367        Self(EnumSet::new())
368    }
369
370    pub fn allow_all() -> Self {
371        Self::new()
372            .allow(EncryptionKeyChoice::Big)
373            .allow(EncryptionKeyChoice::Small)
374    }
375
376    pub fn allow(mut self, v: EncryptionKeyChoice) -> Self {
377        self.0.insert(v);
378        self
379    }
380
381    pub fn deny(mut self, v: EncryptionKeyChoice) -> Self {
382        self.0.remove(v);
383        self
384    }
385
386    fn is_compatible(&self, v: EncryptionKeyChoice) -> bool {
387        self.0.contains(v)
388    }
389}
390
391impl Default for PkeKeyswitchTargetChoice {
392    fn default() -> Self {
393        Self::new()
394    }
395}
396
397/// Constraints for the dedicated compact public key
398pub struct DedicatedPublicKeyChoice {
399    zk_scheme: CompactPkeZkSchemeChoice,
400    pke_keyswitch_target: PkeKeyswitchTargetChoice,
401    require_re_rand: bool,
402}
403
404impl DedicatedPublicKeyChoice {
405    pub fn new() -> Self {
406        Self::default()
407    }
408
409    /// Sets the Zero-Knowledge scheme constraints
410    pub fn with_zk_scheme(mut self, zk_scheme: CompactPkeZkSchemeChoice) -> Self {
411        self.zk_scheme = zk_scheme;
412        self
413    }
414
415    /// Sets the keyswitch constraints scheme constraints
416    pub fn with_pke_switch(mut self, pke_switch: PkeKeyswitchTargetChoice) -> Self {
417        self.pke_keyswitch_target = pke_switch;
418        self
419    }
420
421    pub fn with_re_randomization(mut self, required: bool) -> Self {
422        self.require_re_rand = required;
423        self
424    }
425
426    fn is_compatible(&self, dedicated_pk_params: &DedicatedCompactPublicKeyParameters) -> bool {
427        if self.require_re_rand && dedicated_pk_params.re_randomization_parameters.is_none() {
428            return false;
429        }
430
431        self.pke_keyswitch_target
432            .is_compatible(dedicated_pk_params.ksk_params.destination_key)
433            && self
434                .zk_scheme
435                .is_compatible(dedicated_pk_params.pke_params.zk_scheme)
436    }
437}
438
439impl Default for DedicatedPublicKeyChoice {
440    fn default() -> Self {
441        Self {
442            zk_scheme: CompactPkeZkSchemeChoice::allow_all(),
443            pke_keyswitch_target: PkeKeyswitchTargetChoice::allow_all(),
444            require_re_rand: false,
445        }
446    }
447}
448
449/// Choices for the noise squashing
450#[derive(Copy, Clone, Debug)]
451pub enum NoiseSquashingChoice {
452    /// Noise squashing required, with or without compression
453    Yes { with_compression: bool },
454    /// No noise squashing required
455    No,
456}
457
458impl NoiseSquashingChoice {
459    fn is_compatible(self, params: Option<&MetaNoiseSquashingParameters>) -> bool {
460        match (self, params) {
461            (Self::Yes { .. }, None) => false,
462            (Self::Yes { with_compression }, Some(params)) => {
463                params.compression_parameters.is_some() == with_compression
464            }
465            (Self::No, None | Some(_)) => true,
466        }
467    }
468}
469
470const KNOWN_PARAMETERS: [(Version, &[(&MetaParameters, &str)]); 2] = [
471    (Version(1, 4), &super::v1_4::VEC_ALL_META_PARAMETERS),
472    (Version(1, 5), &super::v1_5::VEC_ALL_META_PARAMETERS),
473];
474
475/// Struct that allows to search for known parameters of TFHE-RS given some
476/// constraints/choices.
477///
478/// # Note
479///
480/// Only parameters starting from version 1.4 can be found
481pub struct MetaParametersFinder {
482    msg_modulus: MessageModulus,
483    carry_modulus: CarryModulus,
484    failure_probability: Constraint<Log2PFail>,
485    version: Version,
486    backend: Backend,
487    atomic_pattern_choice: AtomicPatternChoice,
488    noise_distribution: NoiseDistributionChoice,
489    dedicated_compact_public_key_choice: Option<DedicatedPublicKeyChoice>,
490    use_compression: bool,
491    noise_squashing_choice: NoiseSquashingChoice,
492    use_re_randomization: bool,
493}
494
495impl MetaParametersFinder {
496    /// Creates a finder with the given pfail constraint, version and target backend
497    ///
498    /// * The default message and carry modulus are both 2^2
499    /// * The atomic pattern constraints depends on the `backend`
500    /// * No other extra 'features' like compression, dedicated public key encryption, noise
501    ///   squashing are 'enabled'
502    pub fn new(pfail: Constraint<Log2PFail>, backend: Backend) -> Self {
503        Self {
504            msg_modulus: MessageModulus(4),
505            carry_modulus: CarryModulus(4),
506            failure_probability: pfail,
507            version: Version::current(),
508            backend,
509            atomic_pattern_choice: AtomicPatternChoice::default_for_device(backend),
510            dedicated_compact_public_key_choice: None,
511            use_compression: false,
512            noise_squashing_choice: NoiseSquashingChoice::No,
513            noise_distribution: NoiseDistributionChoice::allow_all(),
514            use_re_randomization: false,
515        }
516    }
517
518    pub fn with_version(mut self, version: Version) -> Self {
519        self.version = version;
520        self
521    }
522
523    /// Sets the noise distribution choice
524    pub const fn with_noise_distribution(
525        mut self,
526        noise_distribution: NoiseDistributionChoice,
527    ) -> Self {
528        self.noise_distribution = noise_distribution;
529        self
530    }
531
532    /// Sets the atomic pattern choice
533    pub const fn with_atomic_pattern(mut self, atomic_pattern_choice: AtomicPatternChoice) -> Self {
534        self.atomic_pattern_choice = atomic_pattern_choice;
535        self
536    }
537
538    /// Sets the dedicated compact public key choice
539    pub const fn with_dedicated_compact_public_key(
540        mut self,
541        choice: Option<DedicatedPublicKeyChoice>,
542    ) -> Self {
543        self.dedicated_compact_public_key_choice = choice;
544        self
545    }
546
547    /// Sets whether compression is desired
548    pub const fn with_compression(mut self, enabled: bool) -> Self {
549        self.use_compression = enabled;
550        self
551    }
552
553    /// Sets the noise squashing choice
554    pub const fn with_noise_squashing(mut self, choice: NoiseSquashingChoice) -> Self {
555        self.noise_squashing_choice = choice;
556        self
557    }
558
559    /// Sets the block modulus
560    ///
561    /// Only MessageModulus is required as MessageModulus == CarryModulus is forced
562    pub const fn with_block_modulus(mut self, message_modulus: MessageModulus) -> Self {
563        self.msg_modulus = message_modulus;
564        self.carry_modulus = CarryModulus(message_modulus.0);
565        self
566    }
567
568    pub const fn with_re_randomization(mut self, enabled: bool) -> Self {
569        self.use_re_randomization = enabled;
570        self
571    }
572
573    /// Checks if the meta parameter is compatible with the choices of the finder
574    ///
575    /// A parameter that has more capabilities (e.g. compression), when the user did not ask for
576    /// compression is deemed compatible because we can remove the compression params from the
577    /// struct.
578    fn is_compatible(&self, parameters: &MetaParameters) -> bool {
579        if self.backend != parameters.backend {
580            return false;
581        }
582
583        if self.msg_modulus != parameters.compute_parameters.message_modulus()
584            || self.carry_modulus != parameters.compute_parameters.carry_modulus()
585        {
586            return false;
587        }
588
589        if !self
590            .failure_probability
591            .is_compatible(&parameters.failure_probability())
592        {
593            return false;
594        }
595
596        if !self.noise_distribution.is_compatible(parameters) {
597            return false;
598        }
599
600        if !self
601            .atomic_pattern_choice
602            .is_compatible(&parameters.compute_parameters)
603        {
604            return false;
605        }
606
607        match (
608            self.dedicated_compact_public_key_choice.as_ref(),
609            &parameters.dedicated_compact_public_key_parameters,
610        ) {
611            (None, None | Some(_)) => {}
612            (Some(_), None) => return false,
613            (Some(choice), Some(params)) => {
614                if !choice.is_compatible(params) {
615                    return false;
616                }
617            }
618        }
619
620        if self.use_compression && parameters.compression_parameters.is_none() {
621            return false;
622        }
623
624        if !self
625            .noise_squashing_choice
626            .is_compatible(parameters.noise_squashing_parameters.as_ref())
627        {
628            return false;
629        }
630
631        true
632    }
633
634    /// Returns None if the parameters are not compatible
635    /// Returns Some(_) if the parameters are compatible
636    ///     The returned params may come from more 'capable' parameters
637    ///     where unnecessary params were removed
638    fn fit(&self, parameters: &MetaParameters) -> Option<MetaParameters> {
639        if self.is_compatible(parameters) {
640            let mut result = *parameters;
641            if self.dedicated_compact_public_key_choice.is_none() {
642                result.dedicated_compact_public_key_parameters = None;
643            }
644
645            if let Some(dedicated_pke_choice) = self.dedicated_compact_public_key_choice.as_ref() {
646                if !dedicated_pke_choice.require_re_rand {
647                    if let Some(pke_params) =
648                        result.dedicated_compact_public_key_parameters.as_mut()
649                    {
650                        pke_params.re_randomization_parameters = None;
651                    }
652                }
653            } else {
654                result.dedicated_compact_public_key_parameters = None;
655            }
656
657            if !self.use_compression {
658                result.compression_parameters = None;
659            }
660
661            match self.noise_squashing_choice {
662                NoiseSquashingChoice::Yes { with_compression } => {
663                    if !with_compression {
664                        if let Some(ns_params) = result.noise_squashing_parameters.as_mut() {
665                            ns_params.compression_parameters.take();
666                        }
667                    }
668                }
669                NoiseSquashingChoice::No => result.noise_squashing_parameters = None,
670            }
671
672            Some(result)
673        } else {
674            None
675        }
676    }
677
678    /// Returns all known meta parameter that satisfy the choices
679    pub fn find_all(&self) -> Vec<MetaParameters> {
680        self.named_find_all().into_iter().map(|(p, _)| p).collect()
681    }
682
683    /// Tries to find parameters that matches the constraints/choices
684    ///
685    /// Returns Some(_) if at least 1 compatible parameter was found,
686    /// None otherwise
687    ///
688    /// If more than one parameter is found, this function will apply its
689    /// own set of rule to select one of them:
690    /// * The parameter with highest pfail is prioritized
691    /// * CPU will favor classical PBS, with TUniform
692    /// * GPU will favor multi-bit PBS, with TUniform
693    /// * ZKV2 is prioritized
694    pub fn find(&self) -> Option<MetaParameters> {
695        let mut candidates = self.named_find_all();
696
697        if candidates.is_empty() {
698            return None;
699        }
700
701        if candidates.len() == 1 {
702            return candidates.pop().map(|(param, _)| param);
703        }
704
705        fn filter_candidates(
706            candidates: Vec<(MetaParameters, &str)>,
707            filter: impl Fn(&(MetaParameters, &str)) -> bool,
708        ) -> Vec<(MetaParameters, &str)> {
709            let filtered = candidates
710                .iter()
711                .copied()
712                .filter(filter)
713                .collect::<Vec<_>>();
714
715            // The filter keeps elements, based on what tfhe-rs sees as better default
716            // However, it's possible, that the input candidates list does not include
717            // anything that matches the filter
718            // e.g. the use selected MultiBitPBS on CPU, but our cpu filter prefers ClassicPBS
719            // therefor nothing will match, so we return the original list instead
720            if filtered.is_empty() {
721                candidates
722            } else {
723                filtered
724            }
725        }
726
727        let candidates = filter_candidates(candidates, |(params, _)| {
728            if let Some(pke_params) = params.dedicated_compact_public_key_parameters {
729                return pke_params.pke_params.zk_scheme == SupportedCompactPkeZkScheme::V2;
730            }
731            true
732        });
733
734        let mut candidates = match self.backend {
735            // On CPU we prefer Classical PBS with TUniform
736            Backend::Cpu => filter_candidates(candidates, |(params, _)| {
737                matches!(
738                    params.compute_parameters,
739                    AtomicPatternParameters::Standard(PBSParameters::PBS(_))
740                ) && params.noise_distribution_kind() == NoiseDistributionKind::TUniform
741            }),
742            // On GPU we prefer MultiBit PBS with TUniform
743            Backend::CudaGpu => filter_candidates(candidates, |(params, _)| {
744                matches!(
745                    params.compute_parameters,
746                    AtomicPatternParameters::Standard(PBSParameters::MultiBitPBS(_))
747                ) && params.noise_distribution_kind() == NoiseDistributionKind::TUniform
748            }),
749        };
750
751        // highest failure probability is the last element,
752        // and higher failure probability means better performance
753        //
754        // Since pfails are negative e.g: -128, -40 for 2^-128 and 2^-40
755        // the closest pfail to the constraint is the last one
756        candidates.sort_by(|(a, _), (b, _)| {
757            a.failure_probability()
758                .partial_cmp(&b.failure_probability())
759                .unwrap()
760        });
761
762        candidates.last().copied().map(|(params, _)| params)
763    }
764
765    /// Returns all known meta parameter that satisfy the choices
766    ///
767    /// This also returns the name of the original params, as it could help to make
768    /// debugging easier
769    fn named_find_all(&self) -> Vec<(MetaParameters, &'static str)> {
770        let mut candidates = Vec::new();
771
772        for (version, parameter_list) in KNOWN_PARAMETERS.iter() {
773            if *version != self.version {
774                continue; // Skip parameters from different versions
775            }
776
777            for (parameters, name) in *parameter_list {
778                if let Some(params) = self.fit(parameters) {
779                    candidates.push((params, *name));
780                }
781            }
782        }
783
784        candidates
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791
792    #[test]
793    fn test_parameter_finder() {
794        {
795            let finder = MetaParametersFinder::new(
796                Constraint::LessThanOrEqual(Log2PFail(-64.0)),
797                Backend::Cpu,
798            )
799            .with_compression(true)
800            .with_noise_squashing(NoiseSquashingChoice::Yes {
801                with_compression: true,
802            })
803            .with_version(Version(1, 4));
804
805            let params = finder.find();
806
807            let mut expected =
808                super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_BIG_ZKV1_TUNIFORM_2M128;
809            expected.dedicated_compact_public_key_parameters = None;
810            assert_eq!(params, Some(expected));
811
812            let finder = MetaParametersFinder::new(
813                Constraint::LessThanOrEqual(Log2PFail(-64.0)),
814                Backend::Cpu,
815            )
816            .with_version(Version(1, 4))
817            .with_atomic_pattern(AtomicPatternChoice::new().classic_pbs(true))
818            .with_compression(true)
819            .with_noise_squashing(NoiseSquashingChoice::Yes {
820                with_compression: true,
821            });
822            let params = finder.find();
823            assert_eq!(params.unwrap(), expected);
824        }
825
826        {
827            // Try to find multi-bit params for CPU
828            let finder = MetaParametersFinder::new(
829                Constraint::LessThanOrEqual(Log2PFail(-40.0)),
830                Backend::Cpu,
831            )
832            .with_version(Version(1, 4))
833            .with_atomic_pattern(
834                AtomicPatternChoice::new()
835                    .multi_bit_pbs(Some(MultiBitPBSChoice::new(Constraint::LessThanOrEqual(4)))),
836            );
837            let params = finder.find();
838            assert_eq!(
839                params,
840                Some(super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_MULTI_BIT_GROUP_4_KS_PBS_GAUSSIAN_2M40)
841            );
842
843            // Try to find multi-bit params for GPU
844            let finder = MetaParametersFinder::new(
845                Constraint::LessThanOrEqual(Log2PFail(-40.0)),
846                Backend::CudaGpu,
847            )
848            .with_version(Version(1, 4))
849            .with_atomic_pattern(
850                AtomicPatternChoice::new()
851                    .multi_bit_pbs(Some(MultiBitPBSChoice::new(Constraint::LessThanOrEqual(4)))),
852            );
853            let params = finder.find();
854
855            let mut expected =
856                super::super::v1_4::meta::gpu::V1_4_META_PARAM_GPU_2_2_MULTI_BIT_GROUP_3_KS_PBS_TUNIFORM_2M40;
857            expected.dedicated_compact_public_key_parameters = None;
858
859            assert_eq!(params, Some(expected));
860        }
861    }
862    #[test]
863    fn test_parameter_finder_dedicated_pke() {
864        {
865            // Select BIG, and dont care about ZK
866            let finder = MetaParametersFinder::new(
867                Constraint::LessThanOrEqual(Log2PFail(-64.0)),
868                Backend::Cpu,
869            )
870            .with_version(Version(1, 4))
871            .with_dedicated_compact_public_key(Some(
872                DedicatedPublicKeyChoice::new()
873                    .with_zk_scheme(CompactPkeZkSchemeChoice::not_used())
874                    .with_pke_switch(
875                        PkeKeyswitchTargetChoice::new().allow(EncryptionKeyChoice::Big),
876                    ),
877            ));
878
879            let params = finder.find();
880
881            let mut expected =
882                super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_BIG_ZKV2_TUNIFORM_2M128;
883            expected.compression_parameters = None;
884            expected.noise_squashing_parameters = None;
885            expected
886                .dedicated_compact_public_key_parameters
887                .as_mut()
888                .unwrap()
889                .re_randomization_parameters = None;
890            assert_eq!(params, Some(expected));
891
892            // Select SMALL, and dont care about ZK
893            let finder = MetaParametersFinder::new(
894                Constraint::LessThanOrEqual(Log2PFail(-64.0)),
895                Backend::Cpu,
896            )
897            .with_version(Version(1, 4))
898            .with_dedicated_compact_public_key(Some(
899                DedicatedPublicKeyChoice::new()
900                    .with_zk_scheme(CompactPkeZkSchemeChoice::not_used())
901                    .with_pke_switch(
902                        PkeKeyswitchTargetChoice::new().allow(EncryptionKeyChoice::Small),
903                    )
904                    .with_re_randomization(true),
905            ));
906
907            let params = finder.find();
908
909            let mut expected =
910                super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
911            expected.compression_parameters = None;
912            expected.noise_squashing_parameters = None;
913            assert_eq!(params, Some(expected));
914        }
915    }
916
917    #[test]
918    fn test_parameter_finder_ks32() {
919        let finder =
920            MetaParametersFinder::new(Constraint::LessThanOrEqual(Log2PFail(-64.0)), Backend::Cpu)
921                .with_version(Version(1, 4))
922                .with_atomic_pattern(AtomicPatternChoice::new().keyswitch32(true));
923
924        let params = finder.find();
925
926        let expected =
927            super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS32_PBS_TUNIFORM_2M128;
928
929        assert_eq!(params, Some(expected));
930    }
931}