Factoring FHE programs

In this section we'll show you how to factor your programs in a specific way that allows for

  • Reusing algorithms with different data types.
  • Running your algorithm without FHE. This allows you to debug the algorithm without encryption getting in your way and measure FHE's performance overhead.

Let's begin by rewriting our simple_multiply example with a common implementation (simple_multiply_impl):

#![allow(unused)]
fn main() {
use sunscreen::{
    fhe_program,
    types::{bfv::{Signed, Fractional}, Cipher},
};
use std::ops::Mul;

fn simple_multiply_impl<T, U>(a: T, b: U) -> T
where T: Mul<U, Output=T> + Copy
{
    a * b
}

#[fhe_program(scheme = "bfv")]
fn simple_multiply_signed(a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
    simple_multiply_impl(a, b)
}


#[fhe_program(scheme = "bfv")]
fn simple_multiply_fractional(a: Cipher<Fractional<64>>, b: Fractional<64>) -> Cipher<Fractional<64>> {
    simple_multiply_impl(a, b)
}
}

The first FHE program multiplies encrypted Signed values. In the second, a is an encrypted Fractional value while b is an unencrypted Fractional value. We can run both of these programs using runtime.run() as normal.

Running your implementation without FHE

If we inspect the trait bounds on simple_multiply_impl, we'll notice there is no mention of anything Sunscreen related. This means we can directly run our implementation with Rust i64 values by simply calling:

use std::ops::Mul;

fn simple_multiply_impl<T, U>(a: T, b: U) -> T
where T: Mul<U, Output=T> + Copy
{
    a * b
}

fn main() {
    simple_multiply_impl(7, 5);
}

It's worth explicitly pointing out that T and U may be of the same or different types; the trait bounds merely require that you can multiply T and U values.