# Compressing ciphertexts/keys

This document explains the mechanism and steps to compress ciphertext and keys to reduce the storage needed as well as transmission times.

Most **TFHE-rs** entities contain random numbers generated by a Pseudo Random Number Generator (PRNG). Since the implemented PRNG is deterministic, storing only the random seed used to generate those numbers preserves all necessary information. When decompressing the entity, using the same PRNG and the same seed will reconstruct the full chain of random values.

In **TFHE-rs**, compressible entities are prefixed with `Compressed`. For instance, a compressed `FheUint256` is declared as `CompressedFheUint256`.

In the following example code, we use the `bincode` crate dependency to serialize in a binary format and compare serialized sizes.

## Compressing Ciphertexts

### Compressing ciphertexts at encryption time

This example shows how to compress a ciphertext encrypting messages over 16 bits:

```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, CompressedFheUint16};

fn main() {
    let config = ConfigBuilder::default().build();
    let (client_key, _) = generate_keys(config);

    let clear = 12_837u16;
    let compressed = CompressedFheUint16::try_encrypt(clear, &client_key).unwrap();
    println!(
        "compressed size  : {}",
        bincode::serialize(&compressed).unwrap().len()
    );
    
    let decompressed = compressed.decompress();
    
    println!(
        "decompressed size: {}",
        bincode::serialize(&decompressed).unwrap().len()
    );

    let clear_decompressed: u16 = decompressed.decrypt(&client_key);
    assert_eq!(clear_decompressed, clear);
}
```

### Compression ciphertexts after some homomorphic computation

You can compress ciphertexts at any time, even after performing multiple homomorphic operations.

To do so, you need to build a list containing all the ciphertexts that have to be compressed. This list might contain ciphertexts of different types, e.g., FheBool, FheUint32, FheInt64,...
There is no constraint regarding the size of the list.

There are two possible approaches:

- **Single list**: Compressing several ciphertexts into a single list. This generally yields a better compression ratio between output and input sizes;
- **Multiple lists**: Using multiple lists. This offers more flexibility, since compression might happen at different times in the code, but could lead to larger outputs.

In more details, the optimal ratio is achieved with a list whose size is
equal to the `lwe_per_glwe` field from the `CompressionParameters`.

The following example shows how to compress and decompress a list containing 4 messages: one 32-bits integer, one 64-bit integer, one boolean, and one 2-bit integer.

```rust
use tfhe::prelude::*;
use tfhe::shortint::parameters::{
    MetaParametersFinder, Log2PFail, Constraint, Backend,
};
use tfhe::{
    set_server_key, CompressedCiphertextList, CompressedCiphertextListBuilder, FheBool,
    FheInt64, FheUint16, FheUint2, FheUint32,
};

fn main() {
    let parameters = MetaParametersFinder::new(
        Constraint::LessThanOrEqual(Log2PFail(-128.0)),
        Backend::Cpu
    )
    .with_compression(true)
    .find()
    .expect("Could not find suitable parameters");

    let ck = tfhe::ClientKey::generate(parameters);
    let sk = tfhe::ServerKey::new(&ck);

    set_server_key(sk);

    let ct1 = FheUint32::encrypt(17_u32, &ck);

    let ct2 = FheInt64::encrypt(-1i64, &ck);

    let ct3 = FheBool::encrypt(false, &ck);

    let ct4 = FheUint2::encrypt(3u8, &ck);

    let compressed_list = CompressedCiphertextListBuilder::new()
        .push(ct1)
        .push(ct2)
        .push(ct3)
        .push(ct4)
        .build()
        .unwrap();

    let serialized = bincode::serialize(&compressed_list).unwrap();

    println!("Serialized size: {} bytes", serialized.len());

    let compressed_list: CompressedCiphertextList = bincode::deserialize(&serialized).unwrap();

    let a: FheUint32 = compressed_list.get(0).unwrap().unwrap();
    let b: FheInt64 = compressed_list.get(1).unwrap().unwrap();
    let c: FheBool = compressed_list.get(2).unwrap().unwrap();
    let d: FheUint2 = compressed_list.get(3).unwrap().unwrap();

    let a: u32 = a.decrypt(&ck);
    assert_eq!(a, 17);
    let b: i64 = b.decrypt(&ck);
    assert_eq!(b, -1);
    let c = c.decrypt(&ck);
    assert!(!c);
    let d: u8 = d.decrypt(&ck);
    assert_eq!(d, 3);

    // Out of bound index 
    assert!(compressed_list.get::<FheBool>(4).unwrap().is_none());

    // Incorrect type
    assert!(compressed_list.get::<FheInt64>(0).is_err());

    // Correct type but wrong number of bits
    assert!(compressed_list.get::<FheUint16>(0).is_err());
}
```

## Compressing keys

### Compressing server keys

This example shows how to compress the server keys:

```rust
use tfhe::prelude::*;
use tfhe::{
    set_server_key, ClientKey, CompressedServerKey, ConfigBuilder, FheUint8,
};

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

    let cks = ClientKey::generate(config);
    let compressed_sks = CompressedServerKey::new(&cks);

    println!(
        "compressed size  : {}",
        bincode::serialize(&compressed_sks).unwrap().len()
    );

    let sks = compressed_sks.decompress();

    println!(
        "decompressed size: {}",
        bincode::serialize(&sks).unwrap().len()
    );

    set_server_key(sks);

    let clear_a = 12u8;
    let a = FheUint8::try_encrypt(clear_a, &cks).unwrap();

    let c = a + 234u8;
    let decrypted: u8 = c.decrypt(&cks);
    assert_eq!(decrypted, clear_a.wrapping_add(234));
}

```

## Compressed public keys

This example shows how to compress the classical public keys:

{% hint style="warning" %}
It is not currently recommended to use the `CompressedPublicKey` to encrypt ciphertexts without first decompressing them. If the resulting PublicKey is too large to fit in memory, it may result in significant slowdowns.

This issue has been identified and will be addressed in future releases.
{% endhint %}

```rust
use tfhe::prelude::*;
use tfhe::{ConfigBuilder, generate_keys, FheUint8, CompressedPublicKey};

fn main() {
    let config = ConfigBuilder::default().build();
    let (client_key, _) = generate_keys(config);

    let compressed_public_key = CompressedPublicKey::new(&client_key);

    println!("compressed size  : {}", bincode::serialize(&compressed_public_key).unwrap().len());

    let public_key = compressed_public_key.decompress();

    println!("decompressed size: {}", bincode::serialize(&public_key).unwrap().len());


    let a = FheUint8::try_encrypt(213u8, &public_key).unwrap();
    let clear: u8 = a.decrypt(&client_key);
    assert_eq!(clear, 213u8);
}
```

## Compressed compact public key

This example shows how to use compressed compact public keys:

```rust
use tfhe::prelude::*;
use tfhe::{
    generate_keys, CompactCiphertextList, CompressedCompactPublicKey,
    ConfigBuilder, FheUint8,
};

fn main() {
    let config = ConfigBuilder::default()
        .use_custom_parameters(
            tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128,
        )
        .build();
    let (client_key, _) = generate_keys(config);

    let public_key_compressed = CompressedCompactPublicKey::new(&client_key);

    println!(
        "compressed size  : {}",
        bincode::serialize(&public_key_compressed).unwrap().len()
    );

    let public_key = public_key_compressed.decompress();

    println!(
        "decompressed size: {}",
        bincode::serialize(&public_key).unwrap().len()
    );

    let compact_list = CompactCiphertextList::builder(&public_key)
        .push(255u8)
        .build();
    let expanded = compact_list.expand().unwrap();
    let a: FheUint8 = expanded.get(0).unwrap().unwrap();

    let clear: u8 = a.decrypt(&client_key);
    assert_eq!(clear, 255u8);
}

```
