mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-08 22:28:01 -05:00
feat: add MetaParameterFinder
This commit is contained in:
committed by
tmontaigu
parent
be1de6ef2b
commit
aa49d141c7
@@ -5,6 +5,7 @@ This document explains how the choice of cryptographic parameters impacts both t
|
||||
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,6 +49,7 @@ 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.
|
||||
|
||||
@@ -60,7 +61,7 @@ The following example shows how to compress and decompress a list containing 4 m
|
||||
```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