add TLWE ciphertexts addition, substraction, plaintext(constant)-addition
arnaucube git@arnaucube.com
Tue, 22 Jul 2025 15:31:03 +0000
1 files changed,
125 insertions(+),
7 deletions(-)
jump to
M
tfhe/src/tlwe.rs
→
tfhe/src/tlwe.rs
@@ -7,7 +7,7 @@ use std::array;
use std::iter::Sum; use std::ops::{Add, AddAssign, Mul, Sub}; -use arith::{Ring, Rq, Tn, Zq, T64, TR}; +use arith::{Ring, Rq, Tn, T64, TR}; const ERR_SIGMA: f64 = 3.2;@@ -36,12 +36,12 @@ let pk: PublicKey<K> = PublicKey((&a * &s) + e, a);
Ok((SecretKey(s), pk)) } - pub fn encode<const P: u64>(m: Rq<P, 1>) -> Tn<1> { + pub fn encode<const P: u64>(m: &Rq<P, 1>) -> Tn<1> { let delta = u64::MAX / P; // floored let coeffs = m.coeffs(); Tn(array::from_fn(|i| T64(coeffs[i].0 * delta))) } - pub fn decode<const P: u64>(p: Tn<1>) -> Rq<P, 1> { + pub fn decode<const P: u64>(p: &Tn<1>) -> Rq<P, 1> { let p = p.mul_div_round(P, u64::MAX); Rq::<P, 1>::from_vec_u64(p.coeffs().iter().map(|c| c.0).collect()) }@@ -77,6 +77,63 @@ b - &d * &sk.0
} } +impl<const K: usize> Add<TLWE<K>> for TLWE<K> { + type Output = Self; + fn add(self, other: Self) -> Self { + let a: TR<Tn<1>, K> = self.0 + other.0; + let b: Tn<1> = self.1 + other.1; + Self(a, b) + } +} +impl<const K: usize> AddAssign for TLWE<K> { + fn add_assign(&mut self, rhs: Self) { + for i in 0..K { + self.0 .0[i] = self.0 .0[i] + rhs.0 .0[i]; + } + self.1 = self.1 + rhs.1; + } +} +impl<const K: usize> Sum<TLWE<K>> for TLWE<K> { + fn sum<I>(iter: I) -> Self + where + I: Iterator<Item = Self>, + { + let mut acc = TLWE::<K>::zero(); + for e in iter { + acc += e; + } + acc + } +} + +impl<const K: usize> Sub<TLWE<K>> for TLWE<K> { + type Output = Self; + fn sub(self, other: Self) -> Self { + let a: TR<Tn<1>, K> = self.0 - other.0; + let b: Tn<1> = self.1 - other.1; + Self(a, b) + } +} + +// plaintext addition +impl<const K: usize> Add<Tn<1>> for TLWE<K> { + type Output = Self; + fn add(self, plaintext: Tn<1>) -> Self { + let a: TR<Tn<1>, K> = self.0; + let b: Tn<1> = self.1 + plaintext; + Self(a, b) + } +} +// plaintext substraction +impl<const K: usize> Sub<Tn<1>> for TLWE<K> { + type Output = Self; + fn sub(self, plaintext: Tn<1>) -> Self { + let a: TR<Tn<1>, K> = self.0; + let b: Tn<1> = self.1 - plaintext; + Self(a, b) + } +} + #[cfg(test)] mod tests { use anyhow::Result;@@ -86,7 +143,7 @@ use super::*;
#[test] fn test_encrypt_decrypt() -> Result<()> { - const T: u64 = 32; // plaintext modulus + const T: u64 = 128; // msg space (msg modulus) const K: usize = 16; type S = TLWE<K>;@@ -98,21 +155,82 @@
let msg_dist = Uniform::new(0_u64, T); let m = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?; dbg!(&m); - let p: Tn<1> = S::encode::<T>(m); + let p: Tn<1> = S::encode::<T>(&m); dbg!(&p); let c = S::encrypt(&mut rng, &pk, &p)?; let p_recovered = c.decrypt(&sk); - let m_recovered = S::decode::<T>(p_recovered); + let m_recovered = S::decode::<T>(&p_recovered); assert_eq!(m, m_recovered); // same but using encrypt_s (with sk instead of pk)) let c = S::encrypt_s(&mut rng, &sk, &p)?; let p_recovered = c.decrypt(&sk); - let m_recovered = S::decode::<T>(p_recovered); + let m_recovered = S::decode::<T>(&p_recovered); assert_eq!(m, m_recovered); + } + + Ok(()) + } + + #[test] + fn test_addition() -> 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); // plaintext + let p2: Tn<1> = S::encode::<T>(&m2); // plaintext + + let c1 = S::encrypt(&mut rng, &pk, &p1)?; + let c2 = S::encrypt(&mut rng, &pk, &p2)?; + + let c3 = c1 + c2; + + let p3_recovered = c3.decrypt(&sk); + let m3_recovered = S::decode::<T>(&p3_recovered); + + assert_eq!((m1 + m2).remodule::<T>(), m3_recovered.remodule::<T>()); + } + + Ok(()) + } + + #[test] + fn test_add_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); // plaintext + let p2: Tn<1> = S::encode::<T>(&m2); // plaintext + + let c1 = S::encrypt(&mut rng, &pk, &p1)?; + + let c3 = c1 + p2; + + let p3_recovered = c3.decrypt(&sk); + let m3_recovered = S::decode::<T>(&p3_recovered); + + assert_eq!((m1 + m2).remodule::<T>(), m3_recovered); } Ok(())