Running FHE Programs
In our simple example, we called runtime.run to execute our FHE program
use sunscreen::{*, types::{{bfv::Signed}, Cipher}}; fn main() { #[fhe_program(scheme = "bfv")] fn multiply(a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> { a * b } let app = Compiler::new() .fhe_program(multiply) .plain_modulus_constraint(PlainModulusConstraint::Raw(64)) .compile() .unwrap(); let runtime = FheRuntime::new(app.params()).unwrap(); let (public_key, _) = runtime.generate_keys().unwrap(); let a_enc = runtime.encrypt(Signed::from(5), &public_key).unwrap(); let b_enc = runtime.encrypt(Signed::from(15), &public_key).unwrap(); let results = runtime.run(app.get_fhe_program(multiply).unwrap(), vec![a_enc, b_enc], &public_key).unwrap(); }
Let's break down the arguments to runtime.run:
- The first
app.get_fhe_program(multiply).unwrap()argument is the compiledmultiplyprogram you wish to run. - The second
vec![a, b]argument contains the input arguments to the program in aVec. - The final
public_keyargument is the public key used to generate the encrypted program inputs (i.e.a_encandb_enc).
FHE program inputs
Rust requires collections be homogenous (i.e. each item is the same type). However, program arguments may not be always be of the same type!
Our FheProgramInput wrapper enum solves this problem; it wraps values so they can exist in a homogeneous collection. The run function's second argument is a Vec<T> where T readily converts into an FheProgramInput (i.e. T impl Into<FheProgramInput>1). Ciphertext and all types under sunscreen::bfv::types::* do this.
If your FHE program only accepts ciphertexts (a common scenario), it's sufficient to simply pass a Vec<Ciphertext> as we did in the above example. However, if you want to mix Ciphertext and unencrypted values, you'll need to make a Vec<FheProgramInput> manually, converting each argument yourself:
use sunscreen::{*, types::{{bfv::Signed}, Cipher}}; fn main() { // Note the lack of the `Cipher` type on b. This declares // it as a unencrypted. #[fhe_program(scheme = "bfv")] fn multiply(a: Cipher<Signed>, b: Signed) -> Cipher<Signed> { a * b } let app = Compiler::new() .fhe_program(multiply) .compile() .unwrap(); let runtime = FheRuntime::new(app.params()).unwrap(); let (public_key, _) = runtime.generate_keys().unwrap(); let a_enc = runtime.encrypt(Signed::from(5), &public_key).unwrap(); // a is encrypted, but the second argument, b, is not. // We make a Vec<FheProgramInput> by calling `.into()` // on each value. let args: Vec<FheProgramInput> = vec![a_enc.into(), Signed::from(15).into()]; let results = runtime.run(app.get_fhe_program(multiply).unwrap(), args, &public_key).unwrap(); }
FheProgramInput itself meets this requirement because every type in Rust is reflexive.
Validation
When you compile your FHE program, the compiler marks down the call signature (argument and return types). The run function validates that the inputs you gave are appropriately encrypted/unencrypted and are of the correct type.
Return value
On success, the run method returns a Vec<Ciphertext> containing each output of the FHE program.
- If the underlying FHE program returns a tuple, the entries of the
Vecare each of the tuple's entries. - If the underlying FHE program returns a single value, the
Veccontains a single entry. - If the underlying FHE program doesn't return anything, you should write more useful programs. The
Vecwill be empty.