mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-07 22:04:10 -05:00
feat: add MetaParameterFinder
This commit is contained in:
committed by
tmontaigu
parent
be1de6ef2b
commit
aa49d141c7
@@ -2,9 +2,10 @@
|
||||
|
||||
This document explains how the choice of cryptographic parameters impacts both the security and efficiency of FHE algorithms. The chosen parameters determine the error probability (sometimes referred to failure probability) and overall performance of computations using fully homomorphic encryption. This error probability is due to the noisy nature of FHE computations (see [here](../../getting-started/security-and-cryptography.md) for more details about the encryption process).
|
||||
|
||||
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator).
|
||||
All parameter sets provide at least 128-bits of security according to the [Lattice-Estimator](https://github.com/malb/lattice-estimator).
|
||||
|
||||
## Default parameters
|
||||
|
||||
Currently, the default parameters use blocks that contain 2 bits of message and 2 bits of carry - a tweaked uniform (TUniform, defined [here](../../getting-started/security-and-cryptography.md#noise)) noise distribution, and have a bootstrapping failure probability $$p_{error} \le 2^{-128}$$.
|
||||
These are particularly suitable for applications that need to be secure in the IND-CPA^D model (see [here](../../getting-started/security-and-cryptography.md#security) for more details).
|
||||
|
||||
@@ -34,6 +35,7 @@ Parameter sets are versioned for backward compatibility. This means that each se
|
||||
All parameter sets are stored as variables inside the `tfhe::shortint::parameters` module, with submodules named after the versions of **TFHE-rs** in which these parameters where added. For example, parameters added in **TFHE-rs** v1.0 can be found inside `tfhe::shortint::parameters::v1_0`.
|
||||
|
||||
The naming convention of these parameters indicates their capabilities. Taking `tfhe::parameters::v1_0::V1_0_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128` as an example:
|
||||
|
||||
- `V1_0`: these parameters were introduced in **TFHE-rs** v1.0
|
||||
- `MESSAGE_2`: LWE blocks include 2 bits of message
|
||||
- `CARRY_2`: LWE blocks include 2 bits of carry
|
||||
@@ -43,8 +45,69 @@ The naming convention of these parameters indicates their capabilities. Taking `
|
||||
|
||||
For convenience, aliases are provided for the most used sets of parameters and stored in the module `tfhe::shortint::parameters::aliases`. Note, however, that these parameters are not stable over time and are always updated to the latest **TFHE-rs** version. For this reason, they should only be used for prototyping and are not suitable for production use cases.
|
||||
|
||||
|
||||
## How to choose the parameter sets
|
||||
|
||||
Since tfhe-rs 1.5, there is a `MetaParameterFinder` which enables to search for suitable parameters given some choice of constraints.
|
||||
|
||||
### Note
|
||||
|
||||
It is recommended to serialize the parameters found if you plan on re-using them, as the heuristics used by the `MetaParametersFinder` are susceptible to change across TFHE-rs versions
|
||||
|
||||
```rust
|
||||
use tfhe::shortint::parameters::{MetaParametersFinder, Log2PFail, Constraint, Version, Backend, NoiseDistributionChoice, NoiseDistributionKind};
|
||||
use tfhe::{FheUint32, CompressedCiphertextListBuilder, generate_keys, set_server_key};
|
||||
use tfhe::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Create a finder with minimal constraints
|
||||
let finder = MetaParametersFinder::new(
|
||||
// We want parameters that have a failure probability `pfail` that is: pfail <= 2^-64
|
||||
Constraint::LessThanOrEqual(Log2PFail(-64.0)),
|
||||
// We want parameters meant for CPU execution
|
||||
Backend::Cpu
|
||||
);
|
||||
|
||||
let parameters = finder
|
||||
.find()
|
||||
.expect("Could not find suitable parameters");
|
||||
|
||||
// It is recommended to serialize the parameters found if you plan on re-using them
|
||||
let mut serialized_params = vec![];
|
||||
tfhe::safe_serialization::safe_serialize(¶meters, &mut serialized_params, 1 << 10).unwrap();
|
||||
|
||||
|
||||
let (client_key, server_key) = generate_keys(parameters);
|
||||
|
||||
// We can add other constraints:
|
||||
let finder = finder
|
||||
// Find parameters from the 1.4 version
|
||||
// By default, the finder looks in the current tfhe-rs version
|
||||
.with_version(Version(1, 4))
|
||||
// We want to use compression (CompressedCiphertextList)
|
||||
// So we require parameters that support it
|
||||
.with_compression(true)
|
||||
.with_noise_distribution(
|
||||
// Allow any noise distribution that is not TUniform
|
||||
NoiseDistributionChoice::allow_all()
|
||||
.deny(NoiseDistributionKind::TUniform)
|
||||
);
|
||||
|
||||
|
||||
let parameters = finder
|
||||
.find()
|
||||
.expect("Could not find suitable parameters");
|
||||
|
||||
let (client_key, server_key) = generate_keys(parameters);
|
||||
|
||||
let a = FheUint32::encrypt(1337u32, &client_key);
|
||||
set_server_key(server_key);
|
||||
let compressed_list = CompressedCiphertextListBuilder::new()
|
||||
.push(a)
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
You can override the default parameters with the `with_custom_parameters(block_parameters)` method of the `Config` object. For example, to use a Gaussian distribution instead of the TUniform one, you can modify your configuration as follows:
|
||||
|
||||
```rust
|
||||
|
||||
@@ -49,18 +49,19 @@ To do so, you need to build a list containing all the ciphertexts that have to b
|
||||
There is no constraint regarding the size of the list.
|
||||
|
||||
There are two possible approaches:
|
||||
|
||||
- **Single list**: Compressing several ciphertexts into a single list. This generally yields a better compression ratio between output and input sizes;
|
||||
- **Multiple lists**: Using multiple lists. This offers more flexibility, since compression might happen at different times in the code, but could lead to larger outputs.
|
||||
|
||||
In more details, the optimal ratio is achieved with a list whose size is
|
||||
equal to the `lwe_per_glwe` field from the `CompressionParameters`.
|
||||
In more details, the optimal ratio is achieved with a list whose size is
|
||||
equal to the `lwe_per_glwe` field from the `CompressionParameters`.
|
||||
|
||||
The following example shows how to compress and decompress a list containing 4 messages: one 32-bits integer, one 64-bit integer, one boolean, and one 2-bit integer.
|
||||
|
||||
```rust
|
||||
use tfhe::prelude::*;
|
||||
use tfhe::shortint::parameters::{
|
||||
COMP_PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_2_CARRY_2,
|
||||
MetaParametersFinder, Log2PFail, Constraint, Backend,
|
||||
};
|
||||
use tfhe::{
|
||||
set_server_key, CompressedCiphertextList, CompressedCiphertextListBuilder, FheBool,
|
||||
@@ -68,12 +69,15 @@ use tfhe::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let config =
|
||||
tfhe::ConfigBuilder::with_custom_parameters(PARAM_MESSAGE_2_CARRY_2)
|
||||
.enable_compression(COMP_PARAM_MESSAGE_2_CARRY_2)
|
||||
.build();
|
||||
let parameters = MetaParametersFinder::new(
|
||||
Constraint::LessThanOrEqual(Log2PFail(-128.0)),
|
||||
Backend::Cpu
|
||||
)
|
||||
.with_compression(true)
|
||||
.find()
|
||||
.expect("Could not find suitable parameters");
|
||||
|
||||
let ck = tfhe::ClientKey::generate(config);
|
||||
let ck = tfhe::ClientKey::generate(parameters);
|
||||
let sk = tfhe::ServerKey::new(&ck);
|
||||
|
||||
set_server_key(sk);
|
||||
|
||||
@@ -168,7 +168,7 @@ impl From<MetaParameters> for Config {
|
||||
.and_then(|ns_p| ns_p.compression_parameters),
|
||||
cpk_re_randomization_ksk_params: meta_params
|
||||
.dedicated_compact_public_key_parameters
|
||||
.and_then(|dedicated_pke| dedicated_pke.re_randomization_parameters),
|
||||
.and_then(|params| params.re_randomization_parameters),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,4 +173,8 @@ impl ClassicPBSParameters {
|
||||
degree,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn pbs_order(&self) -> PBSOrder {
|
||||
self.encryption_key_choice.into_pbs_order()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ use crate::shortint::backward_compatibility::parameters::{
|
||||
};
|
||||
use crate::shortint::parameters::{
|
||||
Backend, CompactPublicKeyEncryptionParameters, CompressionParameters,
|
||||
MetaNoiseSquashingParameters, ShortintKeySwitchingParameters,
|
||||
MetaNoiseSquashingParameters, ShortintKeySwitchingParameters, SupportedCompactPkeZkScheme,
|
||||
};
|
||||
use crate::shortint::{
|
||||
AtomicPatternParameters, CarryModulus, EncryptionKeyChoice, MessageModulus,
|
||||
MultiBitPBSParameters, PBSParameters,
|
||||
};
|
||||
use crate::shortint::AtomicPatternParameters;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Versionize)]
|
||||
#[versionize(DedicatedCompactPublicKeyParametersVersions)]
|
||||
@@ -199,24 +202,6 @@ impl Version {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let major_cmp = self.major().cmp(&other.major());
|
||||
|
||||
if major_cmp != std::cmp::Ordering::Equal {
|
||||
return major_cmp;
|
||||
}
|
||||
|
||||
self.minor().cmp(&other.minor())
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows specification of constraints for the multi-bit PBS
|
||||
pub struct MultiBitPBSChoice {
|
||||
pub(crate) grouping_factor: Constraint<usize>,
|
||||
@@ -369,7 +354,7 @@ impl Default for CompactPkeZkSchemeChoice {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PkeSwitchChoice(EnumSet<EncryptionKeyChoice>);
|
||||
pub struct PkeKeyswitchTargetChoice(EnumSet<EncryptionKeyChoice>);
|
||||
|
||||
impl CastInto<usize> for EncryptionKeyChoice {
|
||||
fn cast_into(self) -> usize {
|
||||
@@ -377,7 +362,7 @@ impl CastInto<usize> for EncryptionKeyChoice {
|
||||
}
|
||||
}
|
||||
|
||||
impl PkeSwitchChoice {
|
||||
impl PkeKeyswitchTargetChoice {
|
||||
pub fn new() -> Self {
|
||||
Self(EnumSet::new())
|
||||
}
|
||||
@@ -403,7 +388,7 @@ impl PkeSwitchChoice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PkeSwitchChoice {
|
||||
impl Default for PkeKeyswitchTargetChoice {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
@@ -412,7 +397,7 @@ impl Default for PkeSwitchChoice {
|
||||
/// Constraints for the dedicated compact public key
|
||||
pub struct DedicatedPublicKeyChoice {
|
||||
zk_scheme: CompactPkeZkSchemeChoice,
|
||||
pke_switch: PkeSwitchChoice,
|
||||
pke_keyswitch_target: PkeKeyswitchTargetChoice,
|
||||
require_re_rand: bool,
|
||||
}
|
||||
|
||||
@@ -428,8 +413,8 @@ impl DedicatedPublicKeyChoice {
|
||||
}
|
||||
|
||||
/// Sets the keyswitch constraints scheme constraints
|
||||
pub fn with_pke_switch(mut self, pke_switch: PkeSwitchChoice) -> Self {
|
||||
self.pke_switch = pke_switch;
|
||||
pub fn with_pke_switch(mut self, pke_switch: PkeKeyswitchTargetChoice) -> Self {
|
||||
self.pke_keyswitch_target = pke_switch;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -443,7 +428,7 @@ impl DedicatedPublicKeyChoice {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.pke_switch
|
||||
self.pke_keyswitch_target
|
||||
.is_compatible(dedicated_pk_params.ksk_params.destination_key)
|
||||
&& self
|
||||
.zk_scheme
|
||||
@@ -455,7 +440,7 @@ impl Default for DedicatedPublicKeyChoice {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
zk_scheme: CompactPkeZkSchemeChoice::allow_all(),
|
||||
pke_switch: PkeSwitchChoice::allow_all(),
|
||||
pke_keyswitch_target: PkeKeyswitchTargetChoice::allow_all(),
|
||||
require_re_rand: false,
|
||||
}
|
||||
}
|
||||
@@ -571,7 +556,7 @@ impl MetaParametersFinder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the block moduluses (MessageModulus, CarryModulus)
|
||||
/// Sets the block modulus
|
||||
///
|
||||
/// Only MessageModulus is required as MessageModulus == CarryModulus is forced
|
||||
pub const fn with_block_modulus(mut self, message_modulus: MessageModulus) -> Self {
|
||||
@@ -703,8 +688,9 @@ impl MetaParametersFinder {
|
||||
/// If more than one parameter is found, this function will apply its
|
||||
/// own set of rule to select one of them:
|
||||
/// * The parameter with highest pfail is prioritized
|
||||
/// * CPU will favor classical PBS
|
||||
/// * GPU will favor multi-bit PBS
|
||||
/// * CPU will favor classical PBS, with TUniform
|
||||
/// * GPU will favor multi-bit PBS, with TUniform
|
||||
/// * ZKV2 is prioritized
|
||||
pub fn find(&self) -> Option<MetaParameters> {
|
||||
let mut candidates = self.named_find_all();
|
||||
|
||||
@@ -716,43 +702,64 @@ impl MetaParametersFinder {
|
||||
return candidates.pop().map(|(param, _)| param);
|
||||
}
|
||||
|
||||
fn filter_candidates(
|
||||
candidates: Vec<(MetaParameters, &str)>,
|
||||
filter: impl Fn(&(MetaParameters, &str)) -> bool,
|
||||
) -> Vec<(MetaParameters, &str)> {
|
||||
let filtered = candidates
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(filter)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// The filter keeps elements, based on what tfhe-rs sees as better default
|
||||
// However, it's possible, that the input candidates list does not include
|
||||
// anything that matches the filter
|
||||
// e.g. the use selected MultiBitPBS on CPU, but our cpu filter prefers ClassicPBS
|
||||
// therefor nothing will match, so we return the original list instead
|
||||
if filtered.is_empty() {
|
||||
candidates
|
||||
} else {
|
||||
filtered
|
||||
}
|
||||
}
|
||||
|
||||
let candidates = filter_candidates(candidates, |(params, _)| {
|
||||
if let Some(pke_params) = params.dedicated_compact_public_key_parameters {
|
||||
return pke_params.pke_params.zk_scheme == SupportedCompactPkeZkScheme::V2;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
let mut candidates = match self.backend {
|
||||
// On CPU we prefer Classical PBS with TUniform
|
||||
Backend::Cpu => filter_candidates(candidates, |(params, _)| {
|
||||
matches!(
|
||||
params.compute_parameters,
|
||||
AtomicPatternParameters::Standard(PBSParameters::PBS(_))
|
||||
) && params.noise_distribution_kind() == NoiseDistributionKind::TUniform
|
||||
}),
|
||||
// On GPU we prefer MultiBit PBS with TUniform
|
||||
Backend::CudaGpu => filter_candidates(candidates, |(params, _)| {
|
||||
matches!(
|
||||
params.compute_parameters,
|
||||
AtomicPatternParameters::Standard(PBSParameters::MultiBitPBS(_))
|
||||
) && params.noise_distribution_kind() == NoiseDistributionKind::TUniform
|
||||
}),
|
||||
};
|
||||
|
||||
// highest failure probability is the last element,
|
||||
// and higher failure probability means better performance
|
||||
//
|
||||
// Since pfails are negative e.g: -128, -40 for 2^-128 and 2^-40
|
||||
// the closest pfail the constraint is the last one
|
||||
// the closest pfail to the constraint is the last one
|
||||
candidates.sort_by(|(a, _), (b, _)| {
|
||||
a.failure_probability()
|
||||
.partial_cmp(&b.failure_probability())
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
match self.backend {
|
||||
// On CPU we prefer Classical PBS with TUniform
|
||||
Backend::Cpu => candidates
|
||||
.iter()
|
||||
.rfind(|(params, _)| {
|
||||
matches!(
|
||||
params.compute_parameters,
|
||||
AtomicPatternParameters::Standard(PBSParameters::PBS(_))
|
||||
) && params.noise_distribution_kind() == NoiseDistributionKind::TUniform
|
||||
})
|
||||
.copied()
|
||||
.or_else(|| candidates.pop())
|
||||
.map(|(param, _)| param),
|
||||
// On GPU we prefer MultiBit PBS with TUniform
|
||||
Backend::CudaGpu => candidates
|
||||
.iter()
|
||||
.rfind(|(params, _)| {
|
||||
matches!(
|
||||
params.compute_parameters,
|
||||
AtomicPatternParameters::Standard(PBSParameters::MultiBitPBS(_))
|
||||
) && params.noise_distribution_kind() == NoiseDistributionKind::TUniform
|
||||
})
|
||||
.copied()
|
||||
.or_else(|| candidates.pop())
|
||||
.map(|(param, _)| param),
|
||||
}
|
||||
candidates.last().copied().map(|(params, _)| params)
|
||||
}
|
||||
|
||||
/// Returns all known meta parameter that satisfy the choices
|
||||
@@ -864,13 +871,15 @@ mod tests {
|
||||
.with_dedicated_compact_public_key(Some(
|
||||
DedicatedPublicKeyChoice::new()
|
||||
.with_zk_scheme(CompactPkeZkSchemeChoice::not_used())
|
||||
.with_pke_switch(PkeSwitchChoice::new().allow(EncryptionKeyChoice::Big)),
|
||||
.with_pke_switch(
|
||||
PkeKeyswitchTargetChoice::new().allow(EncryptionKeyChoice::Big),
|
||||
),
|
||||
));
|
||||
|
||||
let params = finder.find();
|
||||
|
||||
let mut expected =
|
||||
super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_BIG_ZKV1_TUNIFORM_2M128;
|
||||
super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_BIG_ZKV2_TUNIFORM_2M128;
|
||||
expected.compression_parameters = None;
|
||||
expected.noise_squashing_parameters = None;
|
||||
expected
|
||||
@@ -889,14 +898,16 @@ mod tests {
|
||||
.with_dedicated_compact_public_key(Some(
|
||||
DedicatedPublicKeyChoice::new()
|
||||
.with_zk_scheme(CompactPkeZkSchemeChoice::not_used())
|
||||
.with_pke_switch(PkeSwitchChoice::new().allow(EncryptionKeyChoice::Small))
|
||||
.with_pke_switch(
|
||||
PkeKeyswitchTargetChoice::new().allow(EncryptionKeyChoice::Small),
|
||||
)
|
||||
.with_re_randomization(true),
|
||||
));
|
||||
|
||||
let params = finder.find();
|
||||
|
||||
let mut expected =
|
||||
super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV1_TUNIFORM_2M128;
|
||||
super::super::v1_4::meta::cpu::V1_4_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
|
||||
expected.compression_parameters = None;
|
||||
expected.noise_squashing_parameters = None;
|
||||
assert_eq!(params, Some(expected));
|
||||
|
||||
@@ -79,7 +79,11 @@ pub use compact_public_key_only::{
|
||||
pub use coverage_parameters::*;
|
||||
pub use key_switching::ShortintKeySwitchingParameters;
|
||||
pub use ks32::KeySwitch32PBSParameters;
|
||||
pub use meta::MetaParameters;
|
||||
pub use meta::{
|
||||
AtomicPatternChoice, CompactPkeZkSchemeChoice, Constraint, DedicatedPublicKeyChoice, Log2PFail,
|
||||
MetaParameters, MetaParametersFinder, MultiBitPBSChoice, NoiseDistributionChoice,
|
||||
NoiseDistributionKind, NoiseSquashingChoice, PkeKeyswitchTargetChoice, Version,
|
||||
};
|
||||
pub use multi_bit::MultiBitPBSParameters;
|
||||
pub use noise_squashing::{
|
||||
MetaNoiseSquashingParameters, NoiseSquashingClassicParameters,
|
||||
|
||||
Reference in New Issue
Block a user