Compare commits

...

1 Commits

Author SHA1 Message Date
Arthur Meyre
97cdbf77e4 saved: failure case for mul
RUSTFLAGS="-C target-cpu=native" cargo +stable test --tests --profile devo --features=x86_64-unix,integer,internal-keycache -p tfhe -- integer_default_mul_param_message_2_carry_2
2023-04-19 17:47:20 +02:00
2 changed files with 578 additions and 34 deletions

View File

@@ -1,3 +1,532 @@
// use std::sync::Mutex;
// use crate::integer::ciphertext::RadixCiphertext;
// use crate::integer::ServerKey;
// use crate::shortint::PBSOrderMarker;
// use rayon::prelude::*;
// impl ServerKey {
// /// Computes homomorphically a multiplication between a ciphertext encrypting an integer value
// /// and another encrypting a shortint value.
// ///
// /// This function computes the operation without checking if it exceeds the capacity of the
// /// ciphertext.
// ///
// /// The result is assigned to the `ct_left` ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// ///```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 170;
// /// let clear_2 = 3;
// ///
// /// // Encrypt two messages
// /// let mut ct_left = cks.encrypt(clear_1);
// /// let ct_right = cks.encrypt_one_block(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// sks.unchecked_block_mul_assign_parallelized(&mut ct_left, &ct_right, 0);
// ///
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_left);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn unchecked_block_mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct_left: &mut RadixCiphertext<PBSOrder>,
// ct_right: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) {
// *ct_left = self.unchecked_block_mul_parallelized(ct_left, ct_right, index);
// }
// /// Computes homomorphically a multiplication between a ciphertexts encrypting an integer
// /// value and another encrypting a shortint value.
// ///
// /// This function computes the operation without checking if it exceeds the capacity of the
// /// ciphertext.
// ///
// /// The result is returned as a new ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// ///```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 55;
// /// let clear_2 = 3;
// ///
// /// // Encrypt two messages
// /// let ct_left = cks.encrypt(clear_1);
// /// let ct_right = cks.encrypt_one_block(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// let ct_res = sks.unchecked_block_mul_parallelized(&ct_left, &ct_right, 0);
// ///
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_res);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn unchecked_block_mul_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &RadixCiphertext<PBSOrder>,
// ct2: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) -> RadixCiphertext<PBSOrder> {
// let shifted_ct = self.blockshift(ct1, index);
// let mut result_lsb = shifted_ct.clone();
// let mut result_msb = shifted_ct;
// self.unchecked_block_mul_lsb_msb_parallelized(&mut result_lsb, &mut result_msb, ct2, index);
// result_msb = self.blockshift(&result_msb, 1);
// self.unchecked_add(&result_lsb, &result_msb)
// }
// /// Computes homomorphically a multiplication between a ciphertext encrypting integer value
// /// and another encrypting a shortint value.
// ///
// /// The result is returned as a new ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// ///```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 170;
// /// let clear_2 = 3;
// ///
// /// // Encrypt two messages
// /// let mut ctxt_1 = cks.encrypt(clear_1);
// /// let ctxt_2 = cks.encrypt_one_block(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// let ct_res = sks.smart_block_mul_parallelized(&mut ctxt_1, &ctxt_2, 0);
// ///
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_res);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn smart_block_mul_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) -> RadixCiphertext<PBSOrder> {
// //Makes sure we can do the multiplications
// self.full_propagate_parallelized(ct1);
// let shifted_ct = self.blockshift(ct1, index);
// let mut result_lsb = shifted_ct.clone();
// let mut result_msb = shifted_ct;
// self.unchecked_block_mul_lsb_msb_parallelized(&mut result_lsb, &mut result_msb, ct2, index);
// result_msb = self.blockshift(&result_msb, 1);
// self.smart_add_parallelized(&mut result_lsb, &mut result_msb)
// }
// /// Computes homomorphically a multiplication between a ciphertext encrypting integer value
// /// and another encrypting a shortint value.
// ///
// /// The result is returned as a new ciphertext.
// ///
// /// This function, like all "default" operations (i.e. not smart, checked or unchecked), will
// /// check that the input ciphertexts block carries are empty and clears them if it's not the
// /// case and the operation requires it. It outputs a ciphertext whose block carries are always
// /// empty.
// ///
// /// This means that when using only "default" operations, a given operation (like add for
// /// example) has always the same performance characteristics from one call to another and
// /// guarantees correctness by pre-emptively clearing carries of output ciphertexts.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// ///```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 170;
// /// let clear_2 = 3;
// ///
// /// // Encrypt two messages
// /// let ctxt_1 = cks.encrypt(clear_1);
// /// let ctxt_2 = cks.encrypt_one_block(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// let ct_res = sks.block_mul_parallelized(&ctxt_1, &ctxt_2, 0);
// ///
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_res);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn block_mul_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &RadixCiphertext<PBSOrder>,
// ct2: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) -> RadixCiphertext<PBSOrder> {
// let mut ct_res = ct1.clone();
// self.block_mul_assign_parallelized(&mut ct_res, ct2, index);
// ct_res
// }
// pub fn block_mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) {
// let mut tmp_rhs: crate::shortint::CiphertextBase<PBSOrder>;
// let (lhs, rhs) = match (ct1.block_carries_are_empty(), ct2.carry_is_empty()) {
// (true, true) => (ct1, ct2),
// (true, false) => {
// tmp_rhs = ct2.clone();
// self.key.clear_carry_assign(&mut tmp_rhs);
// (ct1, &tmp_rhs)
// }
// (false, true) => {
// self.full_propagate_parallelized(ct1);
// (ct1, ct2)
// }
// (false, false) => {
// tmp_rhs = ct2.clone();
// rayon::join(
// || self.full_propagate_parallelized(ct1),
// || self.key.clear_carry_assign(&mut tmp_rhs),
// );
// (ct1, &tmp_rhs)
// }
// };
// self.unchecked_block_mul_assign_parallelized(lhs, rhs, index);
// self.full_propagate_parallelized(lhs);
// }
// fn unchecked_block_mul_lsb_msb_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// result_lsb: &mut RadixCiphertext<PBSOrder>,
// result_msb: &mut RadixCiphertext<PBSOrder>,
// ct2: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) {
// let len = result_msb.blocks.len() - 1;
// rayon::join(
// || {
// result_lsb.blocks[index..]
// .par_iter_mut()
// .for_each(|res_lsb_i| {
// self.key.unchecked_mul_lsb_assign(res_lsb_i, ct2);
// });
// },
// || {
// result_msb.blocks[index..len]
// .par_iter_mut()
// .for_each(|res_msb_i| {
// self.key.unchecked_mul_msb_assign(res_msb_i, ct2);
// });
// },
// );
// }
// pub fn smart_block_mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &crate::shortint::CiphertextBase<PBSOrder>,
// index: usize,
// ) {
// *ct1 = self.smart_block_mul_parallelized(ct1, ct2, index);
// }
// /// Computes homomorphically a multiplication between two ciphertexts encrypting integer values.
// ///
// /// This function computes the operation without checking if it exceeds the capacity of the
// /// ciphertext.
// ///
// /// The result is assigned to the `ct_left` ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// /// ```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 255;
// /// let clear_2 = 143;
// ///
// /// // Encrypt two messages
// /// let mut ctxt_1 = cks.encrypt(clear_1);
// /// let ctxt_2 = cks.encrypt(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// let ct_res = sks.unchecked_mul_parallelized(&mut ctxt_1, &ctxt_2);
// ///
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_res);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn unchecked_mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &RadixCiphertext<PBSOrder>,
// ) {
// *ct1 = self.unchecked_mul_parallelized(ct1, ct2);
// }
// /// Computes homomorphically a multiplication between two ciphertexts encrypting integer values.
// ///
// /// This function computes the operation without checking if it exceeds the capacity of the
// /// ciphertext.
// ///
// /// The result is returned as a new ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// pub fn unchecked_mul_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &RadixCiphertext<PBSOrder>,
// ) -> RadixCiphertext<PBSOrder> {
// let mut result = self.create_trivial_zero_radix(ct1.blocks.len());
// // let terms = Mutex::new(Vec::new());
// // ct2.blocks.par_iter().enumerate().for_each(|(i, ct2_i)| {
// // let term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
// // terms.lock().unwrap().push(term);
// // });
// // let mut terms = terms.into_inner().unwrap();
// // for term in terms.iter_mut() {
// // self.smart_add_assign(&mut result, term);
// // }
// let num_blocks = ct1.blocks.len();
// let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
// terms
// .par_iter_mut()
// .zip(ct2.blocks.par_iter().enumerate())
// .for_each(|(term, (i, ct2_i))| {
// *term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
// });
// for term in terms.iter_mut() {
// self.unchecked_add_assign(&mut result, term);
// }
// result
// }
// /// Computes homomorphically a multiplication between two ciphertexts encrypting integer values.
// ///
// /// The result is assigned to the `ct_left` ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// /// ```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 170;
// /// let clear_2 = 6;
// ///
// /// // Encrypt two messages
// /// let mut ctxt_1 = cks.encrypt(clear_1);
// /// let mut ctxt_2 = cks.encrypt(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// let ct_res = sks.smart_mul_parallelized(&mut ctxt_1, &mut ctxt_2);
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_res);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn smart_mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &mut RadixCiphertext<PBSOrder>,
// ) {
// *ct1 = self.smart_mul_parallelized(ct1, ct2);
// }
// /// Computes homomorphically a multiplication between two ciphertexts encrypting integer values.
// ///
// /// The result is returned as a new ciphertext.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// pub fn smart_mul_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &mut RadixCiphertext<PBSOrder>,
// ) -> RadixCiphertext<PBSOrder> {
// rayon::join(
// || self.full_propagate_parallelized(ct1),
// || self.full_propagate_parallelized(ct2),
// );
// let terms = Mutex::new(Vec::new());
// ct2.blocks.par_iter().enumerate().for_each(|(i, ct2_i)| {
// let term = self.unchecked_block_mul_parallelized(ct1, ct2_i, i);
// terms.lock().unwrap().push(term);
// });
// let mut terms = terms.into_inner().unwrap();
// self.smart_binary_op_seq_parallelized(&mut terms, ServerKey::smart_add_parallelized)
// .unwrap_or_else(|| self.create_trivial_zero_radix(ct1.blocks.len()))
// }
// /// Computes homomorphically a multiplication between two ciphertexts encrypting integer values.
// ///
// /// The result is assigned to the `ct_left` ciphertext.
// ///
// /// This function, like all "default" operations (i.e. not smart, checked or unchecked), will
// /// check that the input ciphertexts block carries are empty and clears them if it's not the
// /// case and the operation requires it. It outputs a ciphertext whose block carries are always
// /// empty.
// ///
// /// This means that when using only "default" operations, a given operation (like add for
// /// example) has always the same performance characteristics from one call to another and
// /// guarantees correctness by pre-emptively clearing carries of output ciphertexts.
// ///
// /// # Warning
// ///
// /// - Multithreaded
// ///
// /// # Example
// ///
// /// ```rust
// /// use tfhe::integer::gen_keys_radix;
// /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
// ///
// /// // Generate the client key and the server key:
// /// let num_blocks = 4;
// /// let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_blocks);
// ///
// /// let clear_1 = 170;
// /// let clear_2 = 6;
// ///
// /// // Encrypt two messages
// /// let ctxt_1 = cks.encrypt(clear_1);
// /// let ctxt_2 = cks.encrypt(clear_2);
// ///
// /// // Compute homomorphically a multiplication
// /// let ct_res = sks.mul_parallelized(&ctxt_1, &ctxt_2);
// /// // Decrypt
// /// let res: u64 = cks.decrypt(&ct_res);
// /// assert_eq!((clear_1 * clear_2) % 256, res);
// /// ```
// pub fn mul_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &RadixCiphertext<PBSOrder>,
// ct2: &RadixCiphertext<PBSOrder>,
// ) -> RadixCiphertext<PBSOrder> {
// let mut ct_res = ct1.clone();
// self.mul_assign_parallelized(&mut ct_res, ct2);
// ct_res
// }
// pub fn mul_assign_parallelized<PBSOrder: PBSOrderMarker>(
// &self,
// ct1: &mut RadixCiphertext<PBSOrder>,
// ct2: &RadixCiphertext<PBSOrder>,
// ) {
// let mut tmp_rhs: RadixCiphertext<PBSOrder>;
// let (lhs, rhs) = match (ct1.block_carries_are_empty(), ct2.block_carries_are_empty()) {
// (true, true) => (ct1, ct2),
// (true, false) => {
// tmp_rhs = ct2.clone();
// self.full_propagate_parallelized(&mut tmp_rhs);
// (ct1, &tmp_rhs)
// }
// (false, true) => {
// self.full_propagate_parallelized(ct1);
// (ct1, ct2)
// }
// (false, false) => {
// tmp_rhs = ct2.clone();
// rayon::join(
// || self.full_propagate_parallelized(ct1),
// || self.full_propagate_parallelized(&mut tmp_rhs),
// );
// (ct1, &tmp_rhs)
// }
// };
// // let num_blocks = lhs.blocks.len();
// // let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
// // terms
// // .par_iter_mut()
// // .zip(rhs.blocks.par_iter().enumerate())
// // .for_each(|(term, (i, rhs_i))| {
// // *term = self.unchecked_block_mul_parallelized(lhs, rhs_i, i);
// // });
// // *lhs = self
// // .smart_binary_op_seq_parallelized(&mut terms, ServerKey::smart_add_parallelized)
// // .unwrap_or_else(|| self.create_trivial_zero_radix(num_blocks));
// self.unchecked_mul_assign_parallelized(lhs, rhs);
// self.full_propagate_parallelized(lhs);
// }
// }
use std::sync::Mutex;
use crate::integer::ciphertext::RadixCiphertext;
@@ -342,10 +871,10 @@ impl ServerKey {
terms.lock().unwrap().push(term);
});
let mut terms = terms.into_inner().unwrap();
let terms = terms.into_inner().unwrap();
for term in terms.iter_mut() {
self.smart_add_assign(&mut result, term);
for term in terms {
self.unchecked_add_assign(&mut result, &term);
}
result
@@ -495,20 +1024,7 @@ impl ServerKey {
(ct1, &tmp_rhs)
}
};
let num_blocks = lhs.blocks.len();
let mut terms = vec![self.create_trivial_zero_radix(num_blocks); num_blocks];
terms
.par_iter_mut()
.zip(rhs.blocks.par_iter().enumerate())
.for_each(|(term, (i, rhs_i))| {
*term = self.unchecked_block_mul_parallelized(lhs, rhs_i, i);
});
*lhs = self
.smart_binary_op_seq_parallelized(&mut terms, ServerKey::smart_add_parallelized)
.unwrap_or_else(|| self.create_trivial_zero_radix(num_blocks));
self.unchecked_mul_assign_parallelized(lhs, rhs);
self.full_propagate_parallelized(lhs);
}
}

View File

@@ -1242,18 +1242,19 @@ fn integer_smart_mul(param: Parameters) {
fn integer_default_mul(param: Parameters) {
let (cks, sks) = KEY_CACHE.get_from_params(param);
let cks = RadixClientKey::from((cks, NB_CTXT));
//RNG
let mut rng = rand::thread_rng();
let shortint_cks = cks.key.clone();
let shortint_sks = sks.key.clone();
let nb_ct = 8;
let cks = RadixClientKey::from((cks, nb_ct));
// message_modulus^vec_length
let modulus = param.message_modulus.0.pow(NB_CTXT as u32) as u64;
let modulus = param.message_modulus.0.pow(nb_ct as u32) as u64;
for _ in 0..NB_TEST_SMALLER {
println!("modulus: {modulus}");
for _ in 0..1 {
// Define the cleartexts
let clear1 = rng.gen::<u64>() % modulus;
let clear2 = rng.gen::<u64>() % modulus;
let clear1 = modulus - 1;
let clear2 = modulus - 1;
// println!("clear1 = {}, clear2 = {}", clear1, clear2);
@@ -1261,19 +1262,46 @@ fn integer_default_mul(param: Parameters) {
let ctxt_1 = cks.encrypt(clear1);
let ctxt_2 = cks.encrypt(clear2);
let mut res = ctxt_1.clone();
let mut clear = clear1;
let mut res = sks.mul_parallelized(&ctxt_1, &ctxt_2);
// assert!(res.block_carries_are_empty());
let dec: u64 = cks.decrypt(&res);
println!("sanity dec: {dec}");
res = sks.mul_parallelized(&res, &ctxt_2);
assert!(res.block_carries_are_empty());
for _ in 0..5 {
res = sks.mul_parallelized(&res, &ctxt_2);
assert!(res.block_carries_are_empty());
clear = (clear * clear2) % modulus;
for block in res.blocks.iter() {
let dec_block_carry_msg = shortint_cks.decrypt_message_and_carry(block);
let padding_bit = shortint_cks.decrypt_message_and_carry_without_padding(block)
/ ((param.message_modulus.0 * param.carry_modulus.0) as u64 / 2);
println!("carry_msg: {dec_block_carry_msg}");
println!("padding_bit: {padding_bit}");
println!("Applying identity LUT on block:");
let acc = shortint_sks.generate_accumulator(|x: u64| x);
let lut_res = shortint_sks.apply_lookup_table(&block, &acc);
let dec_after_pbs_with_trash_padding = shortint_cks.decrypt_message_and_carry(&lut_res);
println!("Expected: {dec_block_carry_msg}, got: {dec_after_pbs_with_trash_padding}");
}
sks.full_propagate(&mut res);
for block in res.blocks.iter() {
let dec_block_carry_msg = shortint_cks.decrypt_message_and_carry(block);
let padding_bit = shortint_cks.decrypt_message_and_carry_without_padding(block)
/ ((param.message_modulus.0 * param.carry_modulus.0) as u64 / 2);
println!("carry_msg: {dec_block_carry_msg}");
println!("padding_bit: {padding_bit}");
println!("Applying identity LUT on block:");
let acc = shortint_sks.generate_accumulator(|x: u64| x);
let lut_res = shortint_sks.apply_lookup_table(&block, &acc);
let dec_after_pbs_with_trash_padding = shortint_cks.decrypt_message_and_carry(&lut_res);
println!("Expected: {dec_block_carry_msg}, got: {dec_after_pbs_with_trash_padding}");
}
let dec: u64 = cks.decrypt(&res);
clear = (clear * clear2) % modulus;
let clear = (clear1 * clear2) % modulus;
// Check the correctness
assert_eq!(clear, dec);