Skip to content

Commit

Permalink
doc: update serialization doc
Browse files Browse the repository at this point in the history
  • Loading branch information
nsarlin-zama committed Oct 10, 2024
1 parent 88f7ab5 commit f244102
Showing 1 changed file with 132 additions and 78 deletions.
210 changes: 132 additions & 78 deletions tfhe/docs/fundamentals/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,36 @@

This document explains the `serialization` and `deserialization` features that are useful to send data to a server to perform the computations.

## Serialization/deserialization

**TFHE-rs** uses the [Serde](https://crates.io/crates/serde) framework and implements Serde's `Serialize` and `Deserialize` traits.

To serialize the data, you need to choose a [data format](https://serde.rs/#data-formats). In the following example, we use [bincode](https://crates.io/crates/bincode) for its binary format.

Here is a full example:

```toml
# Cargo.toml
## Safe serialization/deserialization

[dependencies]
# ...
tfhe = { version = "0.8.0", features = ["integer","x86_64-unix"]}
bincode = "1.3.3"
```
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:
```rust
// main.rs

use std::io::Cursor;
use tfhe::{ConfigBuilder, ServerKey, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>>{
let config = ConfigBuilder::default().build();

let (client_key, server_key) = generate_keys(config);

let msg1 = 1;
let msg2 = 0;

let value_1 = FheUint8::encrypt(msg1, &client_key);
let value_2 = FheUint8::encrypt(msg2, &client_key);

// Prepare to send data to the server
// The ClientKey is _not_ sent
let mut serialized_data = Vec::new();
bincode::serialize_into(&mut serialized_data, &server_key)?;
bincode::serialize_into(&mut serialized_data, &value_1)?;
bincode::serialize_into(&mut serialized_data, &value_2)?;

// Simulate sending serialized data to a server and getting
// back the serialized result
let serialized_result = server_function(&serialized_data)?;
let result: FheUint8 = bincode::deserialize(&serialized_result)?;

let output: u8 = result.decrypt(&client_key);
assert_eq!(output, msg1 + msg2);
Ok(())
}
use tfhe::safe_serialization::{safe_deserialize_conformant, safe_serialize};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
use tfhe::ServerKey;
use tfhe::{generate_keys, ConfigBuilder};

fn main() {
let params_1 = PARAM_MESSAGE_2_CARRY_2_KS_PBS;

fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut serialized_data = Cursor::new(serialized_data);
let server_key: ServerKey = bincode::deserialize_from(&mut serialized_data)?;
let ct_1: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
let config = ConfigBuilder::with_custom_parameters(params_1).build();

set_server_key(server_key);
let (client_key, server_key) = generate_keys(config);

let result = ct_1 + ct_2;
let mut buffer = vec![];

let serialized_result = bincode::serialize(&result)?;
// The last argument is the max allowed size for the serialized buffer
safe_serialize(&server_key, &mut buffer, 1 << 30).unwrap();

Ok(serialized_result)
let _server_key_deser: ServerKey =
safe_deserialize_conformant(buffer.as_slice(), 1 << 30, &config.into()).unwrap();
}
```

## Safe serialization/deserialization

When dealing with sensitive types, it's important to implement safe serialization and safe deserialization functions to prevent runtime errors and enhance security. The safe serialization and deserialization use `bincode` internally.

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".
Expand All @@ -90,14 +47,14 @@ The safe deserialization must take the output of a safe-serialization as input.

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 an example:
Here is a more complete example:

```rust
// main.rs

use tfhe::conformance::ParameterSetConformant;
use tfhe::prelude::*;
use tfhe::safe_serialization::{SerializationConfig, DeserializationConfig};
use tfhe::safe_serialization::{safe_serialize, safe_deserialize_conformant};
use tfhe::shortint::parameters::{PARAM_MESSAGE_2_CARRY_2_KS_PBS, PARAM_MESSAGE_2_CARRY_2_PBS_KS};
use tfhe::conformance::ListSizeConstraint;
use tfhe::{
Expand All @@ -108,54 +65,151 @@ use tfhe::{
fn main() {
let params_1 = PARAM_MESSAGE_2_CARRY_2_KS_PBS;
let params_2 = PARAM_MESSAGE_2_CARRY_2_PBS_KS;

let config = ConfigBuilder::with_custom_parameters(params_1).build();

let (client_key, server_key) = generate_keys(config);

let conformance_params_1 = FheUint8ConformanceParams::from(params_1);
let conformance_params_2 = FheUint8ConformanceParams::from(params_2);

let public_key = CompactPublicKey::new(&client_key);

let msg = 27u8;

let ct = FheUint8::try_encrypt(msg, &client_key).unwrap();

assert!(ct.is_conformant(&conformance_params_1));
assert!(!ct.is_conformant(&conformance_params_2));

let mut buffer = vec![];

SerializationConfig::new(1 << 20).serialize_into(&ct, &mut buffer).unwrap();

assert!(DeserializationConfig::new(1 << 20)
.deserialize_from::<FheUint8>(buffer.as_slice(), &conformance_params_2)
safe_serialize(&ct, &mut buffer, 1 << 20).unwrap();

assert!(safe_deserialize_conformant::<FheUint8>(buffer.as_slice(), 1 << 20, &conformance_params_2)
.is_err());

let ct2 = DeserializationConfig::new(1 << 20)
.deserialize_from::<FheUint8>(buffer.as_slice(), &conformance_params_1)
let ct2: FheUint8 = safe_deserialize_conformant(buffer.as_slice(), 1 << 20, &conformance_params_1)
.unwrap();

let dec: u8 = ct2.decrypt(&client_key);
assert_eq!(msg, dec);


// Example with a compact list:
let msgs = [27, 188u8];
let mut builder = CompactCiphertextList::builder(&public_key);
builder.extend(msgs.iter().copied());
let compact_list = builder.build();

let mut buffer = vec![];
SerializationConfig::new(1 << 20).serialize_into(&compact_list, &mut buffer).unwrap();
safe_serialize(&compact_list, &mut buffer, 1 << 20).unwrap();

let conformance_params = CompactCiphertextListConformanceParams {
shortint_params: params_1.to_shortint_conformance_param(),
num_elements_constraint: ListSizeConstraint::exact_size(2),
};
DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), &conformance_params)
safe_deserialize_conformant::<CompactCiphertextList>(buffer.as_slice(), 1 << 20, &conformance_params)
.unwrap();
}
```

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:

```rust
// main.rs

use tfhe::safe_serialization::{safe_deserialize_conformant, SerializationConfig};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
use tfhe::ServerKey;
use tfhe::{generate_keys, ConfigBuilder};

fn main() {
let params_1 = PARAM_MESSAGE_2_CARRY_2_KS_PBS;

let config = ConfigBuilder::with_custom_parameters(params_1).build();

let (client_key, server_key) = generate_keys(config);

let mut buffer = vec![];

SerializationConfig::new(1 << 30).disable_versioning().serialize_into(&server_key, &mut buffer).unwrap();

// You will still be able to load this item with `safe_deserialize_conformant`, but only using the current version of TFHE-rs
let _server_key_deser: ServerKey =
safe_deserialize_conformant(buffer.as_slice(), 1 << 30, &config.into()).unwrap();
}
```

## Serialization/deserialization using serde

**TFHE-rs** uses the [Serde](https://crates.io/crates/serde) framework and implements Serde's `Serialize` and `Deserialize` traits.

This allows you to serialize into any [data format](https://serde.rs/#data-formats) 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 [bincode](https://crates.io/crates/bincode) for its binary format:

```toml
# Cargo.toml

[dependencies]
# ...
tfhe = { version = "0.8.0", features = ["integer","x86_64-unix"]}
bincode = "1.3.3"
```

```rust
// main.rs

use std::io::Cursor;
use tfhe::{ConfigBuilder, ServerKey, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>>{
let config = ConfigBuilder::default().build();

let (client_key, server_key) = generate_keys(config);

let msg1 = 1;
let msg2 = 0;

let value_1 = FheUint8::encrypt(msg1, &client_key);
let value_2 = FheUint8::encrypt(msg2, &client_key);

// Prepare to send data to the server
// The ClientKey is _not_ sent
let mut serialized_data = Vec::new();
bincode::serialize_into(&mut serialized_data, &server_key)?;
bincode::serialize_into(&mut serialized_data, &value_1)?;
bincode::serialize_into(&mut serialized_data, &value_2)?;

// Simulate sending serialized data to a server and getting
// back the serialized result
let serialized_result = server_function(&serialized_data)?;
let result: FheUint8 = bincode::deserialize(&serialized_result)?;

let output: u8 = result.decrypt(&client_key);
assert_eq!(output, msg1 + msg2);
Ok(())
}


fn server_function(serialized_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut serialized_data = Cursor::new(serialized_data);
let server_key: ServerKey = bincode::deserialize_from(&mut serialized_data)?;
let ct_1: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;
let ct_2: FheUint8 = bincode::deserialize_from(&mut serialized_data)?;

set_server_key(server_key);

let result = ct_1 + ct_2;

let serialized_result = bincode::serialize(&result)?;

Ok(serialized_result)
}
```

0 comments on commit f244102

Please sign in to comment.