#![cfg_attr(dylint_lib = "tfhe_lints", allow(serialize_without_versionize))]
use std::borrow::Cow;
use std::fmt::Display;
use crate::conformance::ParameterSetConformant;
use crate::named::Named;
use bincode::Options;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use tfhe_versionable::{Unversionize, Versionize};
const SERIALIZATION_VERSION: &str = "0.5";
const VERSIONING_VERSION: &str = "0.1";
const CRATE_VERSION: &str = concat!(
env!("CARGO_PKG_VERSION_MAJOR"),
".",
env!("CARGO_PKG_VERSION_MINOR")
);
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
enum SerializationVersioningMode {
Versioned {
versioning_version: Cow<'static, str>,
},
Unversioned {
crate_version: Cow<'static, str>,
},
}
impl Display for SerializationVersioningMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Versioned { .. } => write!(f, "versioned"),
Self::Unversioned { .. } => write!(f, "unversioned"),
}
}
}
impl SerializationVersioningMode {
fn versioned() -> Self {
Self::Versioned {
versioning_version: Cow::Borrowed(VERSIONING_VERSION),
}
}
fn unversioned() -> Self {
Self::Unversioned {
crate_version: Cow::Borrowed(CRATE_VERSION),
}
}
}
#[derive(Serialize, Deserialize)]
struct SerializationHeader {
header_version: Cow<'static, str>,
versioning_mode: SerializationVersioningMode,
name: Cow<'static, str>,
}
impl SerializationHeader {
fn new_versioned<T: Named>() -> Self {
Self {
header_version: Cow::Borrowed(SERIALIZATION_VERSION),
versioning_mode: SerializationVersioningMode::versioned(),
name: Cow::Borrowed(T::NAME),
}
}
fn new_unversioned<T: Named>() -> Self {
Self {
header_version: Cow::Borrowed(SERIALIZATION_VERSION),
versioning_mode: SerializationVersioningMode::unversioned(),
name: Cow::Borrowed(T::NAME),
}
}
fn validate<T: Named>(&self) -> Result<(), String> {
if self.header_version != SERIALIZATION_VERSION {
return Err(format!(
"On deserialization, expected serialization header version {SERIALIZATION_VERSION}, \
got version {}", self.header_version
));
}
match &self.versioning_mode {
SerializationVersioningMode::Versioned { versioning_version } => {
if versioning_version != VERSIONING_VERSION {
return Err(format!(
"On deserialization, expected versioning scheme version {VERSIONING_VERSION}, \
got version {versioning_version}"
));
}
}
SerializationVersioningMode::Unversioned { crate_version } => {
if crate_version != CRATE_VERSION {
return Err(format!(
"This {} has been saved from TFHE-rs v{crate_version}, without versioning information. \
Please use the versioned serialization mode for backward compatibility.",
self.name
));
}
}
}
if self.name != T::NAME
&& T::BACKWARD_COMPATIBILITY_ALIASES
.iter()
.all(|alias| self.name != *alias)
{
return Err(format!(
"On deserialization, expected type {}, got type {}",
T::NAME,
self.name
));
}
Ok(())
}
}
#[derive(Clone)]
pub struct SerializationConfig {
versioned: SerializationVersioningMode,
serialized_size_limit: Option<u64>,
}
impl SerializationConfig {
pub fn new(serialized_size_limit: u64) -> Self {
Self {
versioned: SerializationVersioningMode::versioned(),
serialized_size_limit: Some(serialized_size_limit),
}
}
pub fn new_with_unlimited_size() -> Self {
Self {
versioned: SerializationVersioningMode::versioned(),
serialized_size_limit: None,
}
}
pub fn disable_size_limit(self) -> Self {
Self {
serialized_size_limit: None,
..self
}
}
pub fn disable_versioning(self) -> Self {
Self {
versioned: SerializationVersioningMode::unversioned(),
..self
}
}
pub fn with_size_limit(self, size: u64) -> Self {
Self {
serialized_size_limit: Some(size),
..self
}
}
fn create_header<T: Named>(&self) -> SerializationHeader {
match self.versioned {
SerializationVersioningMode::Versioned { .. } => {
SerializationHeader::new_versioned::<T>()
}
SerializationVersioningMode::Unversioned { .. } => {
SerializationHeader::new_unversioned::<T>()
}
}
}
pub fn serialized_size<T: Serialize + Versionize + Named>(
&self,
object: &T,
) -> bincode::Result<u64> {
let options = bincode::DefaultOptions::new().with_fixint_encoding();
let header = self.create_header::<T>();
let header_size = options.serialized_size(&header)?;
let data_size = match self.versioned {
SerializationVersioningMode::Versioned { .. } => {
options.serialized_size(&object.versionize())?
}
SerializationVersioningMode::Unversioned { .. } => options.serialized_size(&object)?,
};
Ok(header_size + data_size)
}
pub fn serialize_into<T: Serialize + Versionize + Named>(
self,
object: &T,
mut writer: impl std::io::Write,
) -> bincode::Result<()> {
let options = bincode::DefaultOptions::new()
.with_fixint_encoding()
.with_limit(0);
let header = self.create_header::<T>();
let header_size = options.with_no_limit().serialized_size(&header)?;
if let Some(size_limit) = self.serialized_size_limit {
options
.with_limit(size_limit)
.serialize_into(&mut writer, &header)?;
let options = options.with_limit(size_limit - header_size);
match self.versioned {
SerializationVersioningMode::Versioned { .. } => {
options.serialize_into(&mut writer, &object.versionize())?
}
SerializationVersioningMode::Unversioned { .. } => {
options.serialize_into(&mut writer, &object)?
}
}
} else {
let options = options.with_no_limit();
options.serialize_into(&mut writer, &header)?;
match self.versioned {
SerializationVersioningMode::Versioned { .. } => {
options.serialize_into(&mut writer, &object.versionize())?
}
SerializationVersioningMode::Unversioned { .. } => {
options.serialize_into(&mut writer, &object)?
}
}
}
Ok(())
}
}
#[derive(Copy, Clone)]
pub struct DeserializationConfig {
serialized_size_limit: Option<u64>,
validate_header: bool,
}
#[derive(Copy, Clone)]
pub struct NonConformantDeserializationConfig {
serialized_size_limit: Option<u64>,
validate_header: bool,
}
impl NonConformantDeserializationConfig {
fn deserialize_header(
&self,
reader: &mut impl std::io::Read,
) -> Result<SerializationHeader, String> {
let options = bincode::DefaultOptions::new()
.with_fixint_encoding()
.with_limit(0);
if let Some(size_limit) = self.serialized_size_limit {
options
.with_limit(size_limit)
.deserialize_from(reader)
.map_err(|err| err.to_string())
} else {
options
.with_no_limit()
.deserialize_from(reader)
.map_err(|err| err.to_string())
}
}
pub fn deserialize_from<T: DeserializeOwned + Unversionize + Named>(
self,
mut reader: impl std::io::Read,
) -> Result<T, String> {
let options = bincode::DefaultOptions::new()
.with_fixint_encoding()
.with_limit(0);
let deserialized_header: SerializationHeader = self.deserialize_header(&mut reader)?;
let header_size = options
.with_no_limit()
.serialized_size(&deserialized_header)
.map_err(|err| err.to_string())?;
if self.validate_header {
deserialized_header.validate::<T>()?;
}
if let Some(size_limit) = self.serialized_size_limit {
let options = options.with_limit(size_limit - header_size);
match deserialized_header.versioning_mode {
SerializationVersioningMode::Versioned { .. } => {
let deser_versioned = options
.deserialize_from(&mut reader)
.map_err(|err| err.to_string())?;
T::unversionize(deser_versioned).map_err(|e| e.to_string())
}
SerializationVersioningMode::Unversioned { .. } => options
.deserialize_from(&mut reader)
.map_err(|err| err.to_string()),
}
} else {
let options = options.with_no_limit();
match deserialized_header.versioning_mode {
SerializationVersioningMode::Versioned { .. } => {
let deser_versioned = options
.deserialize_from(&mut reader)
.map_err(|err| err.to_string())?;
T::unversionize(deser_versioned).map_err(|e| e.to_string())
}
SerializationVersioningMode::Unversioned { .. } => options
.deserialize_from(&mut reader)
.map_err(|err| err.to_string()),
}
}
}
pub fn enable_conformance(self) -> DeserializationConfig {
DeserializationConfig {
serialized_size_limit: self.serialized_size_limit,
validate_header: self.validate_header,
}
}
}
impl DeserializationConfig {
pub fn new(deserialized_size_limit: u64) -> Self {
Self {
serialized_size_limit: Some(deserialized_size_limit),
validate_header: true,
}
}
pub fn new_with_unlimited_size() -> Self {
Self {
serialized_size_limit: None,
validate_header: true,
}
}
pub fn disable_size_limit(self) -> Self {
Self {
serialized_size_limit: None,
..self
}
}
pub fn with_size_limit(self, size: u64) -> Self {
Self {
serialized_size_limit: Some(size),
..self
}
}
pub fn disable_header_validation(self) -> Self {
Self {
validate_header: false,
..self
}
}
pub fn disable_conformance(self) -> NonConformantDeserializationConfig {
NonConformantDeserializationConfig {
serialized_size_limit: self.serialized_size_limit,
validate_header: self.validate_header,
}
}
pub fn deserialize_from<T: DeserializeOwned + Unversionize + Named + ParameterSetConformant>(
self,
reader: impl std::io::Read,
parameter_set: &T::ParameterSet,
) -> Result<T, String> {
let deser: T = self.disable_conformance().deserialize_from(reader)?;
if !deser.is_conformant(parameter_set) {
return Err(format!(
"Deserialized object of type {} not conformant with given parameter set",
T::NAME
));
}
Ok(deser)
}
}
pub fn safe_serialize<T: Serialize + Versionize + Named>(
object: &T,
writer: impl std::io::Write,
serialized_size_limit: u64,
) -> bincode::Result<()> {
SerializationConfig::new(serialized_size_limit).serialize_into(object, writer)
}
pub fn safe_serialized_size<T: Serialize + Versionize + Named>(object: &T) -> bincode::Result<u64> {
SerializationConfig::new_with_unlimited_size().serialized_size(object)
}
pub fn safe_deserialize<T: DeserializeOwned + Unversionize + Named>(
reader: impl std::io::Read,
deserialized_size_limit: u64,
) -> Result<T, String> {
DeserializationConfig::new(deserialized_size_limit)
.disable_conformance()
.deserialize_from(reader)
}
pub fn safe_deserialize_conformant<
T: DeserializeOwned + Unversionize + Named + ParameterSetConformant,
>(
reader: impl std::io::Read,
deserialized_size_limit: u64,
parameter_set: &T::ParameterSet,
) -> Result<T, String> {
DeserializationConfig::new(deserialized_size_limit).deserialize_from(reader, parameter_set)
}
#[cfg(all(test, feature = "shortint"))]
mod test_shortint {
use tfhe_versionable::Versionize;
use crate::named::Named;
use crate::shortint::parameters::test_params::{
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M128,
};
use crate::shortint::{gen_keys, Ciphertext};
use super::*;
#[test]
fn safe_deserialization_ct_unversioned() {
let (ck, _sk) = gen_keys(TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
let msg = 2_u64;
let ct = ck.encrypt(msg);
let mut buffer = vec![];
let config = SerializationConfig::new(1 << 20).disable_versioning();
let size = config.serialized_size(&ct).unwrap();
config.serialize_into(&ct, &mut buffer).unwrap();
assert_eq!(size as usize, buffer.len());
assert!(DeserializationConfig::new(1 << 20)
.deserialize_from::<Ciphertext>(
buffer.as_slice(),
&TEST_PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M128.to_shortint_conformance_param()
)
.is_err());
let ct2 = DeserializationConfig::new(1 << 20)
.deserialize_from::<Ciphertext>(
buffer.as_slice(),
&TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128.to_shortint_conformance_param(),
)
.unwrap();
let dec = ck.decrypt(&ct2);
assert_eq!(msg, dec);
}
#[test]
fn safe_deserialization_ct_versioned() {
let (ck, _sk) = gen_keys(TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
let msg = 2_u64;
let ct = ck.encrypt(msg);
let mut buffer = vec![];
let config = SerializationConfig::new(1 << 20);
let size = config.serialized_size(&ct).unwrap();
config.serialize_into(&ct, &mut buffer).unwrap();
assert_eq!(size as usize, buffer.len());
assert!(DeserializationConfig::new(1 << 20,)
.deserialize_from::<Ciphertext>(
buffer.as_slice(),
&TEST_PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M128.to_shortint_conformance_param()
)
.is_err());
let ct2 = DeserializationConfig::new(1 << 20)
.deserialize_from::<Ciphertext>(
buffer.as_slice(),
&TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128.to_shortint_conformance_param(),
)
.unwrap();
let dec = ck.decrypt(&ct2);
assert_eq!(msg, dec);
}
#[test]
fn safe_deserialization_ct_unlimited_size() {
let (ck, _sk) = gen_keys(TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
let msg = 2_u64;
let ct = ck.encrypt(msg);
let mut buffer = vec![];
let config = SerializationConfig::new_with_unlimited_size();
let size = config.serialized_size(&ct).unwrap();
config.serialize_into(&ct, &mut buffer).unwrap();
assert_eq!(size as usize, buffer.len());
let ct2 = DeserializationConfig::new_with_unlimited_size()
.deserialize_from::<Ciphertext>(
buffer.as_slice(),
&TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128.to_shortint_conformance_param(),
)
.unwrap();
let dec = ck.decrypt(&ct2);
assert_eq!(msg, dec);
}
#[test]
fn safe_deserialization_size_limit() {
let (ck, _sk) = gen_keys(TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
let msg = 2_u64;
let ct = ck.encrypt(msg);
let mut buffer = vec![];
let config = SerializationConfig::new_with_unlimited_size().disable_versioning();
let size = config.serialized_size(&ct).unwrap();
config.serialize_into(&ct, &mut buffer).unwrap();
assert_eq!(size as usize, buffer.len());
let ct2 = DeserializationConfig::new(size)
.deserialize_from::<Ciphertext>(
buffer.as_slice(),
&TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128.to_shortint_conformance_param(),
)
.unwrap();
let dec = ck.decrypt(&ct2);
assert_eq!(msg, dec);
}
#[test]
fn safe_deserialization_named() {
#[derive(Serialize, Deserialize, Versionize)]
#[repr(transparent)]
struct Foo(u64);
impl Named for Foo {
const NAME: &'static str = "Foo";
}
#[derive(Deserialize, Versionize)]
#[repr(transparent)]
struct Bar(u64);
impl Named for Bar {
const NAME: &'static str = "Bar";
const BACKWARD_COMPATIBILITY_ALIASES: &'static [&'static str] = &["Foo"];
}
#[derive(Deserialize, Versionize)]
#[repr(transparent)]
struct Baz(u64);
impl Named for Baz {
const NAME: &'static str = "Baz";
}
let foo = Foo(3);
let mut foo_ser = Vec::new();
safe_serialize(&foo, &mut foo_ser, 0x1000).unwrap();
let foo_deser: Foo = safe_deserialize(foo_ser.as_slice(), 0x1000).unwrap();
let bar_deser: Bar = safe_deserialize(foo_ser.as_slice(), 0x1000).unwrap();
assert_eq!(foo_deser.0, bar_deser.0);
assert!(safe_deserialize::<Baz>(foo_ser.as_slice(), 0x1000).is_err());
}
}
#[cfg(all(test, feature = "integer"))]
mod test_integer {
use crate::conformance::ListSizeConstraint;
use crate::high_level_api::{generate_keys, ConfigBuilder};
use crate::prelude::*;
use crate::safe_serialization::{DeserializationConfig, SerializationConfig};
use crate::shortint::parameters::PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M128;
use crate::{
set_server_key, CompactCiphertextList, CompactCiphertextListConformanceParams,
CompactPublicKey, FheUint8,
};
fn test_safe_deserialization_ct_list(is_packed: bool) {
let (client_key, sks) = generate_keys(ConfigBuilder::default().build());
set_server_key(sks);
let public_key = CompactPublicKey::new(&client_key);
let msg = [27u8, 10, 3];
let mut builder = CompactCiphertextList::builder(&public_key);
for value in msg {
builder.push(value);
}
let ct_list = if is_packed {
builder.build_packed()
} else {
builder.build()
};
let mut buffer = vec![];
let config = SerializationConfig::new(1 << 20).disable_versioning();
let size = config.serialized_size(&ct_list).unwrap();
config.serialize_into(&ct_list, &mut buffer).unwrap();
assert_eq!(size as usize, buffer.len());
let to_param_set = |list_size_constraint| {
let params =
CompactCiphertextListConformanceParams::from_parameters_and_size_constraint(
public_key.parameters(),
list_size_constraint,
);
if is_packed {
params
} else {
params.allow_unpacked()
}
};
let wrong_pke_params =
CompactCiphertextListConformanceParams::from_parameters_and_size_constraint(
PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M128
.try_into()
.unwrap(),
ListSizeConstraint::exact_size(3),
);
for param_set in [
if is_packed {
wrong_pke_params
} else {
wrong_pke_params.allow_unpacked()
},
to_param_set(ListSizeConstraint::exact_size(2)),
to_param_set(ListSizeConstraint::exact_size(4)),
to_param_set(ListSizeConstraint::try_size_in_range(1, 2).unwrap()),
to_param_set(ListSizeConstraint::try_size_in_range(4, 5).unwrap()),
] {
assert!(DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), ¶m_set)
.is_err());
}
for len_constraint in [
ListSizeConstraint::exact_size(3),
ListSizeConstraint::try_size_in_range(2, 3).unwrap(),
ListSizeConstraint::try_size_in_range(3, 4).unwrap(),
ListSizeConstraint::try_size_in_range(2, 4).unwrap(),
] {
let params = to_param_set(len_constraint);
DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), ¶ms)
.unwrap();
}
let params = to_param_set(ListSizeConstraint::exact_size(3));
let ct2 = DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), ¶ms)
.unwrap();
let mut cts = Vec::with_capacity(3);
let expander = ct2.expand().unwrap();
for i in 0..3 {
cts.push(expander.get::<FheUint8>(i).unwrap().unwrap());
}
let dec: Vec<u8> = cts.iter().map(|a| a.decrypt(&client_key)).collect();
assert_eq!(&msg[..], &dec);
}
#[test]
fn safe_deserialization_ct_list() {
test_safe_deserialization_ct_list(false);
}
#[test]
fn safe_deserialization_ct_list_packed() {
test_safe_deserialization_ct_list(true);
}
#[test]
fn safe_deserialization_ct_list_versioned() {
let (client_key, sks) = generate_keys(ConfigBuilder::default().build());
set_server_key(sks);
let public_key = CompactPublicKey::new(&client_key);
let msg = [27u8, 10, 3];
let ct_list = CompactCiphertextList::builder(&public_key)
.push(27u8)
.push(10u8)
.push(3u8)
.build();
let mut buffer = vec![];
let config = SerializationConfig::new(1 << 20);
let size = config.serialized_size(&ct_list).unwrap();
config.serialize_into(&ct_list, &mut buffer).unwrap();
assert_eq!(size as usize, buffer.len());
let to_param_set = |list_size_constraint| {
CompactCiphertextListConformanceParams::from_parameters_and_size_constraint(
public_key.parameters(),
list_size_constraint,
)
.allow_unpacked()
};
for param_set in [
CompactCiphertextListConformanceParams::from_parameters_and_size_constraint(
PARAM_MESSAGE_3_CARRY_3_KS_PBS_GAUSSIAN_2M128
.try_into()
.unwrap(),
ListSizeConstraint::exact_size(3),
)
.allow_unpacked(),
to_param_set(ListSizeConstraint::exact_size(2)),
to_param_set(ListSizeConstraint::exact_size(4)),
to_param_set(ListSizeConstraint::try_size_in_range(1, 2).unwrap()),
to_param_set(ListSizeConstraint::try_size_in_range(4, 5).unwrap()),
] {
assert!(DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), ¶m_set)
.is_err());
}
for len_constraint in [
ListSizeConstraint::exact_size(3),
ListSizeConstraint::try_size_in_range(2, 3).unwrap(),
ListSizeConstraint::try_size_in_range(3, 4).unwrap(),
ListSizeConstraint::try_size_in_range(2, 4).unwrap(),
] {
let params = to_param_set(len_constraint);
DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), ¶ms)
.unwrap();
}
let params = to_param_set(ListSizeConstraint::exact_size(3));
let ct2 = DeserializationConfig::new(1 << 20)
.deserialize_from::<CompactCiphertextList>(buffer.as_slice(), ¶ms)
.unwrap();
let mut cts = Vec::with_capacity(3);
let expander = ct2.expand().unwrap();
for i in 0..3 {
cts.push(expander.get::<FheUint8>(i).unwrap().unwrap());
}
let dec: Vec<u8> = cts.iter().map(|a| a.decrypt(&client_key)).collect();
assert_eq!(&msg[..], &dec);
}
}