From f244102cbca1d692f66092b076a0ecb0e6fe42d0 Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Wed, 9 Oct 2024 14:14:37 +0200 Subject: [PATCH] doc: update serialization doc --- tfhe/docs/fundamentals/serialization.md | 210 +++++++++++++++--------- 1 file changed, 132 insertions(+), 78 deletions(-) diff --git a/tfhe/docs/fundamentals/serialization.md b/tfhe/docs/fundamentals/serialization.md index 4e2ab0e94f..7f97e5022b 100644 --- a/tfhe/docs/fundamentals/serialization.md +++ b/tfhe/docs/fundamentals/serialization.md @@ -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>{ - 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, Box> { - 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". @@ -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::{ @@ -108,39 +65,37 @@ 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::(buffer.as_slice(), &conformance_params_2) + safe_serialize(&ct, &mut buffer, 1 << 20).unwrap(); + + assert!(safe_deserialize_conformant::(buffer.as_slice(), 1 << 20, &conformance_params_2) .is_err()); - let ct2 = DeserializationConfig::new(1 << 20) - .deserialize_from::(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); @@ -148,14 +103,113 @@ fn main() { 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::(buffer.as_slice(), &conformance_params) + safe_deserialize_conformant::(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>{ + 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, Box> { + 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) +} +```