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:

  1. The first app.get_fhe_program(multiply).unwrap() argument is the compiled multiply program you wish to run.
  2. The second vec![a, b] argument contains the input arguments to the program in a Vec.
  3. The final public_key argument is the public key used to generate the encrypted program inputs (i.e. a_enc and b_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();
}
1

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 Vec are each of the tuple's entries.
  • If the underlying FHE program returns a single value, the Vec contains a single entry.
  • If the underlying FHE program doesn't return anything, you should write more useful programs. The Vec will be empty.