The carry will have an influence just by existing, when you use parmeters with 1 bit of message, and add somes ciphertext, there will still be a carry
Also what you need to know is that there is a padding bit, so with V1_4_PARAM_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M128 there are 1 + 1 + 1 = 3 bits, but padding bit is a special bit that needs to stay 0 in order to have a correct PBS output, when the padding bit is not zero, the output of the PBS is the negation
Example 1, getting correct result, but by luck
fn main() {
let params = V1_4_PARAM_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M128;
let (cks, sks) = gen_keys(params);
let mut a = cks.encrypt(1);
let b = cks.encrypt(1);
sks.unchecked_add_assign(&mut a, &b);
assert_eq!(cks.decrypt_message_and_carry(&a), 2);
// mul_lsb is going to do a PBS on a ciphertext that encrypts
// (a * 2) + b = 4 + 1 = 5 which is 0b101, the padding bit is no longer 0
let c = sks.unchecked_mul_lsb(&a, &b);
// luckily the result is still correct, (2 * 1) % 2 = 0
assert_eq!(cks.decrypt_message_and_carry(&c), 0);
let c = sks.unchecked_mul_lsb(&b, &a);
assert_eq!(cks.decrypt_message_and_carry(&c), 0);
}
Example 2, getting incorrect result
We do one more addition
fn main() {
let params = V1_4_PARAM_MESSAGE_1_CARRY_1_KS_PBS_GAUSSIAN_2M128;
let (cks, sks) = gen_keys(params);
let mut a = cks.encrypt(1);
let b = cks.encrypt(1);
sks.unchecked_add_assign(&mut a, &b);
sks.unchecked_add_assign(&mut a, &b);
assert_eq!(cks.decrypt_message_and_carry(&a), 3);
// mul_lsb is going to do a PBS on a ciphertext that encrypts
// (a * 2) + b = 6 + 1 = 7 which is 0b111, the padding bit is no longer 0
let c = sks.unchecked_mul_lsb(&a, &b);
// incorrect (3 * 1) % 2 = 1
assert_eq!(cks.decrypt_message_and_carry(&c), 7);
let c = sks.unchecked_mul_lsb(&b, &a);
assert_eq!(cks.decrypt_message_and_carry(&c), 0); // incorrect
}
Example 3 with 2 bits of carry
fn main() {
let params = V1_4_PARAM_MESSAGE_1_CARRY_2_KS_PBS_GAUSSIAN_2M128;
let (cks, sks) = gen_keys(params);
let mut a = cks.encrypt(1);
let b = cks.encrypt(1);
sks.unchecked_add_assign(&mut a, &b);
sks.unchecked_add_assign(&mut a, &b);
assert_eq!(cks.decrypt_message_and_carry(&a), 3);
// mul_lsb is going to do a PBS on a ciphertext that encrypts
// (a * 2) + b = 6 + 1 = 7 which is 0b0111the padding bit is still 0, so its ok
let c = sks.unchecked_mul_lsb(&a, &b);
assert_eq!(cks.decrypt_message_and_carry(&c), 1);
let c = sks.unchecked_mul_lsb(&b, &a);
assert_eq!(cks.decrypt_message_and_carry(&c), 1);
However, I would point out that, the degree is not the only thing to keep track of, in order to ensure the correctness, there is also the noise level, which grows on each additions, and scalar_muliplication (used to do a bivariate PBS), and example 1&2 are also not OK with regards to the nosie level. If you run them with the noise-asserts feature you’ll get a panic, however, the example 3 is fine