From d389ea67a103653770a252310ea4e783d3382088 Mon Sep 17 00:00:00 2001 From: tmontaigu Date: Thu, 21 Aug 2025 09:49:43 +0200 Subject: [PATCH] refactor!: Use NonZero in DataKind Change the type used to store a block count in DataKind to NonZero. This makes it impossible to store 'empty' kinds such as DataKind::Unsigned(0), DataKind::Signed(0). Also, when deserializing, if the count is zero and error will be returned, adding an additional layer of sanitization. --- tfhe/src/high_level_api/compact_list.rs | 4 +- .../compressed_ciphertext_list.rs | 31 +- ...mpressed_noise_squashed_ciphertext_list.rs | 36 +- tfhe/src/high_level_api/strings/ascii/mod.rs | 12 +- .../backward_compatibility/ciphertext/mod.rs | 45 ++- tfhe/src/integer/ciphertext/compact_list.rs | 354 ++++++++++++++++-- .../ciphertext/compressed_ciphertext_list.rs | 51 ++- ...mpressed_noise_squashed_ciphertext_list.rs | 55 ++- tfhe/src/integer/ciphertext/utils.rs | 7 +- .../integer/gpu/ciphertext/compact_list.rs | 4 +- .../ciphertext/compressed_ciphertext_list.rs | 31 +- tfhe/src/integer/gpu/zk/mod.rs | 9 +- tfhe/src/shortint/ciphertext/compact_list.rs | 4 + tfhe/src/strings/ciphertext.rs | 54 ++- tfhe/src/zk/mod.rs | 2 +- utils/tfhe-versionable/src/lib.rs | 5 +- 16 files changed, 563 insertions(+), 141 deletions(-) diff --git a/tfhe/src/high_level_api/compact_list.rs b/tfhe/src/high_level_api/compact_list.rs index df890a4ce..2ab57a869 100644 --- a/tfhe/src/high_level_api/compact_list.rs +++ b/tfhe/src/high_level_api/compact_list.rs @@ -45,7 +45,7 @@ impl crate::FheTypes { Some(match data_kind { DataKind::Unsigned(n) => { let num_bits_per_block = message_modulus.0.ilog2() as usize; - let num_bits = n * num_bits_per_block; + let num_bits = n.get() * num_bits_per_block; match num_bits { 2 => Self::Uint2, 4 => Self::Uint4, @@ -93,7 +93,7 @@ impl crate::FheTypes { } DataKind::Signed(n) => { let num_bits_per_block = message_modulus.0.ilog2() as usize; - let num_bits = n * num_bits_per_block; + let num_bits = n.get() * num_bits_per_block; match num_bits { 2 => Self::Int2, 4 => Self::Int4, diff --git a/tfhe/src/high_level_api/compressed_ciphertext_list.rs b/tfhe/src/high_level_api/compressed_ciphertext_list.rs index 58ab6e1ef..2b16a4633 100644 --- a/tfhe/src/high_level_api/compressed_ciphertext_list.rs +++ b/tfhe/src/high_level_api/compressed_ciphertext_list.rs @@ -1,3 +1,4 @@ +use std::num::NonZero; use tfhe_versionable::{Unversionize, UnversionizeError, Versionize, VersionizeOwned}; use super::details::MaybeCloned; @@ -40,14 +41,18 @@ impl HlCompressible for FheUint { match self.ciphertext { crate::high_level_api::integers::unsigned::RadixCiphertext::Cpu(cpu_radix) => { let blocks = cpu_radix.blocks; - let kind = DataKind::Unsigned(blocks.len()); - messages.push((ToBeCompressed::Cpu(blocks), kind)); + if let Some(n) = NonZero::new(blocks.len()) { + let kind = DataKind::Unsigned(n); + messages.push((ToBeCompressed::Cpu(blocks), kind)); + } } #[cfg(feature = "gpu")] crate::high_level_api::integers::unsigned::RadixCiphertext::Cuda(gpu_radix) => { let blocks = gpu_radix.ciphertext; - let kind = DataKind::Unsigned(blocks.info.blocks.len()); - messages.push((ToBeCompressed::Cuda(blocks), kind)); + if let Some(n) = NonZero::new(blocks.info.blocks.len()) { + let kind = DataKind::Unsigned(n); + messages.push((ToBeCompressed::Cuda(blocks), kind)); + } } #[cfg(feature = "hpu")] crate::high_level_api::integers::unsigned::RadixCiphertext::Hpu(_) => { @@ -61,14 +66,18 @@ impl HlCompressible for FheInt { match self.ciphertext { crate::high_level_api::integers::signed::SignedRadixCiphertext::Cpu(cpu_radix) => { let blocks = cpu_radix.blocks; - let kind = DataKind::Signed(blocks.len()); - messages.push((ToBeCompressed::Cpu(blocks), kind)); + if let Some(n) = NonZero::new(blocks.len()) { + let kind = DataKind::Signed(n); + messages.push((ToBeCompressed::Cpu(blocks), kind)); + } } #[cfg(feature = "gpu")] crate::high_level_api::integers::signed::SignedRadixCiphertext::Cuda(gpu_radix) => { let blocks = gpu_radix.ciphertext; - let kind = DataKind::Signed(blocks.info.blocks.len()); - messages.push((ToBeCompressed::Cuda(blocks), kind)); + if let Some(n) = NonZero::new(blocks.info.blocks.len()) { + let kind = DataKind::Signed(n); + messages.push((ToBeCompressed::Cuda(blocks), kind)); + } } } } @@ -646,7 +655,7 @@ pub mod gpu { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind { + ) -> Option { self.ciphertext .into_gpu(streams) .compress_into(messages, streams) @@ -658,7 +667,7 @@ pub mod gpu { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind { + ) -> Option { self.ciphertext .into_gpu(streams) .compress_into(messages, streams) @@ -670,7 +679,7 @@ pub mod gpu { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind { + ) -> Option { self.ciphertext .into_gpu(streams) .compress_into(messages, streams) diff --git a/tfhe/src/high_level_api/compressed_noise_squashed_ciphertext_list.rs b/tfhe/src/high_level_api/compressed_noise_squashed_ciphertext_list.rs index 5ded4dedc..98ac385d9 100644 --- a/tfhe/src/high_level_api/compressed_noise_squashed_ciphertext_list.rs +++ b/tfhe/src/high_level_api/compressed_noise_squashed_ciphertext_list.rs @@ -15,6 +15,7 @@ use crate::named::Named; use crate::shortint::ciphertext::SquashedNoiseCiphertext; use crate::{SquashedNoiseFheBool, SquashedNoiseFheInt, SquashedNoiseFheUint, Tag, Versionize}; use serde::{Deserialize, Serialize}; +use std::num::NonZero; use tfhe_versionable::{Unversionize, UnversionizeError, VersionizeOwned}; pub(in crate::high_level_api) enum InnerCompressedSquashedNoiseCiphertextList { @@ -223,7 +224,10 @@ impl SquashedNoiseExpandable for SquashedNoiseFheUint { ) }) } else { - Err(create_error_message(DataKind::Unsigned(0), kind)) + Err(create_error_message( + DataKind::Unsigned(NonZero::new(1).unwrap()), + kind, + )) } } } @@ -242,7 +246,10 @@ impl SquashedNoiseExpandable for SquashedNoiseFheInt { ) }) } else { - Err(create_error_message(DataKind::Signed(0), kind)) + Err(create_error_message( + DataKind::Signed(NonZero::new(1).unwrap()), + kind, + )) } } } @@ -279,11 +286,14 @@ impl HlSquashedNoiseCompressible for SquashedNoiseFheUint { fn compress_into(self, messages: &mut Vec<(private::SquashedNoiseToBeCompressed, DataKind)>) { match self.inner { InnerSquashedNoiseRadixCiphertext::Cpu(cpu_ct) => { - let kind = DataKind::Unsigned(cpu_ct.original_block_count); - messages.push(( - private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks), - kind, - )) + if cpu_ct.original_block_count != 0 { + let kind = + DataKind::Unsigned(NonZero::new(cpu_ct.original_block_count).unwrap()); + messages.push(( + private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks), + kind, + )) + } } } } @@ -293,11 +303,13 @@ impl HlSquashedNoiseCompressible for SquashedNoiseFheInt { fn compress_into(self, messages: &mut Vec<(private::SquashedNoiseToBeCompressed, DataKind)>) { match self.inner { InnerSquashedNoiseSignedRadixCiphertext::Cpu(cpu_ct) => { - let kind = DataKind::Signed(cpu_ct.original_block_count); - messages.push(( - private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks), - kind, - )) + if cpu_ct.original_block_count() != 0 { + let kind = DataKind::Signed(NonZero::new(cpu_ct.original_block_count).unwrap()); + messages.push(( + private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks), + kind, + )) + } } } } diff --git a/tfhe/src/high_level_api/strings/ascii/mod.rs b/tfhe/src/high_level_api/strings/ascii/mod.rs index 5a8abbd92..7d08db599 100644 --- a/tfhe/src/high_level_api/strings/ascii/mod.rs +++ b/tfhe/src/high_level_api/strings/ascii/mod.rs @@ -432,10 +432,14 @@ impl crate::HlCompressible for FheAsciiString { AsciiDevice::Cpu(fhe_string) => { let mut blocks = vec![]; let data_kind = fhe_string.compress_into(&mut blocks); - messages.push(( - crate::high_level_api::compressed_ciphertext_list::ToBeCompressed::Cpu(blocks), - data_kind, - )); + if let Some(data_kind) = data_kind { + messages.push(( + crate::high_level_api::compressed_ciphertext_list::ToBeCompressed::Cpu( + blocks, + ), + data_kind, + )); + } } } } diff --git a/tfhe/src/integer/backward_compatibility/ciphertext/mod.rs b/tfhe/src/integer/backward_compatibility/ciphertext/mod.rs index c5072d1c8..f4e01140b 100644 --- a/tfhe/src/integer/backward_compatibility/ciphertext/mod.rs +++ b/tfhe/src/integer/backward_compatibility/ciphertext/mod.rs @@ -1,7 +1,3 @@ -use std::convert::Infallible; - -use tfhe_versionable::{Upgrade, Version, VersionsDispatch}; - use crate::integer::ciphertext::{ BaseCrtCiphertext, BaseRadixCiphertext, BaseSignedRadixCiphertext, CompactCiphertextList, CompressedCiphertextList, CompressedModulusSwitchedRadixCiphertext, @@ -13,6 +9,9 @@ use crate::integer::BooleanBlock; #[cfg(feature = "zk-pok")] use crate::integer::ProvenCompactCiphertextList; use crate::shortint::ciphertext::CompressedModulusSwitchedCiphertext; +use std::convert::Infallible; +use std::num::NonZero; +use tfhe_versionable::{Upgrade, Version, VersionsDispatch}; #[derive(VersionsDispatch)] pub enum BaseRadixCiphertextVersions { @@ -44,7 +43,10 @@ impl Upgrade for CompactCiphertextListV0 { // Since we can't guess the type of data here, we set them by default as unsigned integer. // Since it this data comes from 0.6, if it is included in a homogeneous compact list it // will be converted to the right type at expand time. - let info = vec![DataKind::Unsigned(self.num_blocks_per_integer); radix_count]; + + let info = NonZero::new(self.num_blocks_per_integer) + .map(|n| vec![DataKind::Unsigned(n); radix_count]) + .unwrap_or_default(); Ok(CompactCiphertextList::from_raw_parts(self.ct_list, info)) } @@ -62,9 +64,40 @@ pub enum ProvenCompactCiphertextListVersions { V0(ProvenCompactCiphertextList), } +#[derive(Version)] +pub enum DataKindV0 { + /// The held value is a number of radix blocks. + Unsigned(usize), + /// The held value is a number of radix blocks. + Signed(usize), + Boolean, + String { + n_chars: u32, + padded: bool, + }, +} + #[derive(VersionsDispatch)] pub enum DataKindVersions { - V0(DataKind), + V0(DataKindV0), + V1(DataKind), +} + +impl Upgrade for DataKindV0 { + type Error = crate::Error; + + fn upgrade(self) -> Result { + match self { + Self::Unsigned(n) => NonZero::new(n) + .ok_or_else(|| crate::error!("DataKind::Unsigned requires non-zero block count")) + .map(DataKind::Unsigned), + Self::Signed(n) => NonZero::new(n) + .ok_or_else(|| crate::error!("DataKind::Signed requires non-zero block count")) + .map(DataKind::Signed), + Self::Boolean => Ok(DataKind::Boolean), + Self::String { n_chars, padded } => Ok(DataKind::String { n_chars, padded }), + } + } } #[derive(VersionsDispatch)] diff --git a/tfhe/src/integer/ciphertext/compact_list.rs b/tfhe/src/integer/ciphertext/compact_list.rs index d643a54cd..4b4704120 100644 --- a/tfhe/src/integer/ciphertext/compact_list.rs +++ b/tfhe/src/integer/ciphertext/compact_list.rs @@ -27,6 +27,7 @@ use crate::zk::{ CompactPkeCrs, CompactPkeProofConformanceParams, ZkComputeLoad, ZkPkeV2HashMode, ZkVerificationOutcome, }; +use std::num::NonZero; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -110,7 +111,7 @@ pub trait Compactable { messages: &mut Vec, message_modulus: MessageModulus, num_blocks: Option, - ) -> DataKind; + ) -> Option; } impl Compactable for bool { @@ -119,9 +120,9 @@ impl Compactable for bool { messages: &mut Vec, _message_modulus: MessageModulus, _num_blocks: Option, - ) -> DataKind { + ) -> Option { messages.push(self as u64); - DataKind::Boolean + Some(DataKind::Boolean) } } @@ -134,18 +135,19 @@ where messages: &mut Vec, message_modulus: MessageModulus, num_blocks: Option, - ) -> DataKind { + ) -> Option { let num_blocks = num_blocks.unwrap_or_else(|| T::BITS.div_ceil(message_modulus.0.ilog2() as usize)); - let decomposer = create_clear_radix_block_iterator(self, message_modulus, num_blocks); + let num_blocks = NonZero::new(num_blocks)?; + let decomposer = create_clear_radix_block_iterator(self, message_modulus, num_blocks.get()); messages.extend(decomposer); // This works because rust always uses two's complement let is_signed = (T::ONE << (T::BITS - 1)) < T::ZERO; if is_signed { - DataKind::Signed(num_blocks) + Some(DataKind::Signed(num_blocks)) } else { - DataKind::Unsigned(num_blocks) + Some(DataKind::Unsigned(num_blocks)) } } } @@ -165,18 +167,44 @@ impl CompactCiphertextListBuilder { } } + /// Pushes Some(kind), and checks that the current block count + /// is coherent with the pushed kind + /// + /// This is to be called after `Compactable::compact_into` + /// + /// `count_before` block count before calling Compactable::compact_into + /// `maybe_kind`: the kind returned by the Compactable::compact_into call + fn push_and_check_kind_coherence( + &mut self, + count_before: usize, + maybe_kind: Option, + ) -> Result<(), ()> { + let added_blocks = match maybe_kind { + Some(kind) => { + let msg_modulus = self.pk.key.message_modulus(); + self.info.push(kind); + kind.num_blocks(msg_modulus) + } + None => 0, + }; + + if self.messages.len() == count_before + added_blocks { + Ok(()) + } else { + Err(()) + } + } + pub fn push(&mut self, data: T) -> &mut Self where T: Compactable, { let n = self.messages.len(); let msg_modulus = self.pk.key.message_modulus(); - let kind = data.compact_into(&mut self.messages, msg_modulus, None); - assert_eq!(n + kind.num_blocks(msg_modulus), self.messages.len()); + let maybe_kind = data.compact_into(&mut self.messages, msg_modulus, None); - if kind.num_blocks(msg_modulus) != 0 { - self.info.push(kind); - } + self.push_and_check_kind_coherence(n, maybe_kind) + .expect("Internal error: non coherent block count after push"); self } @@ -191,10 +219,11 @@ impl CompactCiphertextListBuilder { } let n = self.messages.len(); - let msg_mod = self.pk.key.message_modulus(); - let kind = data.compact_into(&mut self.messages, msg_mod, Some(num_blocks)); - assert_eq!(n + kind.num_blocks(msg_mod), self.messages.len()); - self.info.push(kind); + let msg_modulus = self.pk.key.message_modulus(); + let maybe_kind = data.compact_into(&mut self.messages, msg_modulus, Some(num_blocks)); + + self.push_and_check_kind_coherence(n, maybe_kind) + .expect("Internal error: non coherent block count after push"); self } @@ -741,12 +770,7 @@ fn expansion_helper( impl CompactCiphertextList { pub fn is_packed(&self) -> bool { - self.ct_list.degree.get() - > self - .ct_list - .message_modulus - .corresponding_max_degree() - .get() + self.ct_list.is_packed() } pub fn needs_casting(&self) -> bool { @@ -853,7 +877,7 @@ impl CompactCiphertextList { /// assert_eq!(-1i8, sanity_decrypted); /// /// compact_ct - /// .reinterpret_data(&[DataKind::Unsigned(num_blocks)]) + /// .reinterpret_data(&[DataKind::Unsigned(num_blocks.try_into().unwrap())]) /// .unwrap(); /// /// let expander = compact_ct @@ -892,7 +916,7 @@ impl CompactCiphertextList { } pub fn ciphertext_count(&self) -> usize { - self.info.len() + self.len() } pub fn expand( @@ -983,6 +1007,13 @@ impl ProvenCompactCiphertextList { metadata: &[u8], expansion_mode: IntegerCompactCiphertextListExpansionMode<'_>, ) -> crate::Result { + if self.is_empty() { + if self.verify(crs, public_key, metadata) == ZkVerificationOutcome::Invalid { + return Err(crate::ErrorKind::InvalidZkProof.into()); + } + return Ok(CompactCiphertextListExpander::new(vec![], vec![])); + } + let is_packed = self.is_packed(); // Type annotation needed rust is not able to coerce the type on its own, also forces us to @@ -1038,12 +1069,11 @@ impl ProvenCompactCiphertextList { } pub fn is_packed(&self) -> bool { - self.ct_list.proved_lists[0].0.degree.get() - > self.ct_list.proved_lists[0] - .0 - .message_modulus - .corresponding_max_degree() - .get() + if self.is_empty() { + return false; + } + + self.ct_list.proved_lists[0].0.is_packed() } pub fn needs_casting(&self) -> bool { @@ -1136,11 +1166,25 @@ impl ParameterSetConformant for ProvenCompactCiphertextList { fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool { let Self { ct_list, info } = self; + let is_packed = self.is_packed(); + + let all_have_same_packing = ct_list + .proved_lists + .iter() + .all(|(list, _)| list.is_packed() == is_packed); + + if !all_have_same_packing { + return false; + } + let total_expected_num_blocks: usize = info .iter() .map(|a| a.num_blocks(self.message_modulus())) .sum(); + let total_expected_lwe_count = + total_expected_num_blocks.div_ceil(if is_packed { 2 } else { 1 }); + let a = ProvenCompactCiphertextListConformanceParams { expansion_kind: parameter_set.expansion_kind, encryption_lwe_dimension: parameter_set.encryption_lwe_dimension, @@ -1149,7 +1193,7 @@ impl ParameterSetConformant for ProvenCompactCiphertextList { ciphertext_modulus: parameter_set.ciphertext_modulus, max_lwe_count_per_compact_list: parameter_set.max_elements_per_compact_list, // packing by 2 - total_expected_lwe_count: total_expected_num_blocks.div_ceil(2), + total_expected_lwe_count, zk_conformance_params: parameter_set.zk_conformance_params, }; @@ -1159,7 +1203,7 @@ impl ParameterSetConformant for ProvenCompactCiphertextList { #[cfg(feature = "zk-pok")] #[cfg(test)] -mod tests { +mod zk_pok_tests { // Test utils for tests here impl ProvenCompactCiphertextList { /// For testing and creating potentially invalid lists @@ -1169,13 +1213,17 @@ mod tests { } use super::{DataKind, ProvenCompactCiphertextList}; + use crate::conformance::ParameterSetConformant; use crate::core_crypto::prelude::LweCiphertextCount; - use crate::integer::ciphertext::CompactCiphertextList; + use crate::integer::ciphertext::{ + CompactCiphertextList, IntegerProvenCompactCiphertextListConformanceParams, + }; use crate::integer::key_switching_key::KeySwitchingKey; use crate::integer::parameters::IntegerCompactCiphertextListExpansionMode; use crate::integer::{ BooleanBlock, ClientKey, CompactPrivateKey, CompactPublicKey, RadixCiphertext, ServerKey, }; + use crate::shortint::ciphertext::Degree; use crate::shortint::parameters::test_params::{ TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV1, TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV1, @@ -1185,7 +1233,7 @@ mod tests { PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128, }; - use crate::zk::{CompactPkeCrs, ZkComputeLoad}; + use crate::zk::{CompactPkeCrs, ZkComputeLoad, ZkVerificationOutcome}; use rand::random; #[test] @@ -1250,6 +1298,240 @@ mod tests { } } + #[test] + fn test_empty_list() { + let pke_params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let ksk_params = PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let fhe_params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + + let metadata = [b'i', b'n', b't', b'e', b'g', b'e', b'r']; + + let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(512)).unwrap(); + let cks = ClientKey::new(fhe_params); + let sk = ServerKey::new_radix_server_key(&cks); + let compact_private_key = CompactPrivateKey::new(pke_params); + let ksk = KeySwitchingKey::new((&compact_private_key, None), (&cks, &sk), ksk_params); + let pk = CompactPublicKey::new(&compact_private_key); + + // Test by pushing with zero blocks + { + let proven_ct = CompactCiphertextList::builder(&pk) + .push_with_num_blocks(1u8, 0) + .push_with_num_blocks(-1i8, 0) + .build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof) + .unwrap(); + + assert!(proven_ct.is_empty()); + assert_eq!(proven_ct.len(), 0); + assert_eq!( + proven_ct.verify(&crs, &pk, &metadata), + ZkVerificationOutcome::Valid + ); + assert!(matches!( + proven_ct.verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk.as_view()), + ), + Ok(vec) if vec.is_empty() + )); + } + + // Test by pushing with nothing + { + let proven_ct = CompactCiphertextList::builder(&pk) + .build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof) + .unwrap(); + + assert!(proven_ct.is_empty()); + assert_eq!(proven_ct.len(), 0); + assert_eq!( + proven_ct.verify(&crs, &pk, &metadata), + ZkVerificationOutcome::Valid + ); + assert!(matches!( + proven_ct.verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk.as_view()), + ), + Ok(vec) if vec.is_empty() + )); + } + } + + /// In this test we check the behavior of the proven list when the info vec + /// is modified + #[test] + fn test_attack_list_info() { + let pke_params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let ksk_params = PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let fhe_params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + + let metadata = [b'i', b'n', b't', b'e', b'g', b'e', b'r']; + + let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(2)).unwrap(); + let cks = ClientKey::new(fhe_params); + let sk = ServerKey::new_radix_server_key(&cks); + let compact_private_key = CompactPrivateKey::new(pke_params); + let ksk = KeySwitchingKey::new((&compact_private_key, None), (&cks, &sk), ksk_params); + let pk = CompactPublicKey::new(&compact_private_key); + + let conformance_params = + IntegerProvenCompactCiphertextListConformanceParams::from_crs_and_parameters( + pke_params, &crs, + ); + + let mut proven_ct = CompactCiphertextList::builder(&pk) + .push_with_num_blocks(1u8, 4) + .push_with_num_blocks(-1i8, 4) + .build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof) + .unwrap(); + + assert_eq!(proven_ct.len(), 2); + assert!(!proven_ct.is_empty()); + assert_eq!( + proven_ct.info, + vec![ + DataKind::Unsigned(4.try_into().unwrap()), + DataKind::Signed(4.try_into().unwrap()) + ] + ); + assert_eq!(proven_ct.ct_list.proved_lists.len(), 2); + assert!(proven_ct.is_conformant(&conformance_params)); + + // Change the info vec, conformance should no longer work + let saved_info = std::mem::take(&mut proven_ct.info); + assert!(!proven_ct.is_conformant(&conformance_params)); + assert!(proven_ct.is_empty()); + assert_eq!( + proven_ct.verify(&crs, &pk, &metadata), + ZkVerificationOutcome::Valid + ); + assert!(matches!( + proven_ct.verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk. + as_view()), + ), + Ok(vec) if vec.is_empty() + )); + + // The info vec will still not be coherent (block number wise) + // so conformance fails, we still test verify_and_expand to know its + // behavior + proven_ct.info = vec![DataKind::Signed(4.try_into().unwrap())]; + assert!(!proven_ct.is_conformant(&conformance_params)); + assert!(!proven_ct.is_empty()); + assert!(proven_ct.is_packed()); + assert_eq!( + proven_ct.verify(&crs, &pk, &metadata), + ZkVerificationOutcome::Valid + ); + assert!(proven_ct + .verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk.as_view()) + ) + .is_err()); + + // The info vec will be coherent (block number wise) + // so conformance passes. However, the info metadata is different from + // what it was originally + proven_ct + .info + .push(DataKind::Unsigned(4.try_into().unwrap())); + assert_ne!(proven_ct.info, saved_info); + assert!(proven_ct.is_conformant(&conformance_params)); + assert!(!proven_ct.is_empty()); + assert!(proven_ct.is_packed()); + assert_eq!( + proven_ct.verify(&crs, &pk, &metadata), + ZkVerificationOutcome::Valid + ); + assert!(proven_ct + .verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk.as_view()) + ) + .is_ok()); + + // The info vec now has more entry than there are blocks + // so conformance fails. + proven_ct.info.push(DataKind::Boolean); + assert!(!proven_ct.is_conformant(&conformance_params)); + assert!(!proven_ct.is_empty()); + assert!(proven_ct.is_packed()); + assert_eq!( + proven_ct.verify(&crs, &pk, &metadata), + ZkVerificationOutcome::Valid + ); + assert!(proven_ct + .verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk.as_view()) + ) + .is_err()); + } + + #[test] + fn test_attack_proven_list_metadata() { + let pke_params = PARAM_PKE_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let ksk_params = PARAM_KEYSWITCH_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + let fhe_params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; + + let metadata = [b'i', b'n', b't', b'e', b'g', b'e', b'r']; + + let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(2)).unwrap(); + let cks = ClientKey::new(fhe_params); + let sk = ServerKey::new_radix_server_key(&cks); + let compact_private_key = CompactPrivateKey::new(pke_params); + let ksk = KeySwitchingKey::new((&compact_private_key, None), (&cks, &sk), ksk_params); + let pk = CompactPublicKey::new(&compact_private_key); + + let conformance_params = + IntegerProvenCompactCiphertextListConformanceParams::from_crs_and_parameters( + pke_params, &crs, + ); + + let mut proven_ct = CompactCiphertextList::builder(&pk) + .push_with_num_blocks(1u8, 4) + .push_with_num_blocks(-1i8, 4) + .build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof) + .unwrap(); + + assert!(proven_ct.is_conformant(&conformance_params)); + assert_eq!(proven_ct.len(), 2); + assert!(proven_ct.is_packed()); + assert_eq!(proven_ct.ct_list.proved_lists.len(), 2); + assert!(proven_ct.ct_list.proved_lists[0].0.is_packed()); + assert!(proven_ct.ct_list.proved_lists[1].0.is_packed()); + + proven_ct.ct_list.proved_lists[0].0.degree = Degree::new(0); + assert!(!proven_ct.is_packed()); + assert_eq!(proven_ct.ct_list.proved_lists.len(), 2); + assert!(!proven_ct.ct_list.proved_lists[0].0.is_packed()); + assert!(proven_ct.ct_list.proved_lists[1].0.is_packed()); + assert!(!proven_ct.is_conformant(&conformance_params)); + let expander = proven_ct.verify_and_expand( + &crs, + &pk, + &metadata, + IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(ksk.as_view()), + ); + assert!(expander.is_err()); + } + /// Test a compact list encryption proven with the v1 zk scheme #[test] fn test_zkv1_compact_ciphertext_list_encryption_ci_run_filter() { @@ -1427,7 +1709,7 @@ mod tests { let map_to_fake_boolean = random::() % 2 == 1; if map_to_fake_boolean { if curr_block_count != 0 { - new_infos.push(DataKind::Unsigned(curr_block_count)); + new_infos.push(DataKind::Unsigned(curr_block_count.try_into().unwrap())); curr_block_count = 0; } new_infos.push(DataKind::Boolean); @@ -1436,7 +1718,7 @@ mod tests { } } if curr_block_count != 0 { - new_infos.push(DataKind::Unsigned(curr_block_count)); + new_infos.push(DataKind::Unsigned(curr_block_count.try_into().unwrap())); } assert_eq!( diff --git a/tfhe/src/integer/ciphertext/compressed_ciphertext_list.rs b/tfhe/src/integer/ciphertext/compressed_ciphertext_list.rs index d0f30b82e..1e26cceca 100644 --- a/tfhe/src/integer/ciphertext/compressed_ciphertext_list.rs +++ b/tfhe/src/integer/ciphertext/compressed_ciphertext_list.rs @@ -6,40 +6,41 @@ use crate::shortint::ciphertext::CompressedCiphertextList as ShortintCompressedC use crate::shortint::Ciphertext; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use std::num::NonZero; use tfhe_versionable::Versionize; pub trait Compressible { - fn compress_into(self, messages: &mut Vec) -> DataKind; + fn compress_into(self, messages: &mut Vec) -> Option; } impl Compressible for BooleanBlock { - fn compress_into(self, messages: &mut Vec) -> DataKind { + fn compress_into(self, messages: &mut Vec) -> Option { messages.push(self.0); - DataKind::Boolean + Some(DataKind::Boolean) } } impl Compressible for RadixCiphertext { - fn compress_into(self, messages: &mut Vec) -> DataKind { + fn compress_into(self, messages: &mut Vec) -> Option { let num_blocks = self.blocks.len(); for block in self.blocks { messages.push(block); } - DataKind::Unsigned(num_blocks) + NonZero::new(num_blocks).map(DataKind::Unsigned) } } impl Compressible for SignedRadixCiphertext { - fn compress_into(self, messages: &mut Vec) -> DataKind { + fn compress_into(self, messages: &mut Vec) -> Option { let num_blocks = self.blocks.len(); for block in self.blocks { messages.push(block); } - DataKind::Signed(num_blocks) + NonZero::new(num_blocks).map(DataKind::Signed) } } @@ -63,12 +64,38 @@ impl CompressedCiphertextListBuilder { T: Compressible, { let n = self.ciphertexts.len(); - let kind = data.compress_into(&mut self.ciphertexts); - let num_blocks = self - .ciphertexts - .last() - .map_or(0, |ct| kind.num_blocks(ct.message_modulus)); + let maybe_kind = data.compress_into(&mut self.ciphertexts); + + let Some(modulus) = self.ciphertexts.last().map(|ct| ct.message_modulus) else { + // This means the list of blocks is still empty, so we assert the kind is None + // i.e no type pushed, except for strings as we allow empty strings + if matches!(maybe_kind, Some(DataKind::String { .. })) { + self.info.push(maybe_kind.unwrap()); + } else { + assert!( + maybe_kind.is_none(), + "Internal error: Incoherent block count with regard to kind" + ); + } + + return self; + }; + + let Some(kind) = maybe_kind else { + assert_eq!( + n, + self.ciphertexts.len(), + "Internal error: Incoherent block count with regard to kind" + ); + return self; + }; + + let num_blocks = kind.num_blocks(modulus); + + // Check that the number of blocks that were added matches the + // number of blocks advertised by the DataKind assert_eq!(n + num_blocks, self.ciphertexts.len()); + self.info.push(kind); self } diff --git a/tfhe/src/integer/ciphertext/compressed_noise_squashed_ciphertext_list.rs b/tfhe/src/integer/ciphertext/compressed_noise_squashed_ciphertext_list.rs index 08c66c0a0..895089b2d 100644 --- a/tfhe/src/integer/ciphertext/compressed_noise_squashed_ciphertext_list.rs +++ b/tfhe/src/integer/ciphertext/compressed_noise_squashed_ciphertext_list.rs @@ -22,6 +22,7 @@ use crate::shortint::list_compression::{ }; use crate::shortint::parameters::NoiseSquashingCompressionParameters; use crate::Versionize; +use std::num::NonZero; use crate::integer::backward_compatibility::list_compression::NoiseSquashingCompressionKeyVersions; @@ -228,27 +229,27 @@ mod sealed { } pub trait SquashedNoiseCompressible: sealed::Sealed { - fn compress_into(self, messages: &mut Vec) -> DataKind; + fn compress_into(self, messages: &mut Vec) -> Option; } impl SquashedNoiseCompressible for SquashedNoiseRadixCiphertext { - fn compress_into(mut self, messages: &mut Vec) -> DataKind { + fn compress_into(mut self, messages: &mut Vec) -> Option { messages.append(&mut self.packed_blocks); - DataKind::Unsigned(self.original_block_count) + NonZero::new(self.original_block_count).map(DataKind::Unsigned) } } impl SquashedNoiseCompressible for SquashedNoiseSignedRadixCiphertext { - fn compress_into(mut self, messages: &mut Vec) -> DataKind { + fn compress_into(mut self, messages: &mut Vec) -> Option { messages.append(&mut self.packed_blocks); - DataKind::Signed(self.original_block_count) + NonZero::new(self.original_block_count).map(DataKind::Signed) } } impl SquashedNoiseCompressible for SquashedNoiseBooleanBlock { - fn compress_into(self, messages: &mut Vec) -> DataKind { + fn compress_into(self, messages: &mut Vec) -> Option { messages.push(self.ciphertext); - DataKind::Boolean + Some(DataKind::Boolean) } } @@ -283,10 +284,13 @@ impl SquashedNoiseExpandable for SquashedNoiseRadixCiphertext { if let DataKind::Unsigned(block_count) = kind { Ok(Self { packed_blocks: blocks, - original_block_count: block_count, + original_block_count: block_count.get(), }) } else { - Err(create_error_message(DataKind::Unsigned(0), kind)) + Err(create_error_message( + DataKind::Unsigned(1.try_into().unwrap()), + kind, + )) } } } @@ -299,10 +303,13 @@ impl SquashedNoiseExpandable for SquashedNoiseSignedRadixCiphertext { if let DataKind::Signed(block_count) = kind { Ok(Self { packed_blocks: blocks, - original_block_count: block_count, + original_block_count: block_count.get(), }) } else { - Err(create_error_message(DataKind::Signed(0), kind)) + Err(create_error_message( + DataKind::Signed(1.try_into().unwrap()), + kind, + )) } } } @@ -344,15 +351,29 @@ impl CompressedSquashedNoiseCiphertextListBuilder { pub fn push(&mut self, value: impl SquashedNoiseCompressible) -> &mut Self { let n = self.list.len(); - let kind = value.compress_into(&mut self.list); + let maybe_kind = value.compress_into(&mut self.list); + + let Some(modulus) = self.list.last().map(|ct| ct.message_modulus()) else { + assert!( + maybe_kind.is_none(), + "Internal error: Incoherent block count with regard to kind" + ); + return self; + }; + + let Some(kind) = maybe_kind else { + assert_eq!( + n, + self.list.len(), + "Internal error: Incoherent block count with regard to kind" + ); + return self; + }; + + let num_blocks = kind.num_blocks(modulus).div_ceil(2); // Because blocks are packed when noise squashed // Check that the number of blocks that were added matches the // number of blocks advertised by the DataKind - let num_blocks = self - .list - .last() - .map_or(0, |ct| kind.num_blocks(ct.message_modulus())) - .div_ceil(2); // Because blocks are packed when noise squashed assert_eq!(n + num_blocks, self.list.len()); self.info.push(kind); diff --git a/tfhe/src/integer/ciphertext/utils.rs b/tfhe/src/integer/ciphertext/utils.rs index 5196605f8..8d0273eb3 100644 --- a/tfhe/src/integer/ciphertext/utils.rs +++ b/tfhe/src/integer/ciphertext/utils.rs @@ -2,15 +2,16 @@ use super::{BooleanBlock, IntegerRadixCiphertext}; use crate::integer::backward_compatibility::ciphertext::DataKindVersions; use crate::shortint::{Ciphertext, MessageModulus}; use serde::{Deserialize, Serialize}; +use std::num::NonZeroUsize; use tfhe_versionable::Versionize; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Versionize)] #[versionize(DataKindVersions)] pub enum DataKind { /// The held value is a number of radix blocks. - Unsigned(usize), + Unsigned(NonZeroUsize), /// The held value is a number of radix blocks. - Signed(usize), + Signed(NonZeroUsize), Boolean, String { n_chars: u32, @@ -21,7 +22,7 @@ pub enum DataKind { impl DataKind { pub fn num_blocks(self, message_modulus: MessageModulus) -> usize { match self { - Self::Unsigned(n) | Self::Signed(n) => n, + Self::Unsigned(n) | Self::Signed(n) => n.get(), Self::Boolean => 1, Self::String { n_chars, .. } => { let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); diff --git a/tfhe/src/integer/gpu/ciphertext/compact_list.rs b/tfhe/src/integer/gpu/ciphertext/compact_list.rs index 4646486b3..927d18219 100644 --- a/tfhe/src/integer/gpu/ciphertext/compact_list.rs +++ b/tfhe/src/integer/gpu/ciphertext/compact_list.rs @@ -248,8 +248,8 @@ impl CudaFlattenedVecCompactCiphertextList { .flat_map(|data_kind| { let repetitions = match data_kind { DataKind::Boolean => 1, - DataKind::Signed(x) => *x, - DataKind::Unsigned(x) => *x, + DataKind::Signed(x) => x.get(), + DataKind::Unsigned(x) => x.get(), DataKind::String { .. } => panic!("DataKind not supported on GPUs"), }; std::iter::repeat_n(matches!(data_kind, DataKind::Boolean), repetitions) diff --git a/tfhe/src/integer/gpu/ciphertext/compressed_ciphertext_list.rs b/tfhe/src/integer/gpu/ciphertext/compressed_ciphertext_list.rs index d79c2d957..5bc9a252c 100644 --- a/tfhe/src/integer/gpu/ciphertext/compressed_ciphertext_list.rs +++ b/tfhe/src/integer/gpu/ciphertext/compressed_ciphertext_list.rs @@ -20,6 +20,7 @@ use crate::shortint::parameters::AtomicPatternKind; use crate::shortint::PBSOrder; use itertools::Itertools; use serde::{Deserializer, Serializer}; +use std::num::NonZeroUsize; pub trait CudaExpandable: Sized { fn from_expanded_blocks(blocks: CudaRadixCiphertext, kind: DataKind) -> crate::Result; @@ -447,7 +448,7 @@ pub trait CudaCompressible { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind; + ) -> Option; } impl CudaCompressible for CudaSignedRadixCiphertext { @@ -455,12 +456,15 @@ impl CudaCompressible for CudaSignedRadixCiphertext { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind { + ) -> Option { let x = self.ciphertext.duplicate(streams); let num_blocks = x.d_blocks.lwe_ciphertext_count().0; - messages.push(x); - DataKind::Signed(num_blocks) + let num_blocks = NonZeroUsize::new(num_blocks); + if num_blocks.is_some() { + messages.push(x) + } + num_blocks.map(DataKind::Signed) } } @@ -469,11 +473,11 @@ impl CudaCompressible for CudaBooleanBlock { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind { + ) -> Option { let x = self.0.ciphertext.duplicate(streams); messages.push(x); - DataKind::Boolean + Some(DataKind::Boolean) } } impl CudaCompressible for CudaUnsignedRadixCiphertext { @@ -481,12 +485,15 @@ impl CudaCompressible for CudaUnsignedRadixCiphertext { self, messages: &mut Vec, streams: &CudaStreams, - ) -> DataKind { + ) -> Option { let x = self.ciphertext.duplicate(streams); let num_blocks = x.d_blocks.lwe_ciphertext_count().0; - messages.push(x); - DataKind::Unsigned(num_blocks) + let num_blocks = NonZeroUsize::new(num_blocks); + if num_blocks.is_some() { + messages.push(x) + } + num_blocks.map(DataKind::Unsigned) } } @@ -505,13 +512,9 @@ impl CudaCompressedCiphertextListBuilder { } pub fn push(&mut self, data: T, streams: &CudaStreams) -> &mut Self { - let kind = data.compress_into(&mut self.ciphertexts, streams); - let message_modulus = self.ciphertexts.last().unwrap().info.blocks[0].message_modulus; - - if kind.num_blocks(message_modulus) != 0 { + if let Some(kind) = data.compress_into(&mut self.ciphertexts, streams) { self.info.push(kind); } - self } diff --git a/tfhe/src/integer/gpu/zk/mod.rs b/tfhe/src/integer/gpu/zk/mod.rs index ea4f72e06..3edbaae39 100644 --- a/tfhe/src/integer/gpu/zk/mod.rs +++ b/tfhe/src/integer/gpu/zk/mod.rs @@ -155,6 +155,7 @@ mod tests { ProvenCompactCiphertextList, }; use crate::shortint::parameters::test_params::TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2; + use std::num::NonZero; // TODO test params update for the v1_3 use crate::shortint::parameters::current_params::V1_3_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128; use crate::shortint::parameters::{ @@ -426,8 +427,8 @@ mod tests { for _ in 0..infos_block_count { let map_to_fake_boolean = random::() % 2 == 1; if map_to_fake_boolean { - if curr_block_count != 0 { - new_infos.push(DataKind::Unsigned(curr_block_count)); + if let Some(count) = NonZero::new(curr_block_count) { + new_infos.push(DataKind::Unsigned(count)); curr_block_count = 0; } new_infos.push(DataKind::Boolean); @@ -435,8 +436,8 @@ mod tests { curr_block_count += 1; } } - if curr_block_count != 0 { - new_infos.push(DataKind::Unsigned(curr_block_count)); + if let Some(count) = NonZero::new(curr_block_count) { + new_infos.push(DataKind::Unsigned(count)); } assert_eq!( diff --git a/tfhe/src/shortint/ciphertext/compact_list.rs b/tfhe/src/shortint/ciphertext/compact_list.rs index 5e1472a9f..a6585dac8 100644 --- a/tfhe/src/shortint/ciphertext/compact_list.rs +++ b/tfhe/src/shortint/ciphertext/compact_list.rs @@ -228,4 +228,8 @@ impl CompactCiphertextList { pub fn size_bytes(&self) -> usize { self.ct_list.size_bytes() } + + pub fn is_packed(&self) -> bool { + self.degree.get() > self.message_modulus.corresponding_max_degree().get() + } } diff --git a/tfhe/src/strings/ciphertext.rs b/tfhe/src/strings/ciphertext.rs index 72bd01537..5819b5a8e 100644 --- a/tfhe/src/strings/ciphertext.rs +++ b/tfhe/src/strings/ciphertext.rs @@ -64,7 +64,7 @@ impl Compactable for &ClearString { messages: &mut Vec, message_modulus: MessageModulus, num_blocks: Option, - ) -> crate::integer::ciphertext::DataKind { + ) -> Option { let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); if let Some(n) = num_blocks { @@ -79,7 +79,7 @@ impl Compactable for &ClearString { n_blocks / blocks_per_char as usize }); - // First write the chars we have at hand + // First, write the chars we have at hand let n_real_chars = n_chars.min(self.str().len()); for byte in &self.str.as_bytes()[..n_real_chars] { let mut byte = u64::from(*byte); @@ -95,10 +95,10 @@ impl Compactable for &ClearString { messages.push(0); } - DataKind::String { + Some(DataKind::String { n_chars: n_chars as u32, padded, - } + }) } } @@ -110,13 +110,24 @@ impl crate::integer::ciphertext::CompactCiphertextListBuilder { ) -> &mut Self { let message_modulus = self.pk.key.message_modulus(); let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); + let n = self.messages.len(); - let kind = clear_string.compact_into( - &mut self.messages, - message_modulus, - Some((clear_string.str.len() + padding_count as usize) * blocks_per_char as usize), - ); + let kind = clear_string + .compact_into( + &mut self.messages, + message_modulus, + Some((clear_string.str.len() + padding_count as usize) * blocks_per_char as usize), + ) + .expect("Internal error: compact_into should return a kind"); self.info.push(kind); + + let added_count = kind.num_blocks(message_modulus); + assert_eq!( + n + added_count, + self.messages.len(), + "Internal error: Incoherent number of blocks added" + ); + self } @@ -127,13 +138,24 @@ impl crate::integer::ciphertext::CompactCiphertextListBuilder { ) -> &mut Self { let message_modulus = self.pk.key.message_modulus(); let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); + let n = self.messages.len(); - let kind = clear_string.compact_into( - &mut self.messages, - message_modulus, - Some((size * blocks_per_char) as usize), - ); + let kind = clear_string + .compact_into( + &mut self.messages, + message_modulus, + Some((size * blocks_per_char) as usize), + ) + .expect("Internal error: compact_into should return a kind"); self.info.push(kind); + + let added_count = kind.num_blocks(message_modulus); + assert_eq!( + n + added_count, + self.messages.len(), + "Internal error: Incoherent number of blocks added" + ); + self } } @@ -186,7 +208,7 @@ impl crate::integer::ciphertext::Expandable for FheString { } impl crate::integer::ciphertext::Compressible for FheString { - fn compress_into(self, messages: &mut Vec) -> DataKind { + fn compress_into(self, messages: &mut Vec) -> Option { let n_chars = self.chars().len() as u32; let padded = self.is_padded(); @@ -196,7 +218,7 @@ impl crate::integer::ciphertext::Compressible for FheString { } } - DataKind::String { n_chars, padded } + Some(DataKind::String { n_chars, padded }) } } diff --git a/tfhe/src/zk/mod.rs b/tfhe/src/zk/mod.rs index 4b1b50f12..0e50e8eb7 100644 --- a/tfhe/src/zk/mod.rs +++ b/tfhe/src/zk/mod.rs @@ -318,7 +318,7 @@ impl Named for SerializableCompactPkePublicParams { const NAME: &'static str = ZkCompactPkeV1PublicParams::NAME; } -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum ZkVerificationOutcome { /// The proof and its entity were valid Valid, diff --git a/utils/tfhe-versionable/src/lib.rs b/utils/tfhe-versionable/src/lib.rs index 37c1281b3..46edc7976 100644 --- a/utils/tfhe-versionable/src/lib.rs +++ b/utils/tfhe-versionable/src/lib.rs @@ -18,7 +18,7 @@ use std::convert::Infallible; use std::error::Error; use std::fmt::Display; use std::marker::PhantomData; -use std::num::Wrapping; +use std::num::{NonZero, Wrapping}; use std::sync::Arc; pub use derived_traits::{Version, VersionsDispatch}; @@ -268,6 +268,9 @@ impl_scalar_versionize!(f64); impl_scalar_versionize!(char); +impl_scalar_versionize!(NonZero); +impl_scalar_versionize!(NonZero); + impl Versionize for Wrapping { type Versioned<'vers> = Wrapping>