From 92b6f50cccb4f101045d016d9b0c68dfa212f5a9 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 9 Jul 2025 17:52:59 +0200 Subject: [PATCH] add GLWE ciphertext-ciphertext addition, and ciphertext-plaintext addition --- README.md | 2 + arith/src/tuple_ring.rs | 2 +- generalized-fhe/src/glwe.rs | 85 +++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e6dbe82..cddd9a0 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,5 @@ Implementations from scratch done while studying some FHE papers; do not use in - `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` diff --git a/arith/src/tuple_ring.rs b/arith/src/tuple_ring.rs index 109e11f..fba6ae3 100644 --- a/arith/src/tuple_ring.rs +++ b/arith/src/tuple_ring.rs @@ -13,7 +13,7 @@ use crate::Ring; /// Tuple of K Ring (Rq) elements. We use Vec to allocate it in the heap, /// since if using a fixed-size array it would overflow the stack. #[derive(Clone, Debug)] -pub struct TR(Vec); +pub struct TR(pub Vec); impl TR { pub fn rand(mut rng: impl Rng, dist: impl Distribution) -> Self { diff --git a/generalized-fhe/src/glwe.rs b/generalized-fhe/src/glwe.rs index 7eaae16..da79c16 100644 --- a/generalized-fhe/src/glwe.rs +++ b/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 @@ impl GLWE { } } +impl Add> for GLWE { + type Output = Self; + fn add(self, other: Self) -> Self { + let a: TR, K> = self.0 + other.0; + let b: Rq = self.1 + other.1; + Self(a, b) + } +} + +impl Add> for GLWE { + type Output = Self; + fn add(self, plaintext: Rq) -> Self { + let a: TR, K> = self.0; + let b: Rq = self.1 + plaintext; + Self(a, b) + } +} + #[cfg(test)] mod tests { use anyhow::Result; @@ -112,4 +129,66 @@ mod tests { 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; + + 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::::rand_u64(&mut rng, msg_dist)?; + let m2 = Rq::::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; + + 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::::rand_u64(&mut rng, msg_dist)?; + let m2 = Rq::::rand_u64(&mut rng, msg_dist)?; + let m2_scaled: Rq = m2.remodule::() * 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(()) + } }