Uncheked_bit_and, unchecked_mul

Hello, I attempted to test different operations because the results I had were inconsistent, so I created shortint Ciphertexts with

fn main(){
        let (client_key, server_key) = gen_keys(PARAM_MESSAGE_1_CARRY_0_KS_PBS);

        let msg1 = 1;
        let msg2 = 1;

        let ct_1 = client_key.encrypt(msg1);
        let ct_2 = client_key.encrypt(msg2);
        let mut start_time = Instant::now();
        let ct_3 = server_key.unchecked_add(&ct_1, &ct_2);
        let mut elapsed_time = start_time.elapsed();
        println!("unchecked_add duration{:?}",elapsed_time);
        start_time = Instant::now();
        let ct_4 = server_key.unchecked_mul_lsb(&ct_1, &ct_2);
        elapsed_time = start_time.elapsed();
        println!("unchecked_mul duration {:?}",elapsed_time);
        start_time = Instant::now();
        let ct_5 = server_key.mul_lsb(&ct_1, &ct_2);
        elapsed_time = start_time.elapsed();
        println!("mul_lsb duration {:?}",elapsed_time);
        start_time = Instant::now();
        let ct_6 = server_key.unchecked_bitand(&ct_1, &ct_2);
        elapsed_time = start_time.elapsed();
        println!("unchecked_bitand duration{:?}",elapsed_time);
        start_time = Instant::now();
        let ct_7 = server_key.bitand(&ct_1, &ct_2);
        elapsed_time = start_time.elapsed();
        println!("bitand duration{:?}",elapsed_time);
        let res_1 = client_key.decrypt(&ct_3);
        let res_2 = client_key.decrypt(&ct_4);
        let res_3 = client_key.decrypt(&ct_5);
        let res_4 = client_key.decrypt(&ct_6);
        let res_5 = client_key.decrypt(&ct_7);
        println!("res unchecked_add{}",res_1);
        println!("res unchecked_mul{}",res_2);
        println!("res mul{}",res_3);
        println!("res unchecked_bitand{}",res_4);
        println!("res bitand{}",res_5);

}

And the results are surprising

unchecked_add duration110.613µs
unchecked_mul duration 5.15653459s
mul_lsb duration 18.800073878s
unchecked_bitand duration5.145344191s
bitand duration5.137075047s
res unchecked_add0
res unchecked_mul0
res mul1
res unchecked_bitand0
res bitand0

the unchecked mul gives 0 instead of 1, as well as the unchecked and and the and.

hello @Norrin_Radix

You are using a 1_0 parameter which is not compatible with “bivariate operations”

  • add is modulo your modulo so 1 bit = 2, 1 + 1 % 2 == 0

  • unchecked mul as said not compatible with bivariate operations but as you call the unchecked operations there is no “safety net” and it lets you do whatever you want

  • mul result cannot potentially be relied on as the parameters are not compatible with bivariate ops, but some ops have edge cases for “weird parameters” and you might have been lucky

  • same for bitand and bivariate stuff

Try the 1_1 parameter and the results should all be correct modulo 2

If you need boolean arithmetic you may want to check the boolean module but the set of operation is likely not all you may want/need and is always bootstrapped

Cheers

Also make sure to use the --release flag when running with cargo

cargo run --release

Thanks I thought no carry would just give 1*1 = 1. The same for the and. Not sure why the carry is needed then. I tried it after posting and yes it works with message_1_carry_1, mult or and are twice longer than with 1_0 but at least it works. With a parallel implementation it should work.
Edit: Indeed --release is much faster.
Thanks

Essentially for the carry space when we compute a bivariate pbs we do:

lhs + msg_mod * rhs

this gives a ciphertext encrypting a representation of both lhs and rhs, with a well crafterd lookup table we can evaluate a bivariate function with a single input containing the above.

Only issue is you need space for both as multiplying by msg_mod means you don’t fit in just msg mod, to fit a msg that fits in msg_mod you need carry_mod >= msg_mod

essentially bit wise you have

for carry mod == msg mod = 2

if lhs = rhs = 1

lhs * msg_mod
carry | msg
    1 |   0

+ rhs

carry | msg
    1 |   1

if carry mod = 1 then the carry space does not exist and you have potentially undefined behavior

ok thanks, that’s funny, I did not know that.
I think, depending on the parameters, you can speed up your calculations.
from what I understand, the LUT is produced with

unchecked_evaluate_bivariate_function_assign(ct_left, ct_right, |x, y| {
            (x * y) % res_modulus
        });

So, if x=1 and y = 0, in the 1 bit scenario, you will produce one cell in the LUT, different from x=0 and y = 1, or x = 0 and y =0 while the result is the same.

So in our case, there will be 4 cells while only 2 are needed. If you add lhs and rhs together and consider only the msb, you get the result of the multiplication.

So in a message_1_carry_1 scenario |x, y| {(x + y) / res_modulus});

would give the same result (given that you keep both the lhs and the rhs at the same level).

(lhs + rhs)/res_modulus

That’s possible yes, but some parts of the library are not speciliazed for booleans