Browse Source

tfhe: ciphertext-plaintext multiplication

gfhe-over-ring-trait
arnaucube 2 weeks ago
parent
commit
4790fdbb3b
5 changed files with 88 additions and 15 deletions
  1. +43
    -0
      README.md
  2. +0
    -9
      arith/src/ring_torus.rs
  3. +5
    -5
      arith/src/torus.rs
  4. +1
    -1
      arith/src/tuple_ring.rs
  5. +39
    -0
      tfhe/src/tlwe.rs

+ 43
- 0
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
```

+ 0
- 9
arith/src/ring_torus.rs

@ -167,15 +167,6 @@ fn naive_poly_mul(poly1: &Tn, poly2: &Tn) -> Tn {
// 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>) {

+ 5
- 5
arith/src/torus.rs

@ -44,7 +44,7 @@ impl Add 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 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 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 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))
}
}

+ 1
- 1
arith/src/tuple_ring.rs

@ -55,7 +55,7 @@ impl Sub> for TR {
}
}
/// 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;

+ 39
- 0
tfhe/src/tlwe.rs

@ -133,6 +133,15 @@ impl Sub> for TLWE {
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(())
}
}

Loading…
Cancel
Save