mirror of
https://github.com/arnaucube/fhe-study.git
synced 2026-01-24 04:33:52 +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
|
- `tfhe`: https://eprint.iacr.org/2018/421.pdf scheme implementation
|
||||||
|
|
||||||
`cargo test --release`
|
`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))
|
// apply mod (X^N + 1))
|
||||||
modulus_u128::<N>(&mut result);
|
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)))
|
Tn(array::from_fn(|i| T64(result[i] as u64)))
|
||||||
}
|
}
|
||||||
fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
|
fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl Add<T64> for T64 {
|
|||||||
}
|
}
|
||||||
impl AddAssign for T64 {
|
impl AddAssign for T64 {
|
||||||
fn add_assign(&mut self, rhs: Self) {
|
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 {
|
impl SubAssign for T64 {
|
||||||
fn sub_assign(&mut self, rhs: Self) {
|
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 {
|
impl MulAssign for T64 {
|
||||||
fn mul_assign(&mut self, rhs: Self) {
|
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;
|
type Output = Self;
|
||||||
|
|
||||||
fn mul(self, s: u64) -> Self {
|
fn mul(self, s: u64) -> Self {
|
||||||
Self(self.0 * s)
|
Self(self.0.wrapping_mul(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Mul<&u64> for &T64 {
|
impl Mul<&u64> for &T64 {
|
||||||
type Output = T64;
|
type Output = T64;
|
||||||
|
|
||||||
fn mul(self, s: &u64) -> Self::Output {
|
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
|
/// 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> {
|
impl<R: Ring, const K: usize> Mul<TR<R, K>> for TR<R, K> {
|
||||||
type Output = R;
|
type Output = R;
|
||||||
|
|||||||
@@ -133,6 +133,15 @@ impl<const K: usize> Sub<Tn<1>> for TLWE<K> {
|
|||||||
Self(a, b)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@@ -235,4 +244,34 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
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