refactor!: Use NonZero<T> 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.
This commit is contained in:
tmontaigu
2025-08-21 09:49:43 +02:00
parent 0a28488079
commit d389ea67a1
16 changed files with 563 additions and 141 deletions

View File

@@ -45,7 +45,7 @@ impl crate::FheTypes {
Some(match data_kind { Some(match data_kind {
DataKind::Unsigned(n) => { DataKind::Unsigned(n) => {
let num_bits_per_block = message_modulus.0.ilog2() as usize; 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 { match num_bits {
2 => Self::Uint2, 2 => Self::Uint2,
4 => Self::Uint4, 4 => Self::Uint4,
@@ -93,7 +93,7 @@ impl crate::FheTypes {
} }
DataKind::Signed(n) => { DataKind::Signed(n) => {
let num_bits_per_block = message_modulus.0.ilog2() as usize; 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 { match num_bits {
2 => Self::Int2, 2 => Self::Int2,
4 => Self::Int4, 4 => Self::Int4,

View File

@@ -1,3 +1,4 @@
use std::num::NonZero;
use tfhe_versionable::{Unversionize, UnversionizeError, Versionize, VersionizeOwned}; use tfhe_versionable::{Unversionize, UnversionizeError, Versionize, VersionizeOwned};
use super::details::MaybeCloned; use super::details::MaybeCloned;
@@ -40,14 +41,18 @@ impl<Id: FheUintId> HlCompressible for FheUint<Id> {
match self.ciphertext { match self.ciphertext {
crate::high_level_api::integers::unsigned::RadixCiphertext::Cpu(cpu_radix) => { crate::high_level_api::integers::unsigned::RadixCiphertext::Cpu(cpu_radix) => {
let blocks = cpu_radix.blocks; let blocks = cpu_radix.blocks;
let kind = DataKind::Unsigned(blocks.len()); if let Some(n) = NonZero::new(blocks.len()) {
messages.push((ToBeCompressed::Cpu(blocks), kind)); let kind = DataKind::Unsigned(n);
messages.push((ToBeCompressed::Cpu(blocks), kind));
}
} }
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
crate::high_level_api::integers::unsigned::RadixCiphertext::Cuda(gpu_radix) => { crate::high_level_api::integers::unsigned::RadixCiphertext::Cuda(gpu_radix) => {
let blocks = gpu_radix.ciphertext; let blocks = gpu_radix.ciphertext;
let kind = DataKind::Unsigned(blocks.info.blocks.len()); if let Some(n) = NonZero::new(blocks.info.blocks.len()) {
messages.push((ToBeCompressed::Cuda(blocks), kind)); let kind = DataKind::Unsigned(n);
messages.push((ToBeCompressed::Cuda(blocks), kind));
}
} }
#[cfg(feature = "hpu")] #[cfg(feature = "hpu")]
crate::high_level_api::integers::unsigned::RadixCiphertext::Hpu(_) => { crate::high_level_api::integers::unsigned::RadixCiphertext::Hpu(_) => {
@@ -61,14 +66,18 @@ impl<Id: FheIntId> HlCompressible for FheInt<Id> {
match self.ciphertext { match self.ciphertext {
crate::high_level_api::integers::signed::SignedRadixCiphertext::Cpu(cpu_radix) => { crate::high_level_api::integers::signed::SignedRadixCiphertext::Cpu(cpu_radix) => {
let blocks = cpu_radix.blocks; let blocks = cpu_radix.blocks;
let kind = DataKind::Signed(blocks.len()); if let Some(n) = NonZero::new(blocks.len()) {
messages.push((ToBeCompressed::Cpu(blocks), kind)); let kind = DataKind::Signed(n);
messages.push((ToBeCompressed::Cpu(blocks), kind));
}
} }
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
crate::high_level_api::integers::signed::SignedRadixCiphertext::Cuda(gpu_radix) => { crate::high_level_api::integers::signed::SignedRadixCiphertext::Cuda(gpu_radix) => {
let blocks = gpu_radix.ciphertext; let blocks = gpu_radix.ciphertext;
let kind = DataKind::Signed(blocks.info.blocks.len()); if let Some(n) = NonZero::new(blocks.info.blocks.len()) {
messages.push((ToBeCompressed::Cuda(blocks), kind)); let kind = DataKind::Signed(n);
messages.push((ToBeCompressed::Cuda(blocks), kind));
}
} }
} }
} }
@@ -646,7 +655,7 @@ pub mod gpu {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind { ) -> Option<DataKind> {
self.ciphertext self.ciphertext
.into_gpu(streams) .into_gpu(streams)
.compress_into(messages, streams) .compress_into(messages, streams)
@@ -658,7 +667,7 @@ pub mod gpu {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind { ) -> Option<DataKind> {
self.ciphertext self.ciphertext
.into_gpu(streams) .into_gpu(streams)
.compress_into(messages, streams) .compress_into(messages, streams)
@@ -670,7 +679,7 @@ pub mod gpu {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind { ) -> Option<DataKind> {
self.ciphertext self.ciphertext
.into_gpu(streams) .into_gpu(streams)
.compress_into(messages, streams) .compress_into(messages, streams)

View File

@@ -15,6 +15,7 @@ use crate::named::Named;
use crate::shortint::ciphertext::SquashedNoiseCiphertext; use crate::shortint::ciphertext::SquashedNoiseCiphertext;
use crate::{SquashedNoiseFheBool, SquashedNoiseFheInt, SquashedNoiseFheUint, Tag, Versionize}; use crate::{SquashedNoiseFheBool, SquashedNoiseFheInt, SquashedNoiseFheUint, Tag, Versionize};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::num::NonZero;
use tfhe_versionable::{Unversionize, UnversionizeError, VersionizeOwned}; use tfhe_versionable::{Unversionize, UnversionizeError, VersionizeOwned};
pub(in crate::high_level_api) enum InnerCompressedSquashedNoiseCiphertextList { pub(in crate::high_level_api) enum InnerCompressedSquashedNoiseCiphertextList {
@@ -223,7 +224,10 @@ impl SquashedNoiseExpandable for SquashedNoiseFheUint {
) )
}) })
} else { } 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 { } 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)>) { fn compress_into(self, messages: &mut Vec<(private::SquashedNoiseToBeCompressed, DataKind)>) {
match self.inner { match self.inner {
InnerSquashedNoiseRadixCiphertext::Cpu(cpu_ct) => { InnerSquashedNoiseRadixCiphertext::Cpu(cpu_ct) => {
let kind = DataKind::Unsigned(cpu_ct.original_block_count); if cpu_ct.original_block_count != 0 {
messages.push(( let kind =
private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks), DataKind::Unsigned(NonZero::new(cpu_ct.original_block_count).unwrap());
kind, 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)>) { fn compress_into(self, messages: &mut Vec<(private::SquashedNoiseToBeCompressed, DataKind)>) {
match self.inner { match self.inner {
InnerSquashedNoiseSignedRadixCiphertext::Cpu(cpu_ct) => { InnerSquashedNoiseSignedRadixCiphertext::Cpu(cpu_ct) => {
let kind = DataKind::Signed(cpu_ct.original_block_count); if cpu_ct.original_block_count() != 0 {
messages.push(( let kind = DataKind::Signed(NonZero::new(cpu_ct.original_block_count).unwrap());
private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks), messages.push((
kind, private::SquashedNoiseToBeCompressed::Cpu(cpu_ct.packed_blocks),
)) kind,
))
}
} }
} }
} }

View File

@@ -432,10 +432,14 @@ impl crate::HlCompressible for FheAsciiString {
AsciiDevice::Cpu(fhe_string) => { AsciiDevice::Cpu(fhe_string) => {
let mut blocks = vec![]; let mut blocks = vec![];
let data_kind = fhe_string.compress_into(&mut blocks); let data_kind = fhe_string.compress_into(&mut blocks);
messages.push(( if let Some(data_kind) = data_kind {
crate::high_level_api::compressed_ciphertext_list::ToBeCompressed::Cpu(blocks), messages.push((
data_kind, crate::high_level_api::compressed_ciphertext_list::ToBeCompressed::Cpu(
)); blocks,
),
data_kind,
));
}
} }
} }
} }

View File

@@ -1,7 +1,3 @@
use std::convert::Infallible;
use tfhe_versionable::{Upgrade, Version, VersionsDispatch};
use crate::integer::ciphertext::{ use crate::integer::ciphertext::{
BaseCrtCiphertext, BaseRadixCiphertext, BaseSignedRadixCiphertext, CompactCiphertextList, BaseCrtCiphertext, BaseRadixCiphertext, BaseSignedRadixCiphertext, CompactCiphertextList,
CompressedCiphertextList, CompressedModulusSwitchedRadixCiphertext, CompressedCiphertextList, CompressedModulusSwitchedRadixCiphertext,
@@ -13,6 +9,9 @@ use crate::integer::BooleanBlock;
#[cfg(feature = "zk-pok")] #[cfg(feature = "zk-pok")]
use crate::integer::ProvenCompactCiphertextList; use crate::integer::ProvenCompactCiphertextList;
use crate::shortint::ciphertext::CompressedModulusSwitchedCiphertext; use crate::shortint::ciphertext::CompressedModulusSwitchedCiphertext;
use std::convert::Infallible;
use std::num::NonZero;
use tfhe_versionable::{Upgrade, Version, VersionsDispatch};
#[derive(VersionsDispatch)] #[derive(VersionsDispatch)]
pub enum BaseRadixCiphertextVersions<Block> { pub enum BaseRadixCiphertextVersions<Block> {
@@ -44,7 +43,10 @@ impl Upgrade<CompactCiphertextList> for CompactCiphertextListV0 {
// Since we can't guess the type of data here, we set them by default as unsigned integer. // 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 // 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. // 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)) Ok(CompactCiphertextList::from_raw_parts(self.ct_list, info))
} }
@@ -62,9 +64,40 @@ pub enum ProvenCompactCiphertextListVersions {
V0(ProvenCompactCiphertextList), 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)] #[derive(VersionsDispatch)]
pub enum DataKindVersions { pub enum DataKindVersions {
V0(DataKind), V0(DataKindV0),
V1(DataKind),
}
impl Upgrade<DataKind> for DataKindV0 {
type Error = crate::Error;
fn upgrade(self) -> Result<DataKind, Self::Error> {
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)] #[derive(VersionsDispatch)]

View File

@@ -27,6 +27,7 @@ use crate::zk::{
CompactPkeCrs, CompactPkeProofConformanceParams, ZkComputeLoad, ZkPkeV2HashMode, CompactPkeCrs, CompactPkeProofConformanceParams, ZkComputeLoad, ZkPkeV2HashMode,
ZkVerificationOutcome, ZkVerificationOutcome,
}; };
use std::num::NonZero;
use rayon::prelude::*; use rayon::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -110,7 +111,7 @@ pub trait Compactable {
messages: &mut Vec<u64>, messages: &mut Vec<u64>,
message_modulus: MessageModulus, message_modulus: MessageModulus,
num_blocks: Option<usize>, num_blocks: Option<usize>,
) -> DataKind; ) -> Option<DataKind>;
} }
impl Compactable for bool { impl Compactable for bool {
@@ -119,9 +120,9 @@ impl Compactable for bool {
messages: &mut Vec<u64>, messages: &mut Vec<u64>,
_message_modulus: MessageModulus, _message_modulus: MessageModulus,
_num_blocks: Option<usize>, _num_blocks: Option<usize>,
) -> DataKind { ) -> Option<DataKind> {
messages.push(self as u64); messages.push(self as u64);
DataKind::Boolean Some(DataKind::Boolean)
} }
} }
@@ -134,18 +135,19 @@ where
messages: &mut Vec<u64>, messages: &mut Vec<u64>,
message_modulus: MessageModulus, message_modulus: MessageModulus,
num_blocks: Option<usize>, num_blocks: Option<usize>,
) -> DataKind { ) -> Option<DataKind> {
let num_blocks = let num_blocks =
num_blocks.unwrap_or_else(|| T::BITS.div_ceil(message_modulus.0.ilog2() as usize)); 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); messages.extend(decomposer);
// This works because rust always uses two's complement // This works because rust always uses two's complement
let is_signed = (T::ONE << (T::BITS - 1)) < T::ZERO; let is_signed = (T::ONE << (T::BITS - 1)) < T::ZERO;
if is_signed { if is_signed {
DataKind::Signed(num_blocks) Some(DataKind::Signed(num_blocks))
} else { } 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<DataKind>,
) -> 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<T>(&mut self, data: T) -> &mut Self pub fn push<T>(&mut self, data: T) -> &mut Self
where where
T: Compactable, T: Compactable,
{ {
let n = self.messages.len(); let n = self.messages.len();
let msg_modulus = self.pk.key.message_modulus(); let msg_modulus = self.pk.key.message_modulus();
let kind = data.compact_into(&mut self.messages, msg_modulus, None); let maybe_kind = data.compact_into(&mut self.messages, msg_modulus, None);
assert_eq!(n + kind.num_blocks(msg_modulus), self.messages.len());
if kind.num_blocks(msg_modulus) != 0 { self.push_and_check_kind_coherence(n, maybe_kind)
self.info.push(kind); .expect("Internal error: non coherent block count after push");
}
self self
} }
@@ -191,10 +219,11 @@ impl CompactCiphertextListBuilder {
} }
let n = self.messages.len(); let n = self.messages.len();
let msg_mod = self.pk.key.message_modulus(); let msg_modulus = self.pk.key.message_modulus();
let kind = data.compact_into(&mut self.messages, msg_mod, Some(num_blocks)); let maybe_kind = data.compact_into(&mut self.messages, msg_modulus, Some(num_blocks));
assert_eq!(n + kind.num_blocks(msg_mod), self.messages.len());
self.info.push(kind); self.push_and_check_kind_coherence(n, maybe_kind)
.expect("Internal error: non coherent block count after push");
self self
} }
@@ -741,12 +770,7 @@ fn expansion_helper<ListType>(
impl CompactCiphertextList { impl CompactCiphertextList {
pub fn is_packed(&self) -> bool { pub fn is_packed(&self) -> bool {
self.ct_list.degree.get() self.ct_list.is_packed()
> self
.ct_list
.message_modulus
.corresponding_max_degree()
.get()
} }
pub fn needs_casting(&self) -> bool { pub fn needs_casting(&self) -> bool {
@@ -853,7 +877,7 @@ impl CompactCiphertextList {
/// assert_eq!(-1i8, sanity_decrypted); /// assert_eq!(-1i8, sanity_decrypted);
/// ///
/// compact_ct /// compact_ct
/// .reinterpret_data(&[DataKind::Unsigned(num_blocks)]) /// .reinterpret_data(&[DataKind::Unsigned(num_blocks.try_into().unwrap())])
/// .unwrap(); /// .unwrap();
/// ///
/// let expander = compact_ct /// let expander = compact_ct
@@ -892,7 +916,7 @@ impl CompactCiphertextList {
} }
pub fn ciphertext_count(&self) -> usize { pub fn ciphertext_count(&self) -> usize {
self.info.len() self.len()
} }
pub fn expand( pub fn expand(
@@ -983,6 +1007,13 @@ impl ProvenCompactCiphertextList {
metadata: &[u8], metadata: &[u8],
expansion_mode: IntegerCompactCiphertextListExpansionMode<'_>, expansion_mode: IntegerCompactCiphertextListExpansionMode<'_>,
) -> crate::Result<CompactCiphertextListExpander> { ) -> crate::Result<CompactCiphertextListExpander> {
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(); let is_packed = self.is_packed();
// Type annotation needed rust is not able to coerce the type on its own, also forces us to // 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 { pub fn is_packed(&self) -> bool {
self.ct_list.proved_lists[0].0.degree.get() if self.is_empty() {
> self.ct_list.proved_lists[0] return false;
.0 }
.message_modulus
.corresponding_max_degree() self.ct_list.proved_lists[0].0.is_packed()
.get()
} }
pub fn needs_casting(&self) -> bool { pub fn needs_casting(&self) -> bool {
@@ -1136,11 +1166,25 @@ impl ParameterSetConformant for ProvenCompactCiphertextList {
fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool { fn is_conformant(&self, parameter_set: &Self::ParameterSet) -> bool {
let Self { ct_list, info } = self; 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 let total_expected_num_blocks: usize = info
.iter() .iter()
.map(|a| a.num_blocks(self.message_modulus())) .map(|a| a.num_blocks(self.message_modulus()))
.sum(); .sum();
let total_expected_lwe_count =
total_expected_num_blocks.div_ceil(if is_packed { 2 } else { 1 });
let a = ProvenCompactCiphertextListConformanceParams { let a = ProvenCompactCiphertextListConformanceParams {
expansion_kind: parameter_set.expansion_kind, expansion_kind: parameter_set.expansion_kind,
encryption_lwe_dimension: parameter_set.encryption_lwe_dimension, encryption_lwe_dimension: parameter_set.encryption_lwe_dimension,
@@ -1149,7 +1193,7 @@ impl ParameterSetConformant for ProvenCompactCiphertextList {
ciphertext_modulus: parameter_set.ciphertext_modulus, ciphertext_modulus: parameter_set.ciphertext_modulus,
max_lwe_count_per_compact_list: parameter_set.max_elements_per_compact_list, max_lwe_count_per_compact_list: parameter_set.max_elements_per_compact_list,
// packing by 2 // 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, zk_conformance_params: parameter_set.zk_conformance_params,
}; };
@@ -1159,7 +1203,7 @@ impl ParameterSetConformant for ProvenCompactCiphertextList {
#[cfg(feature = "zk-pok")] #[cfg(feature = "zk-pok")]
#[cfg(test)] #[cfg(test)]
mod tests { mod zk_pok_tests {
// Test utils for tests here // Test utils for tests here
impl ProvenCompactCiphertextList { impl ProvenCompactCiphertextList {
/// For testing and creating potentially invalid lists /// For testing and creating potentially invalid lists
@@ -1169,13 +1213,17 @@ mod tests {
} }
use super::{DataKind, ProvenCompactCiphertextList}; use super::{DataKind, ProvenCompactCiphertextList};
use crate::conformance::ParameterSetConformant;
use crate::core_crypto::prelude::LweCiphertextCount; 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::key_switching_key::KeySwitchingKey;
use crate::integer::parameters::IntegerCompactCiphertextListExpansionMode; use crate::integer::parameters::IntegerCompactCiphertextListExpansionMode;
use crate::integer::{ use crate::integer::{
BooleanBlock, ClientKey, CompactPrivateKey, CompactPublicKey, RadixCiphertext, ServerKey, BooleanBlock, ClientKey, CompactPrivateKey, CompactPublicKey, RadixCiphertext, ServerKey,
}; };
use crate::shortint::ciphertext::Degree;
use crate::shortint::parameters::test_params::{ use crate::shortint::parameters::test_params::{
TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV1, 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, 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_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
PARAM_PKE_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; use rand::random;
#[test] #[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 a compact list encryption proven with the v1 zk scheme
#[test] #[test]
fn test_zkv1_compact_ciphertext_list_encryption_ci_run_filter() { fn test_zkv1_compact_ciphertext_list_encryption_ci_run_filter() {
@@ -1427,7 +1709,7 @@ mod tests {
let map_to_fake_boolean = random::<u8>() % 2 == 1; let map_to_fake_boolean = random::<u8>() % 2 == 1;
if map_to_fake_boolean { if map_to_fake_boolean {
if curr_block_count != 0 { 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; curr_block_count = 0;
} }
new_infos.push(DataKind::Boolean); new_infos.push(DataKind::Boolean);
@@ -1436,7 +1718,7 @@ mod tests {
} }
} }
if curr_block_count != 0 { 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!( assert_eq!(

View File

@@ -6,40 +6,41 @@ use crate::shortint::ciphertext::CompressedCiphertextList as ShortintCompressedC
use crate::shortint::Ciphertext; use crate::shortint::Ciphertext;
use rayon::prelude::*; use rayon::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::num::NonZero;
use tfhe_versionable::Versionize; use tfhe_versionable::Versionize;
pub trait Compressible { pub trait Compressible {
fn compress_into(self, messages: &mut Vec<Ciphertext>) -> DataKind; fn compress_into(self, messages: &mut Vec<Ciphertext>) -> Option<DataKind>;
} }
impl Compressible for BooleanBlock { impl Compressible for BooleanBlock {
fn compress_into(self, messages: &mut Vec<Ciphertext>) -> DataKind { fn compress_into(self, messages: &mut Vec<Ciphertext>) -> Option<DataKind> {
messages.push(self.0); messages.push(self.0);
DataKind::Boolean Some(DataKind::Boolean)
} }
} }
impl Compressible for RadixCiphertext { impl Compressible for RadixCiphertext {
fn compress_into(self, messages: &mut Vec<Ciphertext>) -> DataKind { fn compress_into(self, messages: &mut Vec<Ciphertext>) -> Option<DataKind> {
let num_blocks = self.blocks.len(); let num_blocks = self.blocks.len();
for block in self.blocks { for block in self.blocks {
messages.push(block); messages.push(block);
} }
DataKind::Unsigned(num_blocks) NonZero::new(num_blocks).map(DataKind::Unsigned)
} }
} }
impl Compressible for SignedRadixCiphertext { impl Compressible for SignedRadixCiphertext {
fn compress_into(self, messages: &mut Vec<Ciphertext>) -> DataKind { fn compress_into(self, messages: &mut Vec<Ciphertext>) -> Option<DataKind> {
let num_blocks = self.blocks.len(); let num_blocks = self.blocks.len();
for block in self.blocks { for block in self.blocks {
messages.push(block); messages.push(block);
} }
DataKind::Signed(num_blocks) NonZero::new(num_blocks).map(DataKind::Signed)
} }
} }
@@ -63,12 +64,38 @@ impl CompressedCiphertextListBuilder {
T: Compressible, T: Compressible,
{ {
let n = self.ciphertexts.len(); let n = self.ciphertexts.len();
let kind = data.compress_into(&mut self.ciphertexts); let maybe_kind = data.compress_into(&mut self.ciphertexts);
let num_blocks = self
.ciphertexts let Some(modulus) = self.ciphertexts.last().map(|ct| ct.message_modulus) else {
.last() // This means the list of blocks is still empty, so we assert the kind is None
.map_or(0, |ct| kind.num_blocks(ct.message_modulus)); // 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()); assert_eq!(n + num_blocks, self.ciphertexts.len());
self.info.push(kind); self.info.push(kind);
self self
} }

View File

@@ -22,6 +22,7 @@ use crate::shortint::list_compression::{
}; };
use crate::shortint::parameters::NoiseSquashingCompressionParameters; use crate::shortint::parameters::NoiseSquashingCompressionParameters;
use crate::Versionize; use crate::Versionize;
use std::num::NonZero;
use crate::integer::backward_compatibility::list_compression::NoiseSquashingCompressionKeyVersions; use crate::integer::backward_compatibility::list_compression::NoiseSquashingCompressionKeyVersions;
@@ -228,27 +229,27 @@ mod sealed {
} }
pub trait SquashedNoiseCompressible: sealed::Sealed { pub trait SquashedNoiseCompressible: sealed::Sealed {
fn compress_into(self, messages: &mut Vec<SquashedNoiseCiphertext>) -> DataKind; fn compress_into(self, messages: &mut Vec<SquashedNoiseCiphertext>) -> Option<DataKind>;
} }
impl SquashedNoiseCompressible for SquashedNoiseRadixCiphertext { impl SquashedNoiseCompressible for SquashedNoiseRadixCiphertext {
fn compress_into(mut self, messages: &mut Vec<SquashedNoiseCiphertext>) -> DataKind { fn compress_into(mut self, messages: &mut Vec<SquashedNoiseCiphertext>) -> Option<DataKind> {
messages.append(&mut self.packed_blocks); 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 { impl SquashedNoiseCompressible for SquashedNoiseSignedRadixCiphertext {
fn compress_into(mut self, messages: &mut Vec<SquashedNoiseCiphertext>) -> DataKind { fn compress_into(mut self, messages: &mut Vec<SquashedNoiseCiphertext>) -> Option<DataKind> {
messages.append(&mut self.packed_blocks); 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 { impl SquashedNoiseCompressible for SquashedNoiseBooleanBlock {
fn compress_into(self, messages: &mut Vec<SquashedNoiseCiphertext>) -> DataKind { fn compress_into(self, messages: &mut Vec<SquashedNoiseCiphertext>) -> Option<DataKind> {
messages.push(self.ciphertext); messages.push(self.ciphertext);
DataKind::Boolean Some(DataKind::Boolean)
} }
} }
@@ -283,10 +284,13 @@ impl SquashedNoiseExpandable for SquashedNoiseRadixCiphertext {
if let DataKind::Unsigned(block_count) = kind { if let DataKind::Unsigned(block_count) = kind {
Ok(Self { Ok(Self {
packed_blocks: blocks, packed_blocks: blocks,
original_block_count: block_count, original_block_count: block_count.get(),
}) })
} else { } 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 { if let DataKind::Signed(block_count) = kind {
Ok(Self { Ok(Self {
packed_blocks: blocks, packed_blocks: blocks,
original_block_count: block_count, original_block_count: block_count.get(),
}) })
} else { } 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 { pub fn push(&mut self, value: impl SquashedNoiseCompressible) -> &mut Self {
let n = self.list.len(); 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 // Check that the number of blocks that were added matches the
// number of blocks advertised by the DataKind // 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()); assert_eq!(n + num_blocks, self.list.len());
self.info.push(kind); self.info.push(kind);

View File

@@ -2,15 +2,16 @@ use super::{BooleanBlock, IntegerRadixCiphertext};
use crate::integer::backward_compatibility::ciphertext::DataKindVersions; use crate::integer::backward_compatibility::ciphertext::DataKindVersions;
use crate::shortint::{Ciphertext, MessageModulus}; use crate::shortint::{Ciphertext, MessageModulus};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
use tfhe_versionable::Versionize; use tfhe_versionable::Versionize;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Versionize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Versionize)]
#[versionize(DataKindVersions)] #[versionize(DataKindVersions)]
pub enum DataKind { pub enum DataKind {
/// The held value is a number of radix blocks. /// The held value is a number of radix blocks.
Unsigned(usize), Unsigned(NonZeroUsize),
/// The held value is a number of radix blocks. /// The held value is a number of radix blocks.
Signed(usize), Signed(NonZeroUsize),
Boolean, Boolean,
String { String {
n_chars: u32, n_chars: u32,
@@ -21,7 +22,7 @@ pub enum DataKind {
impl DataKind { impl DataKind {
pub fn num_blocks(self, message_modulus: MessageModulus) -> usize { pub fn num_blocks(self, message_modulus: MessageModulus) -> usize {
match self { match self {
Self::Unsigned(n) | Self::Signed(n) => n, Self::Unsigned(n) | Self::Signed(n) => n.get(),
Self::Boolean => 1, Self::Boolean => 1,
Self::String { n_chars, .. } => { Self::String { n_chars, .. } => {
let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2());

View File

@@ -248,8 +248,8 @@ impl CudaFlattenedVecCompactCiphertextList {
.flat_map(|data_kind| { .flat_map(|data_kind| {
let repetitions = match data_kind { let repetitions = match data_kind {
DataKind::Boolean => 1, DataKind::Boolean => 1,
DataKind::Signed(x) => *x, DataKind::Signed(x) => x.get(),
DataKind::Unsigned(x) => *x, DataKind::Unsigned(x) => x.get(),
DataKind::String { .. } => panic!("DataKind not supported on GPUs"), DataKind::String { .. } => panic!("DataKind not supported on GPUs"),
}; };
std::iter::repeat_n(matches!(data_kind, DataKind::Boolean), repetitions) std::iter::repeat_n(matches!(data_kind, DataKind::Boolean), repetitions)

View File

@@ -20,6 +20,7 @@ use crate::shortint::parameters::AtomicPatternKind;
use crate::shortint::PBSOrder; use crate::shortint::PBSOrder;
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserializer, Serializer}; use serde::{Deserializer, Serializer};
use std::num::NonZeroUsize;
pub trait CudaExpandable: Sized { pub trait CudaExpandable: Sized {
fn from_expanded_blocks(blocks: CudaRadixCiphertext, kind: DataKind) -> crate::Result<Self>; fn from_expanded_blocks(blocks: CudaRadixCiphertext, kind: DataKind) -> crate::Result<Self>;
@@ -447,7 +448,7 @@ pub trait CudaCompressible {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind; ) -> Option<DataKind>;
} }
impl CudaCompressible for CudaSignedRadixCiphertext { impl CudaCompressible for CudaSignedRadixCiphertext {
@@ -455,12 +456,15 @@ impl CudaCompressible for CudaSignedRadixCiphertext {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind { ) -> Option<DataKind> {
let x = self.ciphertext.duplicate(streams); let x = self.ciphertext.duplicate(streams);
let num_blocks = x.d_blocks.lwe_ciphertext_count().0; let num_blocks = x.d_blocks.lwe_ciphertext_count().0;
messages.push(x); let num_blocks = NonZeroUsize::new(num_blocks);
DataKind::Signed(num_blocks) if num_blocks.is_some() {
messages.push(x)
}
num_blocks.map(DataKind::Signed)
} }
} }
@@ -469,11 +473,11 @@ impl CudaCompressible for CudaBooleanBlock {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind { ) -> Option<DataKind> {
let x = self.0.ciphertext.duplicate(streams); let x = self.0.ciphertext.duplicate(streams);
messages.push(x); messages.push(x);
DataKind::Boolean Some(DataKind::Boolean)
} }
} }
impl CudaCompressible for CudaUnsignedRadixCiphertext { impl CudaCompressible for CudaUnsignedRadixCiphertext {
@@ -481,12 +485,15 @@ impl CudaCompressible for CudaUnsignedRadixCiphertext {
self, self,
messages: &mut Vec<CudaRadixCiphertext>, messages: &mut Vec<CudaRadixCiphertext>,
streams: &CudaStreams, streams: &CudaStreams,
) -> DataKind { ) -> Option<DataKind> {
let x = self.ciphertext.duplicate(streams); let x = self.ciphertext.duplicate(streams);
let num_blocks = x.d_blocks.lwe_ciphertext_count().0; let num_blocks = x.d_blocks.lwe_ciphertext_count().0;
messages.push(x); let num_blocks = NonZeroUsize::new(num_blocks);
DataKind::Unsigned(num_blocks) if num_blocks.is_some() {
messages.push(x)
}
num_blocks.map(DataKind::Unsigned)
} }
} }
@@ -505,13 +512,9 @@ impl CudaCompressedCiphertextListBuilder {
} }
pub fn push<T: CudaCompressible>(&mut self, data: T, streams: &CudaStreams) -> &mut Self { pub fn push<T: CudaCompressible>(&mut self, data: T, streams: &CudaStreams) -> &mut Self {
let kind = data.compress_into(&mut self.ciphertexts, streams); if let Some(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 {
self.info.push(kind); self.info.push(kind);
} }
self self
} }

View File

@@ -155,6 +155,7 @@ mod tests {
ProvenCompactCiphertextList, ProvenCompactCiphertextList,
}; };
use crate::shortint::parameters::test_params::TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2; 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 // 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::current_params::V1_3_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
use crate::shortint::parameters::{ use crate::shortint::parameters::{
@@ -426,8 +427,8 @@ mod tests {
for _ in 0..infos_block_count { for _ in 0..infos_block_count {
let map_to_fake_boolean = random::<u8>() % 2 == 1; let map_to_fake_boolean = random::<u8>() % 2 == 1;
if map_to_fake_boolean { if map_to_fake_boolean {
if curr_block_count != 0 { if let Some(count) = NonZero::new(curr_block_count) {
new_infos.push(DataKind::Unsigned(curr_block_count)); new_infos.push(DataKind::Unsigned(count));
curr_block_count = 0; curr_block_count = 0;
} }
new_infos.push(DataKind::Boolean); new_infos.push(DataKind::Boolean);
@@ -435,8 +436,8 @@ mod tests {
curr_block_count += 1; curr_block_count += 1;
} }
} }
if curr_block_count != 0 { if let Some(count) = NonZero::new(curr_block_count) {
new_infos.push(DataKind::Unsigned(curr_block_count)); new_infos.push(DataKind::Unsigned(count));
} }
assert_eq!( assert_eq!(

View File

@@ -228,4 +228,8 @@ impl CompactCiphertextList {
pub fn size_bytes(&self) -> usize { pub fn size_bytes(&self) -> usize {
self.ct_list.size_bytes() self.ct_list.size_bytes()
} }
pub fn is_packed(&self) -> bool {
self.degree.get() > self.message_modulus.corresponding_max_degree().get()
}
} }

View File

@@ -64,7 +64,7 @@ impl Compactable for &ClearString {
messages: &mut Vec<u64>, messages: &mut Vec<u64>,
message_modulus: MessageModulus, message_modulus: MessageModulus,
num_blocks: Option<usize>, num_blocks: Option<usize>,
) -> crate::integer::ciphertext::DataKind { ) -> Option<DataKind> {
let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2());
if let Some(n) = num_blocks { if let Some(n) = num_blocks {
@@ -79,7 +79,7 @@ impl Compactable for &ClearString {
n_blocks / blocks_per_char as usize 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()); let n_real_chars = n_chars.min(self.str().len());
for byte in &self.str.as_bytes()[..n_real_chars] { for byte in &self.str.as_bytes()[..n_real_chars] {
let mut byte = u64::from(*byte); let mut byte = u64::from(*byte);
@@ -95,10 +95,10 @@ impl Compactable for &ClearString {
messages.push(0); messages.push(0);
} }
DataKind::String { Some(DataKind::String {
n_chars: n_chars as u32, n_chars: n_chars as u32,
padded, padded,
} })
} }
} }
@@ -110,13 +110,24 @@ impl crate::integer::ciphertext::CompactCiphertextListBuilder {
) -> &mut Self { ) -> &mut Self {
let message_modulus = self.pk.key.message_modulus(); let message_modulus = self.pk.key.message_modulus();
let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2());
let n = self.messages.len();
let kind = clear_string.compact_into( let kind = clear_string
&mut self.messages, .compact_into(
message_modulus, &mut self.messages,
Some((clear_string.str.len() + padding_count as usize) * blocks_per_char as usize), 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); 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 self
} }
@@ -127,13 +138,24 @@ impl crate::integer::ciphertext::CompactCiphertextListBuilder {
) -> &mut Self { ) -> &mut Self {
let message_modulus = self.pk.key.message_modulus(); let message_modulus = self.pk.key.message_modulus();
let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2()); let blocks_per_char = 7u32.div_ceil(message_modulus.0.ilog2());
let n = self.messages.len();
let kind = clear_string.compact_into( let kind = clear_string
&mut self.messages, .compact_into(
message_modulus, &mut self.messages,
Some((size * blocks_per_char) as usize), message_modulus,
); Some((size * blocks_per_char) as usize),
)
.expect("Internal error: compact_into should return a kind");
self.info.push(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 self
} }
} }
@@ -186,7 +208,7 @@ impl crate::integer::ciphertext::Expandable for FheString {
} }
impl crate::integer::ciphertext::Compressible for FheString { impl crate::integer::ciphertext::Compressible for FheString {
fn compress_into(self, messages: &mut Vec<crate::shortint::Ciphertext>) -> DataKind { fn compress_into(self, messages: &mut Vec<crate::shortint::Ciphertext>) -> Option<DataKind> {
let n_chars = self.chars().len() as u32; let n_chars = self.chars().len() as u32;
let padded = self.is_padded(); 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 })
} }
} }

View File

@@ -318,7 +318,7 @@ impl Named for SerializableCompactPkePublicParams {
const NAME: &'static str = ZkCompactPkeV1PublicParams::NAME; const NAME: &'static str = ZkCompactPkeV1PublicParams::NAME;
} }
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ZkVerificationOutcome { pub enum ZkVerificationOutcome {
/// The proof and its entity were valid /// The proof and its entity were valid
Valid, Valid,

View File

@@ -18,7 +18,7 @@ use std::convert::Infallible;
use std::error::Error; use std::error::Error;
use std::fmt::Display; use std::fmt::Display;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::num::Wrapping; use std::num::{NonZero, Wrapping};
use std::sync::Arc; use std::sync::Arc;
pub use derived_traits::{Version, VersionsDispatch}; pub use derived_traits::{Version, VersionsDispatch};
@@ -268,6 +268,9 @@ impl_scalar_versionize!(f64);
impl_scalar_versionize!(char); impl_scalar_versionize!(char);
impl_scalar_versionize!(NonZero<u32>);
impl_scalar_versionize!(NonZero<usize>);
impl<T: Versionize> Versionize for Wrapping<T> { impl<T: Versionize> Versionize for Wrapping<T> {
type Versioned<'vers> type Versioned<'vers>
= Wrapping<T::Versioned<'vers>> = Wrapping<T::Versioned<'vers>>