TFHE-rs v1.0: Stable CPU Backend

February 27, 2025
Jean-Baptiste Orfila

This month, Zama is releasing TFHE-rs v1.0, the first stable version of the TFHE-rs library. This marks a major milestone, stabilizing the high-level API for the x86 CPU backend while ensuring backward compatibility.

In other words, you can now rely on the TFHE-rs API without worrying about breaking changes in future updates.

The most significant improvement in this release is the refinement of key parameters, which enhances cryptographic security, preserves the performance and optimizes them for use in distributed protocols. This update also introduces an official handbook and a streamlined contribution process, both of which will be covered in detail in this article.

Notably, the probability of computational errors has been reduced to less than 2-128, all while maintaining performance. In practical terms, this means that the likelihood of an error is as negligible as breaking modern cryptographic standards.

A first edition of theTFHE-rs handbook

Alongside the release of TFHE-rs v1.0, Zama has published a first edition of its TFHE-rs handbook detailing all the implementations in the backend.

The TFHE-rs handbook covers:

  • The underlying algorithms, including low-level operations like bootstrapping;
  • Methods and algorithms for implementing operations at the homomorphic integer level, specifically for FheUint and FheInt;
  • Implementation details of key features, such as compression;
  • In-depth technical benchmarks for further analysis.

Another important point is that, unlike the documentation, the handbook focuses on Gaussian noise distribution, aligning with standard definitions used in academic literature.

Refined cryptographic parameters for the highest level of security

A core enhancement in this release is the refinement of cryptographic parameters, reducing the probability of computational errors from less than 2-64 to less than 2-128.

Although often hidden from users, cryptographic parameters are fundamental to TFHE-rs, ensuring the security, efficiency, and correctness of computations.

Until recently, TFHE-rs’ standard parameter sets guaranteed computational correctness with an error probability below 2-64—practically negligible for traditional client-server use cases. However, recent research suggests that in scenarios where both encrypted and decrypted data might be accessible, certain theoretical attacks could emerge. While still computationally infeasible with previous parameter sets—requiring  an average of  263 computations to succeed—this update further strengthens security against such possibilities.

Zama designed TFHE-rs to provide the highest level of security for all applications. With TFHE-rs v1.0, cryptographic parameters have been refined to reduce the computational error probability to below 2-128, aligning with standard cryptographic security levels and ensuring robustness—even in distributed protocols.

Typically, lowering the failure probability from 2-64 to 2-128 would at least double computation time. However, TFHE-rs limits the slowdown to around 10% by implementing a novel technique called drift mitigation (see more details in the associated scientific article).

As TFHE-rs serves as the backbone of Zama's blockchain protocol enabling developers to write confidential smart contracts using FHE. This new version is designed to better support distributed protocols—where private and public key generation may be shared among multiple users —TFHE-rs v1.0 now uses a TUniform noise distribution by default. This variant of Uniform avoids the constraints imposed by Gaussian distributions.

For users who prefer the traditional approach, cryptographic parameters based on the Gaussian distribution remain available.

The TFHE-rs documentation provides comprehensive guidance on selecting cryptographic parameters and understanding different distributions.

For deeper technical insights, benchmarks performed with the new parameter sets—including those for integer operations and low-level operations like bootstrapping—are also available in the documentation.

A practical example

Below is a simple demonstration of how to use TFHE-rs in a client-server setup.

To keep this example straightforward, we’ve intentionally left out advanced features like public key encryption and its associated Zero-Knowledge Proofs—which are commonly used in distributed environments to let users share a common encryption key and verify that ciphertexts are correctly formed.

This example focuses on the core workflow, simulating interactions between a Client and a Server:

  • Client encryption: The Client encrypts its data using its private keys and sends the ciphertexts to the Server.
  • Server-side computation: The Server processes the encrypted data without accessing the plaintext. If needed, it can compress the data for storage and decompress it later for further homomorphic operations.
  • Client decryption: Once the computation is complete, the Server returns the encrypted results to the Client, who decrypts them to obtain the final output.
use tfhe::prelude::*;
use tfhe::shortint::parameters::{COMP_PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_2_CARRY_2};
use tfhe::{
    set_server_key, CompressedCiphertextListBuilder, FheAsciiString, FheBool, FheInt64, FheUint16,
    FheUint2, FheUint32,
};

fn main() {
    // Use aliases for ease of use, do not use aliases for production (not stable through time)
    let config = tfhe::ConfigBuilder::with_custom_parameters(PARAM_MESSAGE_2_CARRY_2)
        .enable_compression(COMP_PARAM_MESSAGE_2_CARRY_2)
        .build();

    // On the client side, generate the ClientKey, must remain SECRET
    let client_key = tfhe::ClientKey::generate(config);
    // Also generate the ServerKey which will allow the server to perform computations, this one is
    // sent to the server
    let server_key = tfhe::ServerKey::new(&client_key);

    // Encrypt some values required by service provider
    let ct1 = FheUint32::encrypt(17_u32, &client_key);
    let ct2 = FheInt64::encrypt(-1i64, &client_key);
    let ct3 = FheBool::encrypt(false, &client_key);
    let ct4 = FheUint2::encrypt(3u8, &client_key);
    let ct5 = FheAsciiString::encrypt("1.0!", &client_key);

    // On the server side, once the server key has been received, set it as the active key
    set_server_key(server_key);

    // The server does some computations without seeing the data
    let ct1_res = ct1 + 25;
    let ct2_res = 43 + ct2;
    let ct3_res = ct3 & true;
    let ct4_res = ct4 - 1;
    let ct5_res = ct5.len().into_ciphertext();

    // We can compress the data to send or store smaller amounts of data to the client
    let compressed_list = CompressedCiphertextListBuilder::new()
        .push(ct1_res)
        .push(ct2_res)
        .push(ct3_res)
        .push(ct4_res)
        .push(ct5_res)
        .build()
        .unwrap();

    // On the client side after receiving the compressed data
    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 e: FheUint16 = compressed_list.get(4).unwrap().unwrap();

    let a: u32 = a.decrypt(&client_key);
    assert_eq!(a, 42);
    let b: i64 = b.decrypt(&client_key);
    assert_eq!(b, 42);
    let c = c.decrypt(&client_key);
    assert!(!c);
    let d: u8 = d.decrypt(&client_key);
    assert_eq!(d, 2);
    let e: u16 = e.decrypt(&client_key);
    assert_eq!(e, 4);

    // The compressed data can be reused by the server if it kept it on disk
    let recovered_a: FheUint32 = compressed_list.get(0).unwrap().unwrap();

    let new_a = recovered_a << 4u32;

    // The result can be sent uncompressed to the Client, which can also decrypt this new result
    let new_a: u32 = new_a.decrypt(&client_key);
    assert_eq!(new_a, a << 4);
}

Contributing to TFHE-rs

At Zama, being an open-source cryptography company isn’t just a requirement—it’s something we actively embrace. Openness drives innovation, collaboration, and the continuous improvement of our technologies.

TFHE-rs is built with this philosophy in mind, welcoming contributions in two main ways:

1. Contribute to TFHE-rs via Zama Bounty Program: Advancing FHE, One Challenge at a Time

The Zama Bounty Program rewards developers for tackling technical challenges that push TFHE-rs forward. Each quarter, a new TFHE-rs bounty is opened, and solutions from participants often lead to meaningful improvements in the library.

A recent example is the homomorphic string feature, which started as a bounty contribution and was later integrated into the main repository. Thanks to this addition, users can now run the following code to check whether a substring is contained within another string:

use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString};

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

    set_server_key(sks);

    // Encrypt without padding, does not hide the string length and has better performance
    let string = FheAsciiString::try_encrypt("TFHE-rs rocks!", &cks).unwrap();

    // Encrypt with padding, hide the true length of the string
    let search_string = FheAsciiString::try_encrypt_with_padding("is meh", 1, &cks).unwrap();

    // We can also use clear strings
    let clear_search_string = ClearString::new("rocks".to_string());

    // Does our initial string contain "is meh"?
    let does_not_contain = string.contains(&search_string);

    // Does our initial string contain "rocks"?
    let contains = string.contains(&clear_search_string);

    // Decrypt
    let decrypted_does_not_contain = does_not_contain.decrypt(&cks);
    let decrypted_contains = contains.decrypt(&cks);

    // Check all worked properly
    assert!(!decrypted_does_not_contain);
    assert!(decrypted_contains);
}

A detailed comparison of the benefits of this new feature can be found in the string documentation.

Several other bounty-driven contributions have also been integrated into TFHE-rs, including homomorphic SHA-256:

The next season of the Bounty Program is opening soon! Follow the official Bounty Program repository to participate. Looking forward to your contributions!

2. Contribute to TFHE-rs Directly: Build, Improve, and Innovate

We've introduced a streamlined contribution process to make it easier for developers and researchers to add new features and improvements to TFHE-rs. Whether you're building an application or experimenting with a novel prototype, you can integrate your work into the library by following these steps:

  1. Fork the repository and create a new branch;
  2. Align your code with existing conventions and commit your changes;
  3. Run the test suite to verify correctness;
  4. Rebase to the latest version of the main branch and open a pull request.

Once accepted, your contribution will be maintained over time, just like any other feature in the library.

What’s Next for TFHE-rs

Looking ahead, TFHE-rs is evolving with two key objectives:

  • User-centric features – Making FHE more intuitive and accessible by introducing features that simplify its use for a broader audience;
  • Performance & hardware support – Integrating state-of-the-art optimizations to improve homomorphic computations. A key focus is on expanding the GPU backend, which is already part of TFHE-rs and is now on track to match the stability and production readiness of the x86 CPU backend.

Additional links

Read more related posts

No items found.