My first FHE program
Now that we have installed the Sunscreen crate as a dependency, let’s get started writing our first private app using FHE! Writing our program will be a gradual process and we’ll add more code as we progress through this section. In this example, we will be developing a program to add two encrypted integers using the following C program.
Writing our program
// fhe-programs/src/add.c
typedef unsigned char uint8_t;
[[clang::fhe_program]] uint8_t add([[clang::encrypted]] uint8_t a,
[[clang::encrypted]] uint8_t b) {
return a + b;
}
Notice that the add
function is similar to any other function in C, except for the attributes. This is where the magic happens—these attributes define which functions should be compiled into FHE programs (using [[clang::fhe_program]]
) and which inputs are encrypted values (using [[clang::encrypted]]
).
add
’s signature specifies both the unsigned uint8_t
values being added and that the returned value is another uint8_t
value. The [[clang::encrypted]]
attribute tells the compiler to treat that specific value as a ciphertext instead of a plaintext value when the program is run.
One final important note on the add
program is that libraries can be included using the #include
directive, just an in normal C. To use these functions inside a FHE function (one marked with [[clang::fhe_program]]
), the function must be marked as inline
.
Compiling our program
Now that we have our add
program, we can compile it using the Sunscreen version of clang
that supports the Parasol processor.
# Create the object file
clang \
-target parasol \ # Target the parasol CPU
-O2 \ # Turn on compiler optimizatons
-c \ # Only run compile, do not link
add.c \ # Specify the file to compile
-o add.o # And where to write the output
# Combine object files into a single binary
ld.lld \
add.o \ # Specify the file to link
-o add # And where to write the output binary file
This will produce a small binary file at fhe-programs/compiled/add
. This file is an Executable and Linkable Format (ELF) file that contains some metadata about the Parasol programs, the specific FHE programs specified with [[clang::fhe_program]]
, and the assembly code to run the FHE programs on the Parasol processor.
Great, we now have the file! But uh, how do we actually run a program over encrypted data? Luckily the code to run a FHE program on the Parasol processor is easy to write in Rust. We will walk through the following example.
Running the program
use parasol_cpu::{run_program, ArgsBuilder};
use parasol_runtime::{
fluent::UInt8, ComputeKey, Encryption, SecretKey,
};
const FHE_FILE: &[u8] =
include_bytes!("../fhe-programs/compiled/add");
fn main() {
// Generate a secret key for the user. By default this ensures
// 128 bit security.
let secret_key =
SecretKey::generate_with_default_params();
// Generate a compute key for the user. These keys are used for
// operations and do not give access to the plaintext data;
// therefore, this key can safely be shared with another party.
let compute_key =
ComputeKey::generate_with_default_params(
&secret_key,
);
// Get the default encryption parameters.
let enc = Encryption::default();
// Define the values we want to add. The sizes of the values
// must match the size of the values defined in the
// C TFHE program!
let a = 2u8;
let b = 7u8;
let enc_a =
UInt8::encrypt_secret(a as u64, &enc, &secret_key);
let enc_b =
UInt8::encrypt_secret(b as u64, &enc, &secret_key);
// To pass arguments into the C program, we must use
// the `ArgsBuilder` to construct the arguments.
let arguments = ArgsBuilder::new()
.arg(enc_a)
.arg(enc_b)
.return_value::<UInt8>();
// Run the program.
let encrypted_result = run_program(
compute_key,
FHE_FILE,
"add",
arguments,
)
.unwrap();
// Decrypt the result.
let result =
encrypted_result.decrypt(&enc, &secret_key);
println!("Encrypted {a} + {b} = {result}");
}
First, we must generate a secret key. This key allows the user to both encrypt and, crucially, decrypt data that has been processed by an FHE program.1 As such, this key should be kept secret (hence the name!) in order to prevent others from decrypting the result of an FHE program.
We then generate the compute key which allows anyone to run FHE programs on data that was encrypted using the secret key, such as our add
program. Crucially this key only allows for processing of encrypted data to produce new encrypted data, and does not provide the ability to decrypt the data at any point during or after an FHE program has been executed. This key can be shared with a service with large compute resources in order to evaluate a program more quickly.
After generating all the keys, we are ready to start! The first step is to encrypt the values using our secret key. For this we use the UIntN::encrypt_secret
method, which specifies that we want to generate an encrypted value of size N
bits. This provides us with the encrypted values enc_a
and enc_b
.
These encrypted values can they be passed as arguments via the definition of arguments
, which uses the builder pattern to specify the inputs to the program. Arguments are specified with the args
method, which in this case are enc_a
and enc_b
. The return value is specified using the return_value
method, here specified to be a UInt8
value. Calling return_value
converts the ArgsBuilder
into an Args
, which is the bundled up arguments that can be passed to the program.
We finally get to the good stuff—running the program. Here we simply call run_program
with our compute key, the generated add
ELF file, the name of the specific program to run (in this case add
), and the encrypted arguments. This function returns the encrypted value.
We can now decrypt the result of our program using the decrypt
method, which takes in the encryption parameters and the secret key. This returns a regular unsigned integer value, which we print to the console.
Congrats! You have successfully written and executed your first FHE program. 🎉
We also support public key encryption (so that we would have a secret key, public key, and compute key). For simplicity, this example uses the secret key for both encryption and decryption instead.