Encrypted Arrays with RadixCiphertext

I’m trying to perform vectorized operations using CpuFheUintXXArray data type in TFHE-rs. I’m using RadixCiphertext instead of FheUintXX because it seems that CpuFheUintXXArray does not support holding FheUintXX types.

This is my code:

use std::time::Instant;
use tfhe::integer::{gen_keys_radix, IntegerRadixCiphertext, RadixCiphertext};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS;
use tfhe::{CpuFheUint32Array, ClearArray, ConfigBuilder, generate_keys, set_server_key, FheUint32};
use tfhe::array::Slicing;
use tfhe::prelude::*;
fn main() {
    let num_block = 6;
    let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);

    let config = ConfigBuilder::default().build();
    let (cks, sks) = generate_keys(config);
    set_server_key(sks);

    let msg1 = 12u32;
    let msg2 = 11u32;
    let msg3 = 9u32;

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

    let ct_4 = client_key.encrypt(1u32);
    let ct_5 = client_key.encrypt(1u32);
    let ct_6 = client_key.encrypt(1u32);

    let t1 = vec![ct_1.clone(), ct_2.clone(), ct_3.clone()];
    let tt1 = CpuFheUint32Array::new(t1, vec![1, 3]);

    let t2 = vec![ct_4.clone(), ct_5.clone(), ct_6.clone()];
    let tt2 = CpuFheUint32Array::new(t2, vec![1, 3]);

    let clear = ClearArray::new(vec![1u32, 2u32, 3u32], vec![1, 3]);
    let res1 = &tt1 * &tt2;
    let res2 = &tt2 + &tt1;
    let res3 = &tt1 + &clear;


    let j0 = clear.as_slice().container().clone().into_inner()[0];
    let j1 = clear.as_slice().container().clone().into_inner()[1];
    let j2 = clear.as_slice().container().clone().into_inner()[2];

    let triv0: RadixCiphertext = server_key.create_trivial_radix(j0, num_block);
    let triv1: RadixCiphertext = server_key.create_trivial_radix(j1, num_block);
    let triv2: RadixCiphertext = server_key.create_trivial_radix(j2, num_block);

    let trivss = CpuFheUint32Array::new(vec![triv0, triv1, triv2], vec![1, 3]);
    let res4 = &tt2 * &trivss;

    let end_time = start_time.elapsed();
    let tmp_t1 = tt1.container();
    let tmp_t2 = tt2.container();
    let tmp_res1 = res1.container();
    let tmp_res2 = res2.container();
    let tmp_res3 = res3.container();
    let tmp_res4 = res4.container();

    let dec_t1: Vec<u32> = tmp_t1.iter().map(|x| {client_key.decrypt(x)}).collect();
    let dec_t2: Vec<u32> = tmp_t2.iter().map(|x| {client_key.decrypt(x)}).collect();
    let dec_res1: Vec<u32> = tmp_res1.iter().map(|x| {client_key.decrypt(x)}).collect();
    let dec_res2: Vec<u32> = tmp_res2.iter().map(|x| {client_key.decrypt(x)}).collect();
    let dec_res3: Vec<u32> = tmp_res3.iter().map(|x| {client_key.decrypt(x)}).collect();
    let dec_res4: Vec<u32> = tmp_res4.iter().map(|x| {client_key.decrypt(x)}).collect();

    println!("{:?}", dec_t1);
    println!("{:?}", dec_t2);
    println!("{:?}", dec_res1);
    println!("{:?}", dec_res2);
    println!("{:?}", dec_res3);
    println!("{:?}", dec_res4);

}

But it outputs non-sense results (except the first two lines):

[12, 11, 9]
[1, 1, 1]
[2938, 1768, 3510]
[1505, 2086, 462]
[7, 2530, 1703]
[4012, 1201, 2203]

I think this because using CpuFheUint32Array requires the keys generated using the default config builder (e.g., let config = ConfigBuilder::default().build(); let (cks,sks)=generate_keys(config); set_server_key(sks);). Thus, the operations are performed using the sks key.

Does this have a solution?

Thanks in advance!

Yes the problem is exactly the fact that you are using 2 different keys,


   let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2_KS_PBS, num_block);

    let config = ConfigBuilder::default().build();
    let (cks, sks) = generate_keys(config);

client_key and cks are completely different keys, so what you encrypt with one cannot be decrypted with the other. And the server keys are meant for the client key that they came with, so mixing them will create wrong results

We are indeed missing some convertion functions to make this easier but you could write your own to bypass this.

these should work

// Vec<FheUint64> -> FheUint64Array
let vec: Vec<_> = my_vec_of_fheuint64
     .into_iter()
     .map(|uint| uint.into_raw_parts().0)
     .collect();
let shape = vec![vec.len()];
CpuFheUint64Array::new(vec, shape)
// FheUint64Array -> Vec<FheUint64>
value
   .into_container()
   .into_iter()
   .map(|radix| FheUint64::try_from(radix))
   .collect::<Result<Vec<_>, _>()
   .unwrap();

Thank you so much for your helpful response.

For converting FheUint64Array to Vec<FheUint64>, I need to have type of value to be CpuFheInt8Array and return Vec<FheInt8>. But it seems that the code snippet you provided works only Uint64 types. In the function below, the code panics at FheInt8::try_from(radix), but it works fine it is FheUint64::try_from(radix)

pub fn arr2vec(ctxt_arr: CpuFheInt8Array) -> Vec<FheInt8> {
    let res = ctxt_arr
        .into_container()
        .into_iter()
        .map(|radix: SignedRadixCiphertext| FheInt8::try_from(radix)) //<-- error here
        .collect::<Result<Vec<_>, _>>()
        .unwrap();

    // The following is to cast FheUint64 to FheInt8
    let n: Vec<FheInt8> = res.into_iter().map(|v| Ctxt::cast_from(v)).collect();
    n
}

Thanks again!

For FheInt try_from does not exists indeed so you have to do something different

This will work

use tfhe::{
    generate_keys, set_server_key, ClearArray, ConfigBuilder, CpuFheInt8Array, CpuFheIntArray,
    CpuFheUint64Array, FheInt, FheIntId, FheUint, FheUint32,
};
use tfhe::{prelude::*, CpuFheUintArray, FheUintId};

fn uint_array_to_uint_vec<Id: FheUintId>(array: CpuFheUintArray<Id>) -> Vec<FheUint<Id>> {
    array
        .into_container()
        .into_iter()
        .map(|radix| FheUint::<Id>::try_from(radix))
        .collect::<Result<Vec<_>, _>>()
        .unwrap()
}

fn uint_vec_to_uint_array<Id: FheUintId>(vec: Vec<FheUint<Id>>) -> CpuFheUintArray<Id> {
    let data = vec
        .into_iter()
        .map(|fhe_uint| fhe_uint.into_raw_parts().0)
        .collect::<Vec<_>>();

    let len = data.len();
    CpuFheUintArray::new(data, vec![len])
}

fn int_array_to_int_vec<Id: FheIntId>(array: CpuFheIntArray<Id>) -> Vec<FheInt<Id>> {
    array
        .into_container()
        .into_iter()
        .map(|radix| {
            FheInt::<Id>::from_raw_parts(
                radix,
                Id::default(),
                Default::default(),
                Default::default(),
            )
        })
        .collect::<Vec<_>>()
}

fn int_vec_to_int_array<Id: FheIntId>(vec: Vec<FheInt<Id>>) -> CpuFheIntArray<Id> {
    let data = vec
        .into_iter()
        .map(|fhe_int| fhe_int.into_raw_parts().0)
        .collect::<Vec<_>>();

    let len = data.len();
    CpuFheIntArray::new(data, vec![len])
}

fn main() {
    let config = ConfigBuilder::default().build();
    let (cks, sks) = generate_keys(config);
    set_server_key(sks);

    let array = CpuFheUint64Array::try_encrypt([1u32, 2, 3].as_slice(), &cks).unwrap();
    let vec = uint_array_to_uint_vec(array);
    let array = uint_vec_to_uint_array(vec);

    let derypted: Vec<u32> = array.decrypt(&cks);
    println!("{derypted:?}");

    let array = CpuFheInt8Array::try_encrypt([-1i32, -2, -3].as_slice(), &cks).unwrap();
    let vec = int_array_to_int_vec(array);
    let array = int_vec_to_int_array(vec);

    let derypted: Vec<i32> = array.decrypt(&cks);
    println!("{derypted:?}");
}

1 Like