fix(integer): handles compression of empty ct in empty list

This commit is contained in:
Nicolas Sarlin
2025-04-08 14:48:20 +02:00
committed by Nicolas Sarlin
parent 21efad5fae
commit 0996604574
3 changed files with 57 additions and 8 deletions

View File

@@ -64,13 +64,12 @@ impl CompressedCiphertextListBuilder {
{
let n = self.ciphertexts.len();
let kind = data.compress_into(&mut self.ciphertexts);
let message_modulus = self.ciphertexts.last().unwrap().message_modulus;
assert_eq!(n + kind.num_blocks(message_modulus), self.ciphertexts.len());
if kind.num_blocks(message_modulus) != 0 {
self.info.push(kind);
}
let num_blocks = self
.ciphertexts
.last()
.map_or(0, |ct| kind.num_blocks(ct.message_modulus));
assert_eq!(n + num_blocks, self.ciphertexts.len());
self.info.push(kind);
self
}

View File

@@ -145,7 +145,16 @@ impl crate::integer::ciphertext::Expandable for FheString {
) -> crate::Result<Self> {
match kind {
DataKind::String { n_chars, padded } => {
let n_blocks_per_chars = 7u32.div_ceil(blocks[0].message_modulus.0.ilog2());
if n_chars == 0 {
return Ok(Self::empty());
}
let Some(first_block) = blocks.first() else {
return Err(crate::error!(
"Invalid number of blocks for a string of {n_chars} chars, got 0 blocks"
));
};
let n_blocks_per_chars = 7u32.div_ceil(first_block.message_modulus.0.ilog2());
let expected_num_blocks = n_chars * n_blocks_per_chars;
if expected_num_blocks != blocks.len() as u32 {
return Err(crate::error!("Invalid number of blocks for a string of {n_chars} chars, expected {expected_num_blocks}, got {}", blocks.len()));

View File

@@ -56,3 +56,44 @@ fn test_compressed_list_with_strings() {
assert_eq!(decrypted, clear_string2);
}
}
#[test]
fn test_compressed_list_empty_string() {
let params = PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128.into();
let (cks, _) = gen_keys::<ShortintParameterSet>(params, IntegerKeyKind::Radix);
let private_compression_key =
cks.new_compression_private_key(COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128);
let (compression_key, decompression_key) =
cks.new_compression_decompression_keys(&private_compression_key);
let cks = StringClientKey::new(cks);
let clear_empty = String::new();
let empty = cks.encrypt_ascii(&clear_empty, None);
let clear_string = String::from("not empty");
let string = cks.encrypt_ascii(&clear_string, None);
// Try to compress 2 empty strings with one not empty in the middle
let mut builder = CompressedCiphertextListBuilder::new();
builder.push(empty.clone());
builder.push(string);
builder.push(empty);
let compressed = builder.build(&compression_key);
assert_eq!(compressed.len(), 3);
let s1: FheString = compressed.get(0, &decompression_key).unwrap().unwrap();
let decrypted = cks.decrypt_ascii(&s1);
assert_eq!(decrypted, clear_empty);
let s2: FheString = compressed.get(1, &decompression_key).unwrap().unwrap();
let decrypted = cks.decrypt_ascii(&s2);
assert_eq!(decrypted, clear_string);
let s3: FheString = compressed.get(2, &decompression_key).unwrap().unwrap();
let decrypted = cks.decrypt_ascii(&s3);
assert_eq!(decrypted, clear_empty);
}