TFHE-rs FHEUint8 vs FHEUint64

Hi!

I ran into an issue while working on TFHE-rs and trying to learn how to use your framework.
In the first tutorial for high level api I was experimenting with some other parameters, and wanted support for larger numbers. However it seems like the FHEencrypt function does not support larger integers than 16 bit, quite similar to the concrete-python implementation.

Here is the sample code I was currently trying to modify:

use tfhe::{ConfigBuilder, generate_keys, set_server_key, FheUint8};
use tfhe::prelude::*;

fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.build();

// Client-side
let (client_key, server_key) = generate_keys(config);

let clear_a = 27u8;
let clear_b = 128u8;

let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);

//Server-side
set_server_key(server_key);
let result = a + b;

//Client-side
let decrypted_result: u8 = result.decrypt(&client_key);

let clear_result = clear_a + clear_b;

assert_eq!(decrypted_result, clear_result);

}

I tried another of your sample code, and then it worked fine with larger numbers, and as far as I could see the reason being that it supports up to 64 bit integers.

Here is the other sample code I tried and modified:

use tfhe::shortint::prelude::*;

fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

let msg1 = 1;
let msg2 = 0;

let modulus = client_key.parameters.message_modulus.0;

// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);

// We use the server public key to execute an integer circuit:
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);

// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_3);
assert_eq!(output, (msg1 + msg2) % modulus as u64);

}

Also, I noted that there is a significant difference in run-time on those two examples where I believe the reason being that the latter has less operations than the first sample code. Is it that easy explanation or is it more complex? (The first code use 12 seconds, while the latter use only 1,2 seconds which I found very surprising)

Hello @bear

Here is an updated sample for u64 on TFHE-rs 0.2.4

use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};

fn main() {
    let config = ConfigBuilder::all_disabled()
        .enable_default_uint64()
        .build();

    // Client-side
    let (client_key, server_key) = generate_keys(config);

    let clear_a = 27u64;
    let clear_b = 128u64;

    let a = FheUint8::try_encrypt(clear_a, &client_key).unwrap();
    let b = FheUint8::try_encrypt(clear_b, &client_key).unwrap();

    //Server-side
    set_server_key(server_key);
    let result = a + b;

    //Client-side
    let decrypted_result: u64 = result.decrypt(&client_key);

    let clear_result = clear_a + clear_b;

    assert_eq!(decrypted_result, clear_result);
}

the second sample is for shortint and will not be able to support more than 8 bits at the very maximum with the right parameters but it’s probably too cumbersome to use compared to the HL API example.

The HL API is being heavily worked on and will be smoother to use in 0.3.0 :slight_smile:

Cheers !

1 Like

Thank you for your fast reply and the modified samplecode!
That helps a lot.

Just a small question for wrapping my head around the modification: The only difference is “encrypt vs try_encrypt” and you added a .unwrap();. Could you explain how to understand this better on how to use those functions? It is not clear from the current documentation… :wink:

Small remark regarding second example, yes, I am aware it should only be for 8 bit, however it worked for much larger integers as input. I dont understand why…

Good to know that there is work on the HL API and that it will be smoother to use. Any idea when it will be launched?

Thanks so much for your help

I’m really not sure how the shortint snippet can work with u64 :thinking:

for the try encrypt it’s just a matter of rough edges being smoothed out, we are aware of the awkwardness and HL API has been updated to be more user friendly on that front !

It is a interesting question. I basically used this code:

use tfhe::shortint::prelude::*;

fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2);

let msg1 = 1;
let msg2 = 0;

let modulus = client_key.parameters.message_modulus.0;

// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);

// We use the server public key to execute an integer circuit:
let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);

// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_3);
assert_eq!(output, (msg1 + msg2) % modulus as u64);

}

On my computer it works with larger integers…

Thanks for the information regarding encrypt and for clarifying that. I am looking forward for the updated HL API.

Cheers!

There is a confusion with “it works with larger integers”, the shortint example does not work with large integers.

The API of shortint accepts u64 as the clear value to encrypt and returns u64 when decrypting
however, that does not means it supports encrypting the full range of value supported by u64

The API being used is shortint API, with PARAM_MESSAGE_2_CARRY_2 as parameters.
meaning the message has to fit on 2 bits.

You are encrypting 0 and 1 which both fits in 2 bits, the result of 0 + 1 also fits in 2 bits.
If you try to encrypt values bigger than 3 it would not work.

1 Like

Aha… Makes sense. Sorry for my confusion.
Thanks for all the help!