add modulus switching to GLWE ciphertexts (and Zq,Rq)

This commit is contained in:
2025-07-16 18:15:51 +02:00
parent c73ff20931
commit 4a082b9187
8 changed files with 60 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
[workspace] [workspace]
members = [ members = [
"arith", "arith",
"generalized-fhe", "gfhe",
"bfv", "bfv",
"ckks" "ckks"
] ]

View File

@@ -2,7 +2,7 @@
Implementations from scratch done while studying some FHE papers; do not use in production. Implementations from scratch done while studying some FHE papers; do not use in production.
- `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. - `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. - `gfhe`: (gfhe=generalized-fhe) contains the structs and logic for RLWE, GLWE, GLev, GGSW, RGSW cryptosystems, and modulus switching and key switching methods, which can be used by concrete FHE schemes.
- `bfv`: https://eprint.iacr.org/2012/144.pdf scheme implementation - `bfv`: https://eprint.iacr.org/2012/144.pdf scheme implementation
- `ckks`: https://eprint.iacr.org/2016/421.pdf scheme implementation - `ckks`: https://eprint.iacr.org/2016/421.pdf scheme implementation

View File

@@ -49,9 +49,6 @@ impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
} }
} }
// TODO define a trait "PolynomialRingTrait" or similar, so that when other structs use it can just
// use the trait and not need to add '<Q, N>' to their params
impl<const Q: u64, const N: usize> From<crate::ring::R<N>> for Rq<Q, N> { impl<const Q: u64, const N: usize> From<crate::ring::R<N>> for Rq<Q, N> {
fn from(r: crate::ring::R<N>) -> Self { fn from(r: crate::ring::R<N>) -> Self {
Self::from_vec( Self::from_vec(
@@ -165,7 +162,7 @@ impl<const Q: u64, const N: usize> Rq<Q, N> {
} }
/// perform the mod switch operation from Q to Q', where Q2=Q' /// perform the mod switch operation from Q to Q', where Q2=Q'
fn mod_switch<const Q2: u64>(&self) -> Rq<Q2, N> { pub fn mod_switch<const Q2: u64>(&self) -> Rq<Q2, N> {
Rq::<Q2, N> { Rq::<Q2, N> {
coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::<Q2>()), coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::<Q2>()),
evals: None, evals: None,

View File

@@ -4,7 +4,7 @@ use std::fmt::Debug;
use std::iter::Sum; use std::iter::Sum;
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
/// represents a ring element /// Represents a ring element. Currently implemented by ring.rs#R and ringq.rs#Rq.
pub trait Ring: pub trait Ring:
Sized Sized
+ Add<Output = Self> + Add<Output = Self>
@@ -25,6 +25,6 @@ pub trait Ring:
fn coeffs(&self) -> Vec<Self::C>; fn coeffs(&self) -> Vec<Self::C>;
fn zero() -> Self; fn zero() -> Self;
fn rand(rng: impl Rng, dist: impl Distribution<f64>) -> Self;
// note/wip/warning: dist (0,q) with f64, will output more '0=q' elements than other values // note/wip/warning: dist (0,q) with f64, will output more '0=q' elements than other values
fn rand(rng: impl Rng, dist: impl Distribution<f64>) -> Self;
} }

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "generalized-fhe" name = "gfhe"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

View File

@@ -1,2 +1,2 @@
# common # gfhe - generalized-fhe
Contains the structs and logic for RLWE, GLWE, GLev, GGSW, RGSW cryptosystems, which can be used by concrete FHE schemes. Contains the structs and logic for RLWE, GLWE, GLev, GGSW, RGSW cryptosystems, which can be used by concrete FHE schemes.

View File

@@ -69,14 +69,22 @@ impl<const Q: u64, const N: usize, const K: usize> GLWE<Q, N, K> {
pub fn decrypt<const T: u64>(&self, sk: &SecretKey<Q, N, K>, delta: u64) -> Rq<T, N> { pub fn decrypt<const T: u64>(&self, sk: &SecretKey<Q, N, K>, delta: u64) -> Rq<T, N> {
let (d, b): (TR<Rq<Q, N>, K>, Rq<Q, N>) = (self.0.clone(), self.1); let (d, b): (TR<Rq<Q, N>, K>, Rq<Q, N>) = (self.0.clone(), self.1);
let r: Rq<Q, N> = b - &d * &sk.0; let r: Rq<Q, N> = b - &d * &sk.0;
let r_scaled: Vec<f64> = r let r = r.mul_div_round(T, Q);
.coeffs() // let r_scaled: Vec<f64> = r
.iter() // .coeffs()
.map(|e| (e.0 as f64 / delta as f64).round()) // .iter()
.collect(); // // .map(|e| (e.0 as f64 / delta as f64).round())
let r = Rq::<Q, N>::from_vec_f64(r_scaled); // .map(|e| e.mul_div_round(T, Q))
// .collect();
// let r = Rq::<Q, N>::from_vec_f64(r_scaled);
r.remodule::<T>() r.remodule::<T>()
} }
pub fn mod_switch<const P: u64>(&self) -> GLWE<P, N, K> {
let a: TR<Rq<P, N>, K> = TR(self.0 .0.iter().map(|r| r.mod_switch::<P>()).collect());
let b: Rq<P, N> = self.1.mod_switch::<P>();
GLWE(a, b)
}
} }
impl<const Q: u64, const N: usize, const K: usize> Add<GLWE<Q, N, K>> for GLWE<Q, N, K> { impl<const Q: u64, const N: usize, const K: usize> Add<GLWE<Q, N, K>> for GLWE<Q, N, K> {
@@ -233,4 +241,43 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn test_mod_switch() -> Result<()> {
const Q: u64 = 2u64.pow(16) + 1;
const P: u64 = 2u64.pow(8) + 1;
// note: wip, Q and P chosen so that P/Q is an integer
const N: usize = 8;
const T: u64 = 8; // plaintext modulus, must be a prime or power of a prime
const K: usize = 16;
type S = GLWE<Q, N, K>;
let delta: u64 = Q / T; // floored
let mut rng = rand::thread_rng();
dbg!(P as f64 / Q as f64);
dbg!(delta);
dbg!(delta as f64 * P as f64 / Q as f64);
dbg!(delta as f64 * (P as f64 / Q as f64));
for _ in 0..200 {
let (sk, pk) = S::new_key(&mut rng)?;
let msg_dist = Uniform::new(0_u64, T);
let m = Rq::<T, N>::rand_u64(&mut rng, msg_dist)?;
let c = S::encrypt(&mut rng, &pk, &m, delta)?;
let c2 = c.mod_switch::<P>();
let sk2: SecretKey<P, N, K> =
SecretKey(TR(sk.0 .0.iter().map(|s_i| s_i.remodule::<P>()).collect()));
let delta2: u64 = ((P as f64 * delta as f64) / Q as f64).round() as u64;
let m_recovered = c2.decrypt(&sk2, delta2);
assert_eq!(m, m_recovered);
}
Ok(())
}
} }