mirror of
https://github.com/Sunscreen-tech/Sunscreen.git
synced 2026-01-14 08:07:56 -05:00
166 lines
4.9 KiB
Rust
166 lines
4.9 KiB
Rust
use crypto_bigint::{Uint, U256, U64};
|
|
use lazy_static::lazy_static;
|
|
use paste::paste;
|
|
use proptest::prelude::{prop::num::u64::ANY, prop_assert_eq, proptest, ProptestConfig};
|
|
use sunscreen::{
|
|
fhe_program,
|
|
types::{
|
|
bfv::{Unsigned, Unsigned256},
|
|
Cipher,
|
|
},
|
|
Compiler, FheApplication, FheProgramInput, FheRuntime, PrivateKey, PublicKey,
|
|
};
|
|
|
|
macro_rules! fhe_program {
|
|
($(($op:ident, $binop:tt, $ty:ident)),+) => {
|
|
$(
|
|
paste! {
|
|
#[fhe_program(scheme = "bfv")]
|
|
fn $op(a: Cipher<$ty>, b: Cipher<$ty>) -> Cipher<$ty> {
|
|
a $binop b
|
|
}
|
|
#[fhe_program(scheme = "bfv")]
|
|
fn [<$op _plain>](a: Cipher<$ty>, b: $ty) -> Cipher<$ty> {
|
|
a $binop b
|
|
}
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
fhe_program! {
|
|
(add, +, Unsigned256),
|
|
(sub, -, Unsigned256),
|
|
(mul, *, Unsigned256)
|
|
}
|
|
|
|
struct FheApp {
|
|
app: FheApplication,
|
|
rt: FheRuntime,
|
|
pk: PublicKey,
|
|
sk: PrivateKey,
|
|
}
|
|
impl FheApp {
|
|
fn new() -> Self {
|
|
let app: FheApplication = Compiler::new()
|
|
.fhe_program(add)
|
|
.fhe_program(add_plain)
|
|
.fhe_program(sub)
|
|
.fhe_program(sub_plain)
|
|
.fhe_program(mul)
|
|
.fhe_program(mul_plain)
|
|
.compile()
|
|
.unwrap();
|
|
let rt: FheRuntime = FheRuntime::new(app.params()).unwrap();
|
|
let (pk, sk) = rt.generate_keys().unwrap();
|
|
Self { app, rt, pk, sk }
|
|
}
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref FHE_APP: FheApp = FheApp::new();
|
|
}
|
|
|
|
macro_rules! op_proptest {
|
|
($($op:ident),+) => {
|
|
$(
|
|
paste! {
|
|
#[test]
|
|
fn [<$op _fhe_proptest>]() {
|
|
let FheApp { app, rt, pk, sk } = &*FHE_APP;
|
|
|
|
proptest!(ProptestConfig::with_cases(20), |(lhs in [ANY; 4], rhs in [ANY; 4])| {
|
|
|
|
// Test both operands as ciphertexts
|
|
let a = U256::from_words(lhs);
|
|
let a_c = rt.encrypt(Unsigned256::from(a), pk).unwrap();
|
|
let b = U256::from_words(rhs);
|
|
let b_c = rt.encrypt(Unsigned256::from(b), pk).unwrap();
|
|
let args: Vec<FheProgramInput> = vec![a_c.clone().into(), b_c.clone().into()];
|
|
|
|
let result = rt
|
|
.run(app.get_fhe_program($op).unwrap(), args, pk)
|
|
.unwrap();
|
|
|
|
let c: Unsigned256 = rt.decrypt(&result[0], &sk).unwrap();
|
|
|
|
prop_assert_eq!(a.[<wrapping_ $op>](&b), c.into());
|
|
|
|
// Test mixed ciphertexts and plaintexts
|
|
let args_mixed: Vec<FheProgramInput> = vec![a_c.into(), Unsigned256::from(b).into()];
|
|
let result_mixed = rt
|
|
.run(app.get_fhe_program([<$op _plain>]).unwrap(), args_mixed, pk)
|
|
.unwrap();
|
|
|
|
let c_mixed: Unsigned256 = rt.decrypt(&result_mixed[0], &sk).unwrap();
|
|
|
|
prop_assert_eq!(a.[<wrapping_ $op>](&b), c_mixed.into());
|
|
});
|
|
}
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
op_proptest! {
|
|
add,
|
|
sub,
|
|
mul
|
|
}
|
|
|
|
fn run_with<const L: usize, O1, O2, F>(a: Uint<L>, b: Uint<L>, op: F, fhe_op: O1, fhe_op_plain: O2)
|
|
where
|
|
O1: AsRef<str>,
|
|
O2: AsRef<str>,
|
|
F: Fn(&Uint<L>, &Uint<L>) -> Uint<L>,
|
|
{
|
|
let FheApp { app, rt, pk, sk } = &*FHE_APP;
|
|
let a_u = Unsigned::from(a);
|
|
let b_u = Unsigned::from(b);
|
|
|
|
let a_c = rt.encrypt(a_u, pk).unwrap();
|
|
let b_c = rt.encrypt(b_u, pk).unwrap();
|
|
let args: Vec<FheProgramInput> = vec![a_c.clone().into(), b_c.into()];
|
|
|
|
let result = rt
|
|
.run(app.get_fhe_program(fhe_op).unwrap(), args, pk)
|
|
.unwrap();
|
|
|
|
let c: Unsigned<L> = rt.decrypt(&result[0], sk).unwrap();
|
|
|
|
assert_eq!(op(&a, &b), c.into());
|
|
|
|
// Same test but subtracting plaintext
|
|
let args_mixed: Vec<FheProgramInput> = vec![a_c.into(), b_u.into()];
|
|
let result_mixed = rt
|
|
.run(app.get_fhe_program(fhe_op_plain).unwrap(), args_mixed, pk)
|
|
.unwrap();
|
|
|
|
let c_mixed: Unsigned<L> = rt.decrypt(&result_mixed[0], sk).unwrap();
|
|
|
|
assert_eq!(op(&a, &b), c_mixed.into());
|
|
}
|
|
|
|
#[test]
|
|
fn underflow_wraps_properly() {
|
|
// U256
|
|
run_with(U256::ZERO, U256::ONE, U256::wrapping_sub, sub, sub_plain);
|
|
// U64
|
|
run_with(U64::ZERO, U64::ONE, U64::wrapping_sub, sub, sub_plain);
|
|
}
|
|
|
|
#[test]
|
|
fn overflow_wraps_properly() {
|
|
// U256
|
|
run_with(U256::MAX, U256::ONE, U256::wrapping_add, add, add_plain);
|
|
// U64
|
|
run_with(U64::MAX, U64::ONE, U64::wrapping_add, add, add_plain);
|
|
}
|
|
|
|
#[test]
|
|
fn carry_at_limb_boundary() {
|
|
let a = U256::from_words([0, u64::MAX, 0, 0]);
|
|
let b = U256::from_words([0, 1, 0, 0]);
|
|
run_with(a, b, U256::wrapping_add, add, add_plain);
|
|
}
|