This document explains how to save and load versioned data using the data versioning feature.
Starting from v0.6.4, TFHE-rs supports versioned data types. This allows you to store data and load it in the future without compatibility concerns. This feature is done by the tfhe-versionable
crate.
This versioning scheme is compatible with all the data formats supported by serde.
To use the versioning feature, wrap your types in their versioned equivalents before serialization using the versionize
method. You can load serialized data with the unversionize
function, even in newer versions of TFHE-rs where the data types might evolve. The unversionize
function manages any necessary data type upgrades, ensuring compatibility.
Calling .versionize()
on a value will add versioning tags. This is done recursively so all the subtypes that compose it are versioned too. Under the hood, it converts the value into an enum where each version of a type is represented by a new variant. The returned object can be serialized using serde:
The Type::unversionize()
function takes a versioned value, upgrades it to the latest version of its type and removes the version tags. To do that, it matches the version in the versioned enum and eventually apply a conversion function that upgrades it to the most recent version. The resulting value can then be used inside TFHE-rs
When possible, data will be upgraded automatically without any kind of interraction. However, some changes might need information that are only known by the user of the library. These are called data breaking changes. In these occasions, TFHE-rs provides a way to upgrade these types manually.
You will find below a list of breaking changes and how to upgrade them.
This document explains the serialization
and deserialization
features that are useful to send data to a server to perform the computations.
When dealing with sensitive types, it's important to implement safe serialization and safe deserialization functions to prevent runtime errors and enhance security. TFHE-rs provide easy to use functions for this purpose, such as safe_serialize
, safe_deserialize
and safe_deserialize_conformant
.
Here is a basic example on how to use it:
The safe deserialization must take the output of a safe-serialization as input. During the process, the following validation occurs:
Type match: deserializing type A
from a serialized type B
raises an error indicating "On deserialization, expected type A, got type B".
Parameter compatibility: deserializing an object of type A
with one set of crypto parameters from an object of type A
with another set of crypto parameters raises an error indicating "Deserialized object of type A not conformant with given parameter set"
If both parameter sets have the same LWE dimension for ciphertexts, a ciphertext from param 1 may not fail this deserialization check with param 2.
This check can't distinguish ciphertexts/server keys from independent client keys with the same parameters.
This check is meant to prevent runtime errors in server homomorphic operations by checking that server keys and ciphertexts are compatible with the same parameter set.
You can use the standalone is_conformant
method to check parameter compatibility. Besides, the safe_deserialize_conformant
function includes the parameter compatibility check, and the safe_deserialize
function does not include the compatibility check.
Size limit: both serialization and deserialization processes expect a size limit (measured in bytes) for the serialized data:
On serialization, an error is raised if the serialized output exceeds the specific limit.
On deserialization, an error is raised if the serialized input exceeds the specific limit.
This feature aims to gracefully return an error in case of an attacker trying to cause an out-of-memory error on deserialization.
Here is a more complete example:
The safe serialization and deserialization use bincode
internally.
To selectively disable some of the features of the safe serialization, you can use SerializationConfig
/DeserializationConfig
builders. For example, it is possible to disable the data versioning:
Version compatibility: data serialized in previous versions of TFHE-rs are automatically upgraded to the latest version using the feature.
TFHE-rs uses the framework and implements Serde's Serialize
and Deserialize
traits.
This allows you to serialize into any supported by serde. However, this is a more bare bone approach as none of the checks described in the previous section will be performed for you.
In the following example, we use for its binary format:
This document explains the mechanism and steps to compress ciphertext and keys to reduce the storage needed as well as transmission times.
Most TFHE-rs entities contain random numbers generated by a Pseudo Random Number Generator (PRNG). Since the implemented PRNG is deterministic, storing only the random seed used to generate those numbers preserves all necessary information. When decompressing the entity, using the same PRNG and the same seed will reconstruct the full chain of random values.
In TFHE-rs, compressible entities are prefixed with Compressed
. For instance, a compressed FheUint256
is declared as CompressedFheUint256
.
In the following example code, we use the bincode
crate dependency to serialize in a binary format and compare serialized sizes.
This example shows how to compress a ciphertext encrypting messages over 16 bits:
You can compress ciphertexts at any time, even after performing multiple homomorphic operations.
To do so, you need to build a list containing all the ciphertexts that have to be compressed. This list might contain ciphertexts of different types, e.g., FheBool, FheUint32, FheInt64,... 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
.
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.
This example shows how to compress the server keys:
This example shows how to compress the classical public keys:
It is not currently recommended to use the CompressedPublicKey
to encrypt ciphertexts without first decompressing them. If the resulting PublicKey is too large to fit in memory, it may result in significant slowdowns.
This issue has been identified and will be addressed in future releases.
This example shows how to use compressed compact public keys: