arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
add GLWE ciphertext-ciphertext addition, and ciphertext-plaintext addition
arnaucube git@arnaucube.com
Wed, 09 Jul 2025 17:52:59 +0200
commit

92b6f50cccb4f101045d016d9b0c68dfa212f5a9

parent

2998f1761cb5b8def50141c191b6f4dffb69bc30

3 files changed, 85 insertions(+), 4 deletions(-)

jump to
M README.md → README.md

@@ -5,3 +5,5 @@ - `arith`: contains $\mathbb{Z}_q$, $R_q=\mathbb{Z}_q[X]/(X^N+1)$ and $R=\mathbb{Z}[X]/(X^N+1)$ arithmetic implementations, together with the NTT implementation.

- `generalized-fhe`: contains the structs and logic for RLWE, GLWE, GLev, GGSW, RGSW cryptosystems, which can be used by concrete FHE schemes. - `bfv`: https://eprint.iacr.org/2012/144.pdf scheme implementation - `ckks`: https://eprint.iacr.org/2016/421.pdf scheme implementation + +`cargo test --release`
M arith/src/tuple_ring.rs → arith/src/tuple_ring.rs

@@ -13,7 +13,7 @@

/// Tuple of K Ring (Rq) elements. We use Vec<R> to allocate it in the heap, /// since if using a fixed-size array it would overflow the stack. #[derive(Clone, Debug)] -pub struct TR<R: Ring, const K: usize>(Vec<R>); +pub struct TR<R: Ring, const K: usize>(pub Vec<R>); impl<R: Ring, const K: usize> TR<R, K> { pub fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
M generalized-fhe/src/glwe.rs → generalized-fhe/src/glwe.rs

@@ -1,10 +1,9 @@

use anyhow::Result; -use itertools::zip_eq; use rand::Rng; use rand_distr::{Normal, Uniform}; -use std::{array, ops}; +use std::ops::Add; -use arith::{Ring, Rq, R, TR}; +use arith::{Ring, Rq, TR}; const ERR_SIGMA: f64 = 3.2;

@@ -80,6 +79,24 @@ r.remodule::<T>()

} } +impl<const Q: u64, const N: usize, const K: usize> Add<GLWE<Q, N, K>> for GLWE<Q, N, K> { + type Output = Self; + fn add(self, other: Self) -> Self { + let a: TR<Rq<Q, N>, K> = self.0 + other.0; + let b: Rq<Q, N> = self.1 + other.1; + Self(a, b) + } +} + +impl<const Q: u64, const N: usize, const K: usize> Add<Rq<Q, N>> for GLWE<Q, N, K> { + type Output = Self; + fn add(self, plaintext: Rq<Q, N>) -> Self { + let a: TR<Rq<Q, N>, K> = self.0; + let b: Rq<Q, N> = self.1 + plaintext; + Self(a, b) + } +} + #[cfg(test)] mod tests { use anyhow::Result;

@@ -108,6 +125,68 @@ let c = S::encrypt(&mut rng, &pk, &m, delta)?;

let m_recovered = c.decrypt(&sk, delta); assert_eq!(m, m_recovered); + } + + Ok(()) + } + + #[test] + fn test_addition() -> Result<()> { + const Q: u64 = 2u64.pow(16) + 1; + const N: usize = 128; + const T: u64 = 20; + const K: usize = 16; + type S = GLWE<Q, N, K>; + + let delta: u64 = Q / T; // floored + 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, N>::rand_u64(&mut rng, msg_dist)?; + let m2 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?; + + let c1 = S::encrypt(&mut rng, &pk, &m1, delta)?; + let c2 = S::encrypt(&mut rng, &pk, &m2, delta)?; + + let c3 = c1 + c2; + + let m3_recovered = c3.decrypt(&sk, delta); + + assert_eq!(m1 + m2, m3_recovered); + } + + Ok(()) + } + + #[test] + fn test_add_plaintext() -> Result<()> { + const Q: u64 = 2u64.pow(16) + 1; + const N: usize = 128; + const T: u64 = 32; + const K: usize = 16; + type S = GLWE<Q, N, K>; + + let delta: u64 = Q / T; // floored + 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, N>::rand_u64(&mut rng, msg_dist)?; + let m2 = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?; + let m2_scaled: Rq<Q, N> = m2.remodule::<Q>() * delta; + + let c1 = S::encrypt(&mut rng, &pk, &m1, delta)?; + + let c3 = c1 + m2_scaled; + + let m3_recovered = c3.decrypt(&sk, delta); + + assert_eq!(m1 + m2, m3_recovered); } Ok(())