Skip to content

Commit

Permalink
fix(integer): is_neg/sub/add possible
Browse files Browse the repository at this point in the history
The way we did the is_neg/add/sub possible at the integer level was
incorrect in two ways.

1) We simply called the is_neg/add/sub_possible from
   the shortint impl on each block as if the were independant.
   However that is not the case, and to the check did not reflect
   actual computation.

2) We checked that we did not go beyond max degree on each block,
   However, a more correct approach would be to check that adding
   the potential carry from preceding block would not exceeding the
   current block max capacity.
  • Loading branch information
tmontaigu committed Sep 24, 2023
1 parent 7fe3ad3 commit 651f5dc
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 28 deletions.
5 changes: 4 additions & 1 deletion tfhe/docs/fine_grained_api/integer/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,15 @@ fn main() {
assert!(result.is_ok());

let result = server_key.checked_sub_assign(&mut ct_1, &ct_2);
assert!(result.is_ok());

let result = server_key.checked_add_assign(&mut ct_1, &ct_2);
assert!(result.is_err());

// We use the client key to decrypt the output of the circuit:
// Only the scalar multiplication could be done
let output: u64 = client_key.decrypt(&ct_1);
assert_eq!(output, (msg1 * scalar) % modulus as u64);
assert_eq!(output, ((msg1 * scalar) - msg2) % modulus as u64);
}
```

Expand Down
13 changes: 11 additions & 2 deletions tfhe/src/integer/server_key/radix/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,19 @@ impl ServerKey {
where
T: IntegerRadixCiphertext,
{
for (ct_left_i, ct_right_i) in ct_left.blocks().iter().zip(ct_right.blocks().iter()) {
if !self.key.is_add_possible(ct_left_i, ct_right_i) {
// Assumes message_modulus and carry_modulus matches between pairs of block
let mut preceding_block_carry = 0;
for (left_block, right_block) in ct_left.blocks().iter().zip(ct_right.blocks().iter()) {
let degree_after_add = left_block.degree.0 + right_block.degree.0;

// Also need to take into account preceding_carry
if (degree_after_add + preceding_block_carry)
>= (left_block.message_modulus.0 * left_block.carry_modulus.0)
{
// We would exceed the block 'capacity'
return false;
}
preceding_block_carry = degree_after_add / left_block.message_modulus.0;
}
true
}
Expand Down
53 changes: 30 additions & 23 deletions tfhe/src/integer/server_key/radix/neg.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::core_crypto::prelude::misc::divide_ceil;
use crate::integer::ciphertext::IntegerRadixCiphertext;
use crate::integer::server_key::CheckError;
use crate::integer::server_key::CheckError::CarryFull;
Expand Down Expand Up @@ -58,19 +59,16 @@ impl ServerKey {
{
//z is used to make sure the negation doesn't fill the padding bit
let mut z;
let mut z_b;
let mut z_b = 0;

for i in 0..ctxt.blocks().len() {
let c_i = &mut ctxt.blocks_mut()[i];
z = self.key.unchecked_neg_assign_with_correcting_term(c_i);

// Subtract z/B to the next ciphertext to compensate for the addition of z
z_b = z / self.key.message_modulus.0 as u64;

if i < ctxt.blocks().len() - 1 {
let c_j = &mut ctxt.blocks_mut()[i + 1];
self.key.unchecked_scalar_add_assign(c_j, z_b as u8);
for block in ctxt.blocks_mut() {
if z_b != 0 {
self.key.unchecked_scalar_add_assign(block, z_b);
}
z = self.key.unchecked_neg_assign_with_correcting_term(block);
block.degree.0 = z as usize - z_b as usize;

z_b = (z / self.key.message_modulus.0 as u64) as u8;
}
}

Expand Down Expand Up @@ -100,26 +98,35 @@ impl ServerKey {
where
T: IntegerRadixCiphertext,
{
for i in 0..ctxt.blocks().len() {
let mut preceding_block_carry = 0;
let mut preceding_scaled_z = 0;
for block in ctxt.blocks().iter() {
let msg_mod = block.message_modulus.0;
let carry_mod = block.carry_modulus.0;
let total_modulus = msg_mod * carry_mod;

// z = ceil( degree / 2^p ) x 2^p
let msg_mod = self.key.message_modulus.0;
let mut z = (ctxt.blocks()[i].degree.0 + msg_mod - 1) / msg_mod;
let mut z = divide_ceil(block.degree.0, msg_mod);
z = z.wrapping_mul(msg_mod);
// In the actual operation, preceding_scaled_z is added to the ciphertext
// before doing lwe_ciphertext_opposite:
// i.e the code does -(ciphertext + preceding_scaled_z) + z
// here we do -ciphertext -preceding_scaled_z + z
// which is easier to express degree
let block_degree_after_negation = z - preceding_scaled_z;

// z will be the new degree of ctxt.blocks[i]
if z > self.key.max_degree.0 {
if block_degree_after_negation >= (total_modulus) {
return false;
}

let z_b = z / msg_mod;

if i < ctxt.blocks().len() - 1
&& !self
.key
.is_scalar_add_possible(&ctxt.blocks()[i + 1], z_b as u8)
{
// We want to be able to the negated block and the carry from preceding negated
// block to make sure carry propagation would be correct.
if (block_degree_after_negation + preceding_block_carry) >= (total_modulus) {
return false;
}

preceding_block_carry = block_degree_after_negation / msg_mod;
preceding_scaled_z = z / msg_mod;
}
true
}
Expand Down
34 changes: 32 additions & 2 deletions tfhe/src/integer/server_key/radix/sub.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::core_crypto::algorithms::misc::divide_ceil;
use crate::integer::ciphertext::IntegerRadixCiphertext;
use crate::integer::server_key::CheckError;
use crate::integer::server_key::CheckError::CarryFull;
Expand Down Expand Up @@ -110,10 +111,39 @@ impl ServerKey {
where
T: IntegerRadixCiphertext,
{
for (ct_left_i, ct_right_i) in ctxt_left.blocks().iter().zip(ctxt_right.blocks().iter()) {
if !self.key.is_sub_possible(ct_left_i, ct_right_i) {
let mut preceding_block_carry = 0;
let mut preceding_scaled_z = 0;
for (left_block, right_block) in ctxt_left.blocks().iter().zip(ctxt_right.blocks().iter()) {
// Assumes message_modulus and carry_modulus matches between pairs of block
let msg_mod = left_block.message_modulus.0;
let carry_mod = left_block.carry_modulus.0;
let total_modulus = msg_mod * carry_mod;

// z = ceil( degree / 2^p ) x 2^p
let mut z = divide_ceil(right_block.degree.0, msg_mod);
z = z.wrapping_mul(msg_mod);
// In the actual operation, preceding_scaled_z is added to the ciphertext
// before doing lwe_ciphertext_opposite:
// i.e the code does -(ciphertext + preceding_scaled_z) + z
// here we do -ciphertext -preceding_scaled_z + z
// which is easier to express degree
let right_block_degree_after_negation = z - preceding_scaled_z;

if right_block_degree_after_negation >= (total_modulus) {
return false;
}

let degree_after_add = left_block.degree.0 + right_block_degree_after_negation;

// We want to be able to add the the left block, the negated right block
// and we also want to be able to add the carry from preceding block addition
// to make sure carry propagation would be correct.
if (degree_after_add + preceding_block_carry) >= (total_modulus) {
return false;
}

preceding_block_carry = degree_after_add / msg_mod;
preceding_scaled_z = z / msg_mod;
}
true
}
Expand Down

0 comments on commit 651f5dc

Please sign in to comment.