Manual Bootstrapping and Noise Measuring

Hello,

I am wondering if there are any mechanisms to allow me to perform operations (either from the Boolean level or the Integers level) without bootstrapping and I manually call the bootstrapping function on a ciphertext. Specifically, I am trying to do something like:

ct1  = Ciphertext(1);
ct2 = Ciphertext(2);

// Perform computations
res = NAND(ct1, ct2);       // or res = ct1 + ct2

res.noise_leve();

res.bootstrap();

res_decrypted = res.decrypt();

I’m newbie to Rust, and I’ve checked similar questions but they only somewhat address my question.

Thanks in advance!

Hi !
The best way to do that is to use the “shortint” layer (small integer of 2bits with the default parameters). This API gives you more control than the “integer” API:

use tfhe::shortint::gen_keys;
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;

pub fn main() {
    // Generate the client key and the server key:
    let (cks, sks) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);

    // Encrypt values and add them
    let ct1 = cks.encrypt(1);
    let ct2 = cks.encrypt(2);

    let res = sks.unchecked_add(&ct1, &ct2);

    println!("noise level before BS: {:?}", res.noise_level());

    // Manually bootstrap the result
    let lut = sks.generate_lookup_table(|x| x);
    let bootstrapped = sks.apply_lookup_table(&res, &lut);

    println!("noise level after BS: {:?}", bootstrapped.noise_level());

    // Decrypt
    let dec = cks.decrypt(&bootstrapped);
    println!("dec: {:?}", dec);
}

TFHE’s bootstrap is called a PBS (programmable bootstrap) because it allows to run any function as a lookup table (that’s why I run it using the apply_lookup_table function). Most operations are actually done with a bootstrap so you cannot run them without bootstrap.

1 Like

Thank you so much!

I am also wondering if I can do it at the Boolean level. I could get to the engine\mod.rs file that contains the implementation of the Boolean gates operation. For example, the or function is implemented as follows:


    fn or(
        &mut self,
        ct_left: &Ciphertext,
        ct_right: &Ciphertext,
        server_key: &ServerKey,
    ) -> Ciphertext {
        match (ct_left, ct_right) {
            (Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
                Ciphertext::Trivial(*message_left || *message_right)
            }
            (Ciphertext::Encrypted(_), Ciphertext::Trivial(message_right)) => {
                self.or(ct_left, *message_right, server_key)
            }
            (Ciphertext::Trivial(message_left), Ciphertext::Encrypted(_)) => {
                self.or(*message_left, ct_right, server_key)
            }
            (Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
                let mut buffer_lwe_before_pbs = LweCiphertext::new(
                    0u32,
                    ct_left_ct.lwe_size(),
                    ct_left_ct.ciphertext_modulus(),
                );
                let bootstrapper = &mut self.bootstrapper;

                // Compute the linear combination for OR: ct_left + ct_right + (0,...,0,+1/8)
                // ct_left + ct_right
                lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
                let cst = Plaintext(PLAINTEXT_TRUE);
                // + 1/8
                lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst);

                // compute the bootstrap and the key switch
                bootstrapper.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
            }
        }
    }

I am trying to separate the Bootstrapping from the Boolean gate operation itself like the following:

fn or(
        &mut self,
        ct_left: &Ciphertext,
        ct_right: &Ciphertext,
        server_key: &ServerKey,
    ) -> Ciphertext {
        match (ct_left, ct_right) {
            (Ciphertext::Trivial(message_left), Ciphertext::Trivial(message_right)) => {
                Ciphertext::Trivial(*message_left || *message_right)
            }
            (Ciphertext::Encrypted(_), Ciphertext::Trivial(message_right)) => {
                self.or(ct_left, *message_right, server_key)
            }
            (Ciphertext::Trivial(message_left), Ciphertext::Encrypted(_)) => {
                self.or(*message_left, ct_right, server_key)
            }
            (Ciphertext::Encrypted(ct_left_ct), Ciphertext::Encrypted(ct_right_ct)) => {
                let mut buffer_lwe_before_pbs = LweCiphertext::new(
                    0u32,
                    ct_left_ct.lwe_size(),
                    ct_left_ct.ciphertext_modulus(),
                );
                
                 // Compute the linear combination for OR: ct_left + ct_right + (0,...,0,+1/8)
                // ct_left + ct_right
                lwe_ciphertext_add(&mut buffer_lwe_before_pbs, ct_left_ct, ct_right_ct);
                let cst = Plaintext(PLAINTEXT_TRUE);
                // + 1/8
                lwe_ciphertext_plaintext_add_assign(&mut buffer_lwe_before_pbs, cst);

                // Removed bootstrapping //
               
                Ciphertext::Encrypted(buffer_lwe_before_pbs)
            }
        }
    }

Then I have another bootstrapping function contains the bootstrapping operation:

fn bootstrape_ctxt(buffer_lwe_before_pbfs: Ciphertext, server_key: ServerKey){
                let bootstrapper = &mut self.bootstrapper;
                
                // compute the bootstrap and the key switch
                bootstrapper.apply_bootstrapping_pattern(buffer_lwe_before_pbs, server_key)
}

The problem I face is that the functions and structs are well-baked together so that modifying the mod.rs will causes errors.

So, any ideas for implementing this?

Yes, it is not possible to do that inside tfhe-rs because boolean operations are also bootstrapped, meaning that the results are not correct anymore without the last bootstrap.
The bootstrap at the end of the operation is not only used for the noise but also to apply a specific lookup table.

Since all operations are bootstrapped there is no “noise_level()” method like you can find on the shortint ciphertext.

1 Like