> For the complete documentation index, see [llms.txt](https://docs.zama.org/tfhe-rs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/tfhe-rs/fhe-computation/advanced-features/rerand.md).

# Ciphertexts Rerandomization

This document explains the ciphertext re-randomization feature in TFHE-rs, designed to protect FHE computations against attacks under the sIND-CPA^D security model (`s` stands for strong), described in the paper [Drifting Towards Better Error Probabilities in Fully Homomorphic Encryption Schemes](https://eprint.iacr.org/2024/1718).

To be secure under that model, **TFHE-rs** provides a re-randomization primitive that allows users to re-randomize ciphertexts before they are used as inputs to a predefined FHE program `F`. In this context, `F` should be understood as any FHE computation that must remain secure under the sIND-CPA^D model. All encrypted inputs to `F` must be re-randomized prior to execution.

## Example: Re-randomization of two `FheUint64` values before addition

```rust
use tfhe::prelude::*;
use tfhe::shortint::parameters::v1_8::meta::cpu::V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
use tfhe::{
    generate_keys, set_server_key, CompressedCiphertextListBuilder, FheUint64,
    ReRandomizationContext, ReRandomizationMode
};

pub fn main() {
    // The chosen parameters have re-rand enabled and don't require an extra CompactPublicKey
    let (cks, sks) = generate_keys(V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128);

    let compact_public_encryption_domain_separator = *b"TFHE_Enc";
    let rerand_domain_separator = *b"TFHE_Rrd";

    set_server_key(sks);

    // We want to compute FheUint64 + FheUint64, prepare inputs
    let clear_a = rand::random::<u64>();
    let clear_b = rand::random::<u64>();
    let a = FheUint64::encrypt(clear_a, &cks);
    let b = FheUint64::encrypt(clear_b, &cks);

    // Simulate the data being stored on disk
    let mut builder = CompressedCiphertextListBuilder::new();
    builder.push(a);
    builder.push(b);
    let list = builder.build().unwrap();

    // Actual Re-Randomization context
    let c = {
        // Inputs are fetched from storage
        let mut a: FheUint64 = list.get(0).unwrap().unwrap();
        let mut b: FheUint64 = list.get(1).unwrap().unwrap();

        // Simulate a 256 bits nonce to make the execution unique
        let nonce: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());

        let mut re_rand_context = ReRandomizationContext::new(
            rerand_domain_separator,
            // First is the function description, second is a nonce
            [b"FheUint64+FheUint64".as_slice(), nonce.as_slice()],
            compact_public_encryption_domain_separator,
        );

        // Add ciphertexts to the context
        re_rand_context.add_ciphertext(&a);
        re_rand_context.add_ciphertext(&b);

        // Get the seeds for the rerandomization
        let mut seed_gen = re_rand_context.finalize();

        // Re-Randomize a and b
        // We can use the default mode which does not require an additional CompactPublicKey
        a.re_randomize(ReRandomizationMode::default(), seed_gen.next_seed().unwrap()).unwrap();
        b.re_randomize(ReRandomizationMode::default(), seed_gen.next_seed().unwrap()).unwrap();

        // Compute our function F
        a + b
    };

    // Check everything went well
    let dec: u64 = c.decrypt(&cks);
    assert_eq!(clear_a.wrapping_add(clear_b), dec);
}
```

Note that if ciphertexts require auxiliary metadata to perform the re-randomization, those can be added on the ciphertext using the `re_randomization_metadata_mut` accessors and then calling the `set_data` function on it:

```rust
use tfhe::prelude::*;
use tfhe::shortint::parameters::v1_8::meta::cpu::V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
use tfhe::{ClientKey, FheUint64};

pub fn main() {
    let cks = ClientKey::generate(V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128);

    let clear_a = rand::random::<u64>();
    let mut a = FheUint64::encrypt(clear_a, &cks);

    // Generate some random metadata simulating a 256 bits hash
    let rand_a: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());

    // Add it to the ciphertext a
    a.re_randomization_metadata_mut().set_data(&rand_a);
}
```

## Example: Re-randomization of a `ProvenCompactCiphertextList`

Untrusted user inputs received in a `ProvenCompactCiphertextList` should also be re-randomized. Here is how to do it:

```rust
use tfhe::prelude::*;
use tfhe::shortint::parameters::v1_8::meta::cpu::V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
use tfhe::zk::{CompactPkeCrs, ZkComputeLoad};
use tfhe::{
    CompactPublicKey, Config, FheBool, FheInt8, FheUint64, ProvenCompactCiphertextList,
    ReRandomizationContext, generate_keys, set_server_key,
};

pub fn main() {
    let config = Config::from(V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128);
    let (cks, sks) = generate_keys(config);
    let cpk = CompactPublicKey::new(&cks);

    let compact_public_encryption_domain_separator = *b"TFHE_Enc";
    let rerand_domain_separator = *b"TFHE_Rrd";

    let crs = CompactPkeCrs::from_config(config, 2048).unwrap();
    let metadata = b"rerand";

    set_server_key(sks);

    // Generate a list of ciphertexts
    let clear_a = rand::random::<u64>();
    let clear_b = rand::random::<i8>();

    let compact_list = ProvenCompactCiphertextList::builder(&cpk)
        .push(clear_a)
        .push(clear_b)
        .push(false)
        .build_with_proof_packed(&crs, metadata, ZkComputeLoad::Proof)
        .unwrap();

    // Simulate a 256 bits nonce
    let nonce: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());

    let mut re_rand_context = ReRandomizationContext::new(
        rerand_domain_separator,
        [b"expand".as_slice(), nonce.as_slice()],
        compact_public_encryption_domain_separator,
    );

    // Add the compact list to the context
    re_rand_context.add_ciphertext(&compact_list);

    let mut seed_gen = re_rand_context.finalize();

    // Verify, re_randomize and expand
    let expander = compact_list
        .verify_re_randomize_and_expand(&crs, &cpk, metadata, seed_gen.next_seed().unwrap())
        .unwrap();

    let a: FheUint64 = expander.get(0).unwrap().unwrap();
    let b: FheInt8 = expander.get(1).unwrap().unwrap();
    let c: FheBool = expander.get(2).unwrap().unwrap();

    let dec_a: u64 = a.decrypt(&cks);
    assert_eq!(dec_a, clear_a);
    let dec_b: i8 = b.decrypt(&cks);
    assert_eq!(dec_b, clear_b);
    let dec_c: bool = c.decrypt(&cks);
    assert!(!dec_c);
}
```

## Re-Randomized PRF

Here is an example of how to call the re-randomized PRF in the same setting:

```rust
use std::num::NonZeroU64;
use tfhe::prelude::{FheDecrypt, FheTryEncrypt};
use tfhe::shortint::parameters::v1_8::meta::cpu::V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
use tfhe::{
    generate_keys, re_randomized_keys_bitonic_shuffle, set_server_key, BitonicShuffleKeySize,
    FheInt8, FheUint8, PrfReRandomizationContext, RangeForRandom, ReRandomizationMode, Seed,
};

pub fn main() {
    // The chosen parameters have re-rand enabled and don't require an extra CompactPublicKey
    let (client_key, server_key) =
        generate_keys(V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128);

    set_server_key(server_key);

    let excluded_upper_bound = NonZeroU64::new(3).unwrap();
    let range = RangeForRandom::new_from_excluded_upper_bound(excluded_upper_bound);
    // Uses default domain separators and Blake3 for hashing during re-randomization
    let prf_rerand_context = PrfReRandomizationContext::default();
    let rerand_mode = ReRandomizationMode::default();

    // in [0, excluded_upper_bound[ = {0, 1, 2}
    // DANGER: Static Seed(0) given as an example only
    // use proper seeding strategy depending on use case
    let ct_res = FheUint8::generate_oblivious_pseudo_random_custom_range_and_re_randomize(
        Seed(0),
        &range,
        None,
        rerand_mode,
        &prf_rerand_context,
    )
    .unwrap();
    let dec_result: u8 = ct_res.decrypt(&client_key);

    let random_bits_count = 3;

    // in [0, 2^8[
    // DANGER: Static Seed(0) given as an example only
    // use proper seeding strategy depending on use case
    let ct_res = FheUint8::generate_oblivious_pseudo_random_and_re_randomize(
        Seed(0),
        rerand_mode,
        &prf_rerand_context,
    )
    .unwrap();
    let dec_result: u8 = ct_res.decrypt(&client_key);

    // in [0, 2^random_bits_count[ = [0, 8[
    // DANGER: Static Seed(0) given as an example only
    // use proper seeding strategy depending on use case
    let ct_res = FheUint8::generate_oblivious_pseudo_random_bounded_and_re_randomize(
        Seed(0),
        random_bits_count,
        rerand_mode,
        &prf_rerand_context,
    )
    .unwrap();
    let dec_result: u8 = ct_res.decrypt(&client_key);
    assert!(dec_result < (1 << random_bits_count));

    // in [-2^7, 2^7[
    // DANGER: Static Seed(0) given as an example only
    // use proper seeding strategy depending on use case
    let ct_res = FheInt8::generate_oblivious_pseudo_random_and_re_randomize(
        Seed(0),
        rerand_mode,
        &prf_rerand_context,
    )
    .unwrap();
    let dec_result: i8 = ct_res.decrypt(&client_key);

    // in [0, 2^random_bits_count[ = [0, 8[
    // DANGER: Static Seed(0) given as an example only
    // use proper seeding strategy depending on use case
    let ct_res = FheInt8::generate_oblivious_pseudo_random_bounded_and_re_randomize(
        Seed(0),
        random_bits_count,
        rerand_mode,
        &prf_rerand_context,
    )
    .unwrap();
    let dec_result: i8 = ct_res.decrypt(&client_key);
    assert!(dec_result < (1 << random_bits_count));

    // Shuffle
    let mut clear_values: Vec<u8> = (0..15).map(|_| rand::random()).collect();

    let encrypted: Vec<FheUint8> = clear_values
        .iter()
        .map(|&v| FheUint8::try_encrypt(v, &client_key).unwrap())
        .collect();

    // Depending on applications this won't be enough to guarantee a low enough collision
    // probability to have an unbiased shuffle, adapt to your use case
    let key_size = BitonicShuffleKeySize::num_bits(32);

    // DANGER: Static Seed(0) given as an example only
    // use proper seeding strategy depending on use case
    let rerand_result = re_randomized_keys_bitonic_shuffle(
        encrypted.clone(),
        key_size,
        Seed(0),
        rerand_mode,
        &prf_rerand_context,
    )
    .unwrap();

    clear_values.sort_unstable();

    let mut decrypted_rerand: Vec<u8> = rerand_result
        .iter()
        .map(|ct| ct.decrypt(&client_key))
        .collect();

    // Check the input values are still there
    decrypted_rerand.sort_unstable();
    assert_eq!(clear_values, decrypted_rerand);
}

```

## Managing legacy Re-Randomization API

Because of an API change in version 1.6 you may find yourself needing to manage older keys using the old API, the following example shows how it can be done:

```rust
use tfhe::prelude::*;
use tfhe::shortint::parameters::v1_5::meta::cpu::V1_5_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
use tfhe::shortint::parameters::v1_8::meta::cpu::V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128;
use tfhe::shortint::parameters::ReRandomizationConfiguration;
use tfhe::{
    generate_keys, set_server_key, ClientKey, CompactPublicKey, CompressedCiphertextListBuilder,
    FheUint64, ReRandomizationContext, ReRandomizationMode, ServerKey
};

pub fn main() {
    fn rerand_with_legacy_support(
        cks: ClientKey,
        sks: ServerKey,
        legacy_cpk: Option<CompactPublicKey>
    ) {
        let compact_public_encryption_domain_separator = *b"TFHE_Enc";
        let rerand_domain_separator = *b"TFHE_Rrd";

        set_server_key(sks);

        // We want to compute FheUint64 + FheUint64, prepare inputs
        let clear_a = rand::random::<u64>();
        let clear_b = rand::random::<u64>();
        let a = FheUint64::encrypt(clear_a, &cks);
        let b = FheUint64::encrypt(clear_b, &cks);

        // Simulate the data being stored on disk
        let mut builder = CompressedCiphertextListBuilder::new();
        builder.push(a);
        builder.push(b);
        let list = builder.build().unwrap();

        // Actual Re-Randomization context
        let c = {
            // Inputs are fetched from storage
            let mut a: FheUint64 = list.get(0).unwrap().unwrap();
            let mut b: FheUint64 = list.get(1).unwrap().unwrap();

            // Simulate a 256 bits nonce to make the execution unique
            let nonce: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());

            let mut re_rand_context = ReRandomizationContext::new(
                rerand_domain_separator,
                // First is the function description, second is a nonce
                [b"FheUint64+FheUint64".as_slice(), nonce.as_slice()],
                compact_public_encryption_domain_separator,
            );

            // Add ciphertexts to the context
            re_rand_context.add_ciphertext(&a);
            re_rand_context.add_ciphertext(&b);

            // Get the seeds for the rerandomization
            let mut seed_gen = re_rand_context.finalize();

            let rerand_mode = match legacy_cpk.as_ref() {
                // If you needed a legacy key for re-randomization before
                // this sets up the mode for you
                Some(pk) => pk.into(),
                // No legacy key, default mode will work for re-randomization
                None => ReRandomizationMode::default()
            };

            a.re_randomize(rerand_mode, seed_gen.next_seed().unwrap())
                                    .unwrap();
            b.re_randomize(rerand_mode, seed_gen.next_seed().unwrap())
                                    .unwrap();

            // Compute our function F
            a + b
        };

        // Check everything went well
        let dec: u64 = c.decrypt(&cks);
        assert_eq!(clear_a.wrapping_add(clear_b), dec);
    }

    for params in [
        // 1.5 did not have the new style more efficient API and is used as the legacy case
        V1_5_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128,
        V1_8_META_PARAM_CPU_2_2_KS_PBS_PKE_TO_SMALL_ZKV2_TUNIFORM_2M128,
    ] {
        let needs_rerand_cpk = matches!(
            params.rerand_configuration,
            Some(ReRandomizationConfiguration::LegacyDedicatedCompactPublicKeyWithKeySwitch),
        );

        let (cks, sks) = generate_keys(params);
        let cpk = if needs_rerand_cpk { Some(CompactPublicKey::new(&cks)) } else { None };
        rerand_with_legacy_support(cks, sks, cpk);
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.zama.org/tfhe-rs/fhe-computation/advanced-features/rerand.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
