diff --git a/README.md b/README.md index b5bfb57..78606cc 100644 --- a/README.md +++ b/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` to use `CKKS` or `BFV`. + +```rust +const T: u64 = 128; // msg space (msg modulus) +const K: usize = 16; +type S = TLWE; + +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::::rand_u64(&mut rng, msg_dist)?; +let m2 = Rq::::rand_u64(&mut rng, msg_dist)?; +let m3 = Rq::::rand_u64(&mut rng, msg_dist)?; + +// encode the msgs into the plaintext space +let p1: Tn<1> = S::encode::(&m1); // plaintext +let p2: Tn<1> = S::encode::(&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::(&p4_recovered); + +// m4 is equal to (m1+m2)*m3 +``` diff --git a/arith/src/ring_torus.rs b/arith/src/ring_torus.rs index 9da3656..6cc1abc 100644 --- a/arith/src/ring_torus.rs +++ b/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::(&mut result); - // sanity check: check that there are no coeffs > i64_max - assert_eq!( - result, - Tn::(array::from_fn(|i| T64(result[i] as u64))) - .coeffs() - .iter() - .map(|c| c.0 as u128) - .collect::>() - ); Tn(array::from_fn(|i| T64(result[i] as u64))) } fn modulus_u128(p: &mut Vec) { diff --git a/arith/src/torus.rs b/arith/src/torus.rs index 0c6abb2..dc85eb6 100644 --- a/arith/src/torus.rs +++ b/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)) } } diff --git a/arith/src/tuple_ring.rs b/arith/src/tuple_ring.rs index af18e76..f60b75b 100644 --- a/arith/src/tuple_ring.rs +++ b/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 Mul> for TR { type Output = R; diff --git a/tfhe/src/tlwe.rs b/tfhe/src/tlwe.rs index a5669a4..68fb56a 100644 --- a/tfhe/src/tlwe.rs +++ b/tfhe/src/tlwe.rs @@ -133,6 +133,15 @@ impl Sub> for TLWE { Self(a, b) } } +// plaintext multiplication +impl Mul> for TLWE { + type Output = Self; + fn mul(self, plaintext: Tn<1>) -> Self { + let a: TR, 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; + + 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::::rand_u64(&mut rng, msg_dist)?; + let m2 = Rq::::rand_u64(&mut rng, msg_dist)?; + let p1: Tn<1> = S::encode::(&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::(&p3_recovered); + assert_eq!((m1.to_r() * m2.to_r()).to_rq::(), m3_recovered); + } + + Ok(()) + } }