Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a Fully Homomorphic Version of the AES-128 Cryptosystem using TFHE-rs #135

Open
zaccherinij opened this issue Dec 6, 2024 · 47 comments

Comments

@zaccherinij
Copy link
Collaborator

zaccherinij commented Dec 6, 2024

Overview

The goal of this bounty is to implement a homomorphic version of the AES-128 cryptosystem using the TFHE-rs library. The primary focus of the implementation should be on performance optimization. The implementation is expected to consist of two main blocks: KeyExpansion and Encryption/Decryption. This split is motivated by the possibility of performing key expansion as an offline phase, which may not always be required for all use cases.

You are encouraged to utilize recent research results and optimization techniques. If new cryptographic primitives or parameters are introduced, they must ensure:

  • A failure probability for operations lower than 2^-64.
  • A security level of at least 2^128.

Alternatively, you may focus on achieving the fastest possible implementation using the existing parameter sets provided in TFHE-rs. If you decide to use the WoPBS primitives, the parameter sets might need adjustment to meet the requirements above, as the provided sets are only examples for an experimental feature.

What We Expect

We expect a complete FHE AES-128 implementation. For reference, you may consult:

  • The ISO/IEC 18033-4 standard.
  • The tfhe-csprng implementation.
  • Other Rust implementations of AES, such as the aes crate.

You are allowed to use any API from the TFHE-rs library. The implementation must:

  1. Be tested using standard test vectors as well as randomly generated test cases.
  2. Use the noise-asserts feature (available only in main). We recommend starting with the commit 38a7e4feef7d398b8e7a6f8f8d02e285855396ec
    or any later commit that includes bug fixes or performance improvements.

Additionally, you are required to provide a small executable that:

  • Takes as input a number of outputs, an IV, and a key.
  • Generates the requested number of AES values using a cleartext implementation (e.g., the aes crate) as a reference.
  • Produces the same values homomorphically in FHE, decrypts them, and verifies correctness.

Program Inputs

You can use the clap library to parse command-line flags. The program inputs should be named as follows:

  • --number-of-outputs
  • --iv
  • --key

Runtime Output

The executable must print FHE runtime details (excluding encryption and decryption times) in the following format:

AES key expansion took: {key_expansion_elapsed:?}
AES of #{number_of_outputs} outputs computed in: {elapsed:?}

The elapsed variables should be computed using std::time::Instant::elapsed() on the relevant start instant.

Additional Requirements

A README file must accompany the submission, explaining:

  • How to use the FHE implementation.
  • How to run the provided executable.

Benchmarking

All benchmarks will be conducted on an AWS hpc7a.96xlarge instance. When benchmarking, ensure that:

  • AVX512 is enabled by using the nightly-avx512 feature.
  • The implementation is compiled with a modern nightly toolchain.

Reward

🥇Best submission: up to $5,000

To be considered best submission, a solution must be efficient, effective and demonstrate a deep understanding of the core problem. Alongside the technical correctness, it should also be submitted with a clean code, clear explanations and a complete documentation.

🥈Second-best submission: up to $3,000

For a solution to be considered the second best submission, it should be both efficient and effective. The code should be neat and readable, while its documentation might not be as exhaustive as the best submission, it should cover the key aspects of the solution.

🥉Third-best submission: up to $2,000

The third best submission is one that presents a solution that effectively tackles the challenge at hand, even if it may have certain areas of improvement in terms of efficiency or depth of understanding. Documentation should be present, covering the essential components of the solution.

Register

Step 1: Registration

Click here to register for the TFHE-rs Bounty. Fill out the registration form with your information. Once you fill out the form, you will receive a confirmation email with a link to the submission portal for when you are ready to submit your code.

Note

Check your spam folder in case you don't receive the confirmation email. If you haven't received it within 24 hour, please contact us by email at [email protected].

Step 2: Work on the Challenge

Read through the Bounty details and requirements carefully. Use the provided resources and create your own GitHub repository to store your code.
If you have any questions during your work, feel free to comment directly in the Bounty issue and our team will be happy to assist you.

Step 3: Submission

Once you have completed your work, upload your completed work to the submission portal using the link provided in the confirmation email.

Note

The deadline for submission is February, 9th 2025 (23:59, Anywhere On Earth). Late submissions will not be considered.

We wish you the best of luck with the challenge!

Support

  • Comment on this issue with any questions regarding this bounty.
  • Email for private questions: [email protected]
  • Join the Zama community channels here.
@Jineshbansal
Copy link

Hi @zaccherinij, could you please explain where homomorphic encryption should be applied in this process? Are you suggesting encrypting the key and block using homomorphic encryption first, and then performing AES encryption?

@IceTDrinker
Copy link
Member

IceTDrinker commented Jan 15, 2025

hello @Jineshbansal

We expect the given IV and the key provided in command line to be encrypted in FHE, then as indicated in the summary we expect both KeyDerivation and Encrypt/Decrypt to be implemented and tested.

The Key derivation time must be measured standalone and then the time to run the AES in counter mode for the given key and IV for the given number of output is going to be measured, fully in FHE, we don't expect the encryption time of the IV and Key to FHE to be measured.

Does that clear things up for you ?

Cheers

@Jineshbansal
Copy link

Hey @IceTDrinker,

This clears up most of our doubts. However, I wanted to confirm: will we be getting the client key or a decryption key to access the encrypted data?

In AES, the S-Box is used during the encryption process. However, if the data is already encrypted, we can’t directly work with the S-Box without the decryption key, to access specific elements in the S-Box, we rely on indexes provided by the FHE-encrypted vector. Since these indexes are themselves encrypted, we cannot directly access the required elements in the S-Box by this way :

// [[  0,  1,  2,  3]
//  [  4,  5,  6,  7]
//  [  8,  9, 10, 11]
//  [ 12, 13, 14, 15]]
let xss: FheArrayBase<&[..], tfhe::FheUint32Id> = xs.slice(&[0..1, 0..0]);
   
//[[  1,  1,  1,  1]
//  [  1,  1,  1,  1]
//  [  1,  1,  1,  1]
//  [  1,  1,  1,  1]]
let yss:  FheArrayBase<&[..], tfhe::FheUint32Id> = ys.slice(&[0..xss(encrypted thing), 0..xss(encrypted thing)]);

Let me know your thoughts!

@lla-dane
Copy link

lla-dane commented Jan 16, 2025

Like if we want to set a bool parameter in the while loop like this but it is of type FheBool

   while b != 0 (type: FheBool) {
        ..
        } 

Normally we would just decrpyt the FheBool and move forward, so can we use the client key with which the given IV and the key are Fhe encrypted with. Just a thought :) @IceTDrinker

@IceTDrinker
Copy link
Member

@IceTDrinker
Copy link
Member

IceTDrinker commented Jan 17, 2025

Note that the number of outputs is in clear, so you can iterate n times knowing how many values we want as output

@a104995
Copy link

a104995 commented Jan 17, 2025 via email

@lla-dane
Copy link

lla-dane commented Jan 19, 2025

What is the meaning of number-of-outputs that will be sent in the command line, and also in benchmarking, there is a little bit confusion. Do we have to provide all the benchmarking on AWS hpc7a.96xlarge instance, or you guys will do it while testing/judging ? @IceTDrinker

@lla-dane
Copy link

lla-dane commented Jan 19, 2025

Do we had to build a library in which a user can call the function like aes_encryption. So does this mean that user will call the set_server_key by their own before using our library. Is it right? @IceTDrinker

@IceTDrinker
Copy link
Member

What is the meaning of number-of-outputs that will be sent in the command line, and also in benchmarking, there is a little bit confusion. Do we have to provide all the benchmarking on AWS hpc7a.96xlarge instance, or you guys will do it while testing/judging ? @IceTDrinker

number-of-outputs is how many times the executable should run the AES in counter mode, yielding number-of-outputs encrypted values that must match the clear evaluation of the AES in CTR mode on the clear IV an key

The benchmarking will be done by us to avoid unfaithful results.

having the user cal set_server_key is fine as long as you indicate it in the usage of your function @lla-dane

@lla-dane
Copy link

Use the noise-asserts feature (available only in main). We recommend starting with the commit 38a7e4feef7d398b8e7a6f8f8d02e285855396ec
or any later commit that includes bug fixes or performance improvements.

can you please elaborate on this, we didn't understand anything. @IceTDrinker

@IceTDrinker
Copy link
Member

Use the noise-asserts feature (available only in main). We recommend starting with the commit 38a7e4feef7d398b8e7a6f8f8d02e285855396ec
or any later commit that includes bug fixes or performance improvements.

can you please elaborate on this, we didn't understand anything. @IceTDrinker

@lla-dane

you have to enable the noise-asserts feature while building to make sure that the noise levels don't go above the predefined levels if you are using shortint APIs, we will run the code you provide with that feature to make sure everything work correctly, it checks the validity of the FHE operations from a noise perspective. The good news is this is now available in the 0.11.x line of release and main but it was not available in 0.10.x when we wrote this bounty

@TianWZhang
Copy link

I run into an error when I enable the nightly-avx512 feature. I have set the toolchain to nightly rust with version rustc 1.86.0-nightly (f3d1d47fd 2025-01-20). Here is the config of tfhe:

tfhe = { version = "0.11.1", features = ["integer", "noise-asserts", "nightly-avx512"] }

Here are the errors when I compile:

error[E0308]: mismatched types
     --> /home/xxx/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pulp-0.20.0/src/core_arch/mod.rs:42:62
      |
42    |                   unsafe { arch::$func $(::<$($generic,)*>)?($($arg,)*) }
      |                            -----------                         ^^^^ expected `*mut __m512i`, found `*mut i32`
      |                            |
      |                            arguments to this function are incorrect
      |
     ::: /home/xxx/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pulp-0.20.0/src/core_arch/x86/avx512f.rs:5:5
      |
5     | /     delegate! {
6     | |         fn _mm512_abs_epi32(a: __m512i) -> __m512i;
7     | |         fn _mm512_mask_abs_epi32(src: __m512i, k: __mmask16, a: __m512i) -> __m512i;
8     | |         fn _mm512_maskz_abs_epi32(k: __mmask16, a: __m512i) -> __m512i;
...     |
3423  | |         fn _mm_comi_round_sd<const IMM5: i32, const SAE: i32>(a: __m128d, b: __m128d) -> i32;
3424  | |     }
      | |_____- in this macro invocation
      |
      = note: expected raw pointer `*mut std::arch::x86_64::__m512i`
                 found raw pointer `*mut i32`
note: function defined here

I’m wondering if the errors are due to my laptop not supporting AVX-512 optimization or if they stem from some mismatches on the tfhe.rs side. Could you help me check this? Thank you!

@TianWZhang
Copy link

We should not only implement a FHE-AES block cipher but also FHE-AES in counter mode. Is right correct? If we only provide number-of-outputs, iv and key in the command line, where are the plaintext messages that needs to be encrypted by AES128? Should we also provide them in the command line or use standard test vectors as well as randomly generated test cases?

@IceTDrinker
Copy link
Member

@TianWZhang for the issue you encounter use the toolchain written in toolchain.txt

the AES counter mode mode just encrypts the same values incremented several times

https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)

@TianWZhang
Copy link

@IceTDrinker Thank you very much. Using the toolchain written in toolchain.txt solved my problem.

@sharkbot1
Copy link

Hi @IceTDrinker. I was wondering what is the approximate value of --number-of-outputs, a few, tens, or hundreds?

@IceTDrinker
Copy link
Member

Hi @IceTDrinker. I was wondering what is the approximate value of --number-of-outputs, a few, tens, or hundreds?

we'll likely adapt to the runtimes we measure to have something long enough that we avoid variability but short enough that it does not take hours to get a representative benchmark of a solution

@lla-dane
Copy link

Image
@IceTDrinker, Me and my team are working on this architecture, can you please confirm, if we are in the right direction. Thanks

@IceTDrinker
Copy link
Member

IceTDrinker commented Jan 27, 2025

@IceTDrinker, Me and my team are working on this architecture, can you please confirm, if we are in the right direction. Thanks

That looks correct, key and IV are FHE encrypted, then you do the AES computation that yield C1, ... CN

@lla-dane
Copy link

lla-dane commented Jan 30, 2025

Hey, so is there any way to pull this off ? @IceTDrinker

let mut file = File::create("output.txt").expect("Unable to create file");
        for i in 0..16 {
            let result: FheUint<FheUint8Id> = output_encryption[i];
            writeln!(file, "{:?}", result).expect("Unable to write data");
        }

because this is giving me error that, FheUint<FheUint8Id> does not implement the Debug trait.

Would we also have to return the Fhe encrypted ciphertext in the runtime output, or just the time elapsed in the key expansion and AES encryption data ?

@IceTDrinker
Copy link
Member

Hey, so is there any way to pull this off ? @IceTDrinker

let mut file = File::create("output.txt").expect("Unable to create file");
for i in 0..16 {
let result: FheUint = output_encryption[i];
writeln!(file, "{:?}", result).expect("Unable to write data");
}
because this is giving me error that, FheUint<FheUint8Id> does not implement the Debug trait.

Would we also have to return the Fhe encrypted ciphertext in the runtime output, or just the time elapsed in the key expansion and AES encryption data ?

What are you trying to achieve here ?

Here having the values returned is not necessary as it could be that ciphertexts are used directly in memory.

We do require a check at the end that all decrypted FHE values correspond to the clear AES 128 computations

@lla-dane
Copy link

lla-dane commented Jan 30, 2025

We do require a check at the end that all decrypted FHE values correspond to the clear AES 128 computations

We are already doing this.

Just wanted to see what the Fhe encrpyted ciphertexts looks like by printing it somewhere.

@IceTDrinker
Copy link
Member

We do require a check at the end that all decrypted FHE values correspond to the clear AES 128 computations

We are already doing this.

Just wanted to see what the Fhe encrpyted ciphertexts looks like by printing it somewhere.

you can try into_raw_parts to get access to lower level entities some of them should have the debug trait enabled

@Jineshbansal
Copy link

We are nearly completed with the implementation here , Would It be possible to get our code reviewed once? @IceTDrinker

@IceTDrinker
Copy link
Member

We are nearly completed with the implementation here , Would It be possible to get our code reviewed once? @IceTDrinker

That's what the submission process is about, once at the deadline we'll review the code, normally you should have tested against the clear equivalent of AES 128

@lla-dane
Copy link

Do we also include the time (~5sec) it take to execute set_server_key(server_key) function in the time elapsed to execute AES encryption data? @IceTDrinker

@IceTDrinker
Copy link
Member

Do we also include the time (~5sec) it take to execute set_server_key(server_key) function in the time elapsed to execute AES encryption data? @IceTDrinker

This is way too slow for a set_server_key do you use the release flag of cargo ?

@lla-dane
Copy link

Haah !! now I run it will the release flag, and it takes almost no time with trivial encryption. How did that happen ?

@IceTDrinker
Copy link
Member

Haah !! now I run it will the release flag, and it takes almost no time with trivial encryption. How did that happen ?

that’s the power of compiler optimization

https://en.m.wikipedia.org/wiki/Optimizing_compiler

@lla-dane
Copy link

lla-dane commented Feb 2, 2025

The executable must print FHE runtime details (excluding encryption and decryption times) in the following format:
AES key expansion took: {key_expansion_elapsed:?}
AES of #{number_of_outputs} outputs computed in: {elapsed:?}

Here in this line AES of #{number_of_outputs} outputs computed in: {elapsed:?}, we will print the whole time it takes to execute the main function which includes all key_expansion, encyption, decryption, cross-checking ? right ?

or just the (encryption + decryption) part
@IceTDrinker

@IceTDrinker
Copy link
Member

The executable must print FHE runtime details (excluding encryption and decryption times) in the following format:
AES key expansion took: {key_expansion_elapsed:?}
AES of #{number_of_outputs} outputs computed in: {elapsed:?}

Here in this line AES of #{number_of_outputs} outputs computed in: {elapsed:?}, we will print the whole time it takes to execute the main function which includes all key_expansion, encyption, decryption, cross-checking ? right ?

or just the (encryption + decryption) part @IceTDrinker

one you have encrypted IV and key in FHE we want all the FHE computation before you verify it's correct @lla-dane

@allanbrondum
Copy link

Hi! How are the encryption scheme parameters defined in tfhe-rs "calculated"? As I understand it all the parameter sets defined have 128 bits of security and less than 1/2^64 probability of decryption error as long as noise level is below the max noise level (e.g. https://docs.rs/tfhe/latest/src/tfhe/shortint/parameters/classic/gaussian/p_fail_2_minus_64/ks_pbs.rs.html). But how are the properties calculated? Can the properties be reproduced by some tool or calculation?

@IceTDrinker
Copy link
Member

Hi! How are the encryption scheme parameters defined in tfhe-rs "calculated"? As I understand it all the parameter sets defined have 128 bits of security and less than 1/2^64 probability of decryption error as long as noise level is below the max noise level (e.g. https://docs.rs/tfhe/latest/src/tfhe/shortint/parameters/classic/gaussian/p_fail_2_minus_64/ks_pbs.rs.html). But how are the properties calculated? Can the properties be reproduced by some tool or calculation?

We have an optimization method to generate such parameters, you can check an implementation of such an optimizer in the concrete project: https://github.com/zama-ai/concrete/tree/main/compilers/concrete-optimizer

@allanbrondum
Copy link

@IceTDrinker Thanks, it makes sense now. Except the noise distribution parameters, e.g.:

        lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
            3.5539902359442825e-06,
        )),
        glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
            2.845267479601915e-15,
        )),

How do you calculate them based on the output of concrete optimizer?

@IceTDrinker
Copy link
Member

@IceTDrinker Thanks, it makes sense now. Except the noise distribution parameters, e.g.:

        lwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
            3.5539902359442825e-06,
        )),
        glwe_noise_distribution: DynamicDistribution::new_gaussian_from_std_dev(StandardDev(
            2.845267479601915e-15,
        )),

How do you calculate them based on the output of concrete optimizer?

Normally the optimizer has so called security curves that allow to estimate secure noise for a given security level and a given input lwe dimension, I'm unclear on how exactly the optimizer outputs those

@rostin79s
Copy link

Hi,
I have a few questions regarding the requirements:

  1. Is only iv FHE encrypted and do we need to compute iv, iv+1, .. iv+n homomorphically?

  2. Regarding the nightly toolchain, my system doesn't support AVX-512. Is there any configuration I need to do, or will it be done when benchmarking?

  3. About the second print time, "AES of #{number_of_outputs} outputs computed in: {elapsed:?}",
    Is this the time it takes for key expansion + aes encryption and decryption of all blocks in FHE?

  4. The noise assert feature returns an error when I'm trying to do LWE addition, and my solution heavily relies on additions. The wopbs parameter set I generated from the concrete optimizer is in line with the required security level and p_fail, and I tested it extensively and it works correctly. I'm not sure what to do.

  5. For the testing with standard vectors and random test cases, do we provide a function that does this testing in our codebase?

  6. Do we provide the final binary in our repo? or will you build the binary?

Thanks

@IceTDrinker
Copy link
Member

IceTDrinker commented Feb 7, 2025

Hello @rostin79s

  1. yes already answered above
  2. Benchmarking will be done by us for obvious fairness reasons and equal hardware
  3. As indicated you have two timings to report, the number of outputs timing is only on the aes encryption part, we don't expect a decryption at that point (potential use case is using the AES as a PRNG so we only want the encryption)
  4. It means that you have a max noise level that you may not be respecting, the max noise level if you generated parameters is linked to the norm 2, so if you have a norm 2 of X you should put floor(sqrt(X)) as a Max noise level, are you sure however that those parameters take into account the additions you are doing ?
  5. Yes
  6. No binary in your repo, we'll compile from your submission if the code is deemed safe to compile/run

Regards

@allanbrondum
Copy link

@IceTDrinker If you have an output like this from concrete-optimizer, what would you set max_noise_level to in the tfhe-rs parameters?

  - 2: # bits
    -ln2:   k,  N,    n, br_l,br_b, ks_l,ks_b,  cost, p_error
    - 0 :   3,  9,  745,    1, 18,     3,  4,     47, 5.0e-20
    - 1 :   3,  9,  754,    1, 18,     3,  4,     47, 4.9e-20
    - 2 :   4,  9,  755,    1, 23,     3,  4,     62, 4.6e-20
    - 3 :   4,  9,  755,    1, 23,     3,  4,     62, 4.8e-20
    - 4 :   4,  9,  755,    1, 23,     3,  4,     62, 5.3e-20
    - 5 :   4,  9,  757,    1, 23,     3,  4,     63, 4.3e-20
    - 6 :   4,  9,  763,    1, 23,     3,  4,     63, 4.8e-20
    - 7 :   4,  9,  745,    1, 23,     5,  3,     68, 4.9e-20
    - 8 :   3,  9,  711,    3,  9,     4,  3,     86, 4.8e-20
    - 9 :   3,  9,  715,    3,  9,     4,  3,     87, 4.4e-20

What max_noise_level would respectively line 2, 8 and 10 map to? Is it just the value in the column "ln2" or is it 2^"ln2"?

@IceTDrinker
Copy link
Member

ln2 = log norm 2 so norm2 = 2^(ln2)

and then

max_noise_level = floor(sqrt(norm2))

@IceTDrinker
Copy link
Member

IceTDrinker commented Feb 7, 2025

and it gives you the max number of addition/max clear multiplication you can do

you can at most do max_noise_level additions, or mulitply by max_noise_level at most

@rostin79s
Copy link

rostin79s commented Feb 7, 2025

How do I set the max noise level for the "WopbsParameters" parameter set? it is not defined in its struct.
I found here that it sets it automatically using the message and carry modulus: link
Is there anyway around this to set it manually based on norm2?

@IceTDrinker
Copy link
Member

IceTDrinker commented Feb 7, 2025

wopbs is experimental... try to define a PBS Parameters struct linked to your params with the right MaxNoiseLevel

if you have a pbs and wopbs param set it won't bother you as far as I can tell

@rostin79s
Copy link

This worked thanks.
Quick question, is it possible to compute multiple Lookup tables with a single wopbs call using the integer or shortint API?
For example, I want to circuit bootstrap 8 bits, then apply 3 look-up tables and output 24 bits.

@IceTDrinker
Copy link
Member

I don't think we have that at the moment in shortint/integer, asking people who know these primitives better.

@IceTDrinker
Copy link
Member

This worked thanks. Quick question, is it possible to compute multiple Lookup tables with a single wopbs call using the integer or shortint API? For example, I want to circuit bootstrap 8 bits, then apply 3 look-up tables and output 24 bits.

One thing to note is that once you have your extracted bits you can "just" evaluate 3 lookups with those bits, there is a chance of it being the most efficient (and it can be parallelized per LUT by running each one in a separate thread).

Otherwise you can apply a so called "Many LUT" technique to the wopbs

You pretend to work over 10 bits, which will give you enough space for 4 LUTs of 8 bits, then your task is encoding the lookup tables in this big lookup table, you can check how the many lut works for the classic PBS here for the LUT generation: https://github.com/zama-ai/tfhe-rs/blob/afdb30aa77b1edbbf3948ba76e0304576d073c30/tfhe/src/shortint/server_key/mod.rs#L784

and here for the procedure to extract each result: https://github.com/zama-ai/tfhe-rs/blob/afdb30aa77b1edbbf3948ba76e0304576d073c30/tfhe/src/shortint/server_key/mod.rs#L1326

(you will have to adapt it to the wopbs case as we don't have primitives for it).

@LHW2Y
Copy link

LHW2Y commented Feb 9, 2025

Hello everyone please let me know when testnet live ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants