mirror of
https://github.com/arnaucube/fhe-study.git
synced 2026-01-23 20:23:54 +01:00
tfhe: ciphertext-plaintext multiplication
This commit is contained in:
43
README.md
43
README.md
@@ -8,3 +8,46 @@ Implementations from scratch done while studying some FHE papers; do not use in
|
||||
- `tfhe`: https://eprint.iacr.org/2018/421.pdf scheme implementation
|
||||
|
||||
`cargo test --release`
|
||||
|
||||
|
||||
## Example of usage
|
||||
> the repo is a work in progress, interfaces will change.
|
||||
|
||||
This example shows usage of TFHE, but the idea is that the same interface would
|
||||
work for using CKKS & BFV, the only thing to be changed would be the parameters
|
||||
and the line `type S = TWLE<K>` to use `CKKS<Q, N>` or `BFV<Q, N, T>`.
|
||||
|
||||
```rust
|
||||
const T: u64 = 128; // msg space (msg modulus)
|
||||
const K: usize = 16;
|
||||
type S = TLWE<K>;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let msg_dist = Uniform::new(0_u64, T);
|
||||
|
||||
let (sk, pk) = S::new_key(&mut rng)?;
|
||||
|
||||
// get two random msgs in Z_t
|
||||
let m1 = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?;
|
||||
let m2 = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?;
|
||||
let m3 = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?;
|
||||
|
||||
// encode the msgs into the plaintext space
|
||||
let p1: Tn<1> = S::encode::<T>(&m1); // plaintext
|
||||
let p2: Tn<1> = S::encode::<T>(&m2); // plaintext
|
||||
let c3_const: Tn<1> = Tn(array::from_fn(|i| T64(m3.coeffs()[i].0))); // encode it as constant value
|
||||
|
||||
let c1 = S::encrypt(&mut rng, &pk, &p1)?;
|
||||
let c2 = S::encrypt(&mut rng, &pk, &p2)?;
|
||||
|
||||
// now we can do encrypted operations (notice that we do them using simple
|
||||
// operations by operator overloading):
|
||||
let c3 = c1 + c2;
|
||||
let c4 = c2 * c3_const;
|
||||
|
||||
// decrypt & decode
|
||||
let p4_recovered = c4.decrypt(&sk);
|
||||
let m4 = S::decode::<T>(&p4_recovered);
|
||||
|
||||
// m4 is equal to (m1+m2)*m3
|
||||
```
|
||||
|
||||
@@ -167,15 +167,6 @@ fn naive_poly_mul<const N: usize>(poly1: &Tn<N>, poly2: &Tn<N>) -> Tn<N> {
|
||||
// apply mod (X^N + 1))
|
||||
modulus_u128::<N>(&mut result);
|
||||
|
||||
// sanity check: check that there are no coeffs > i64_max
|
||||
assert_eq!(
|
||||
result,
|
||||
Tn::<N>(array::from_fn(|i| T64(result[i] as u64)))
|
||||
.coeffs()
|
||||
.iter()
|
||||
.map(|c| c.0 as u128)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
Tn(array::from_fn(|i| T64(result[i] as u64)))
|
||||
}
|
||||
fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Add<T64> for T64 {
|
||||
}
|
||||
impl AddAssign for T64 {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.0 += rhs.0;
|
||||
self.0 = self.0.wrapping_add(rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Sub<T64> for T64 {
|
||||
}
|
||||
impl SubAssign for T64 {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
self.0 -= rhs.0;
|
||||
self.0 = self.0.wrapping_sub(rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Mul<T64> for T64 {
|
||||
}
|
||||
impl MulAssign for T64 {
|
||||
fn mul_assign(&mut self, rhs: Self) {
|
||||
self.0 *= rhs.0;
|
||||
self.0 = self.0.wrapping_mul(rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,14 +96,14 @@ impl Mul<u64> for T64 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, s: u64) -> Self {
|
||||
Self(self.0 * s)
|
||||
Self(self.0.wrapping_mul(s))
|
||||
}
|
||||
}
|
||||
impl Mul<&u64> for &T64 {
|
||||
type Output = T64;
|
||||
|
||||
fn mul(self, s: &u64) -> Self::Output {
|
||||
T64(self.0 * s)
|
||||
T64(self.0.wrapping_mul(*s))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ impl<R: Ring, const K: usize> Sub<TR<R, K>> for TR<R, K> {
|
||||
}
|
||||
}
|
||||
|
||||
/// for (TR,TR), the Mul operation is defined as:
|
||||
/// for (TR,TR), the Mul operation is defined as the dot product:
|
||||
/// for A, B \in R^k, result = Σ A_i * B_i \in R
|
||||
impl<R: Ring, const K: usize> Mul<TR<R, K>> for TR<R, K> {
|
||||
type Output = R;
|
||||
|
||||
@@ -133,6 +133,15 @@ impl<const K: usize> Sub<Tn<1>> for TLWE<K> {
|
||||
Self(a, b)
|
||||
}
|
||||
}
|
||||
// plaintext multiplication
|
||||
impl<const K: usize> Mul<Tn<1>> for TLWE<K> {
|
||||
type Output = Self;
|
||||
fn mul(self, plaintext: Tn<1>) -> Self {
|
||||
let a: TR<Tn<1>, K> = TR(self.0 .0.iter().map(|r_i| *r_i * plaintext).collect());
|
||||
let b: Tn<1> = self.1 * plaintext;
|
||||
Self(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -235,4 +244,34 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul_plaintext() -> Result<()> {
|
||||
const T: u64 = 128;
|
||||
const K: usize = 16;
|
||||
type S = TLWE<K>;
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..200 {
|
||||
let (sk, pk) = S::new_key(&mut rng)?;
|
||||
|
||||
let msg_dist = Uniform::new(0_u64, T);
|
||||
let m1 = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?;
|
||||
let m2 = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?;
|
||||
let p1: Tn<1> = S::encode::<T>(&m1);
|
||||
// don't scale up p2, set it directly from m2
|
||||
let p2: Tn<1> = Tn(array::from_fn(|i| T64(m2.coeffs()[i].0)));
|
||||
|
||||
let c1 = S::encrypt(&mut rng, &pk, &p1)?;
|
||||
|
||||
let c3 = c1 * p2;
|
||||
|
||||
let p3_recovered: Tn<1> = c3.decrypt(&sk);
|
||||
let m3_recovered = S::decode::<T>(&p3_recovered);
|
||||
assert_eq!((m1.to_r() * m2.to_r()).to_rq::<T>(), m3_recovered);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user