Browse Source

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

gfhe-over-ring-trait
arnaucube 3 weeks ago
parent
commit
4a082b9187
8 changed files with 60 additions and 16 deletions
  1. +1
    -1
      Cargo.toml
  2. +1
    -1
      README.md
  3. +1
    -4
      arith/src/ringq.rs
  4. +2
    -2
      arith/src/traits.rs
  5. +1
    -1
      gfhe/Cargo.toml
  6. +1
    -1
      gfhe/README.md
  7. +53
    -6
      gfhe/src/glwe.rs
  8. +0
    -0
      gfhe/src/lib.rs

+ 1
- 1
Cargo.toml

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

+ 1
- 1
README.md

@ -2,7 +2,7 @@
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.
- `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
- `ckks`: https://eprint.iacr.org/2016/421.pdf scheme implementation

+ 1
- 4
arith/src/ringq.rs

@ -49,9 +49,6 @@ impl Ring for Rq {
}
}
// 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> {
fn from(r: crate::ring::R<N>) -> Self {
Self::from_vec(
@ -165,7 +162,7 @@ impl Rq {
}
/// 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> {
coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::<Q2>()),
evals: None,

+ 2
- 2
arith/src/traits.rs

@ -4,7 +4,7 @@ use std::fmt::Debug;
use std::iter::Sum;
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:
Sized
+ Add<Output = Self>
@ -25,6 +25,6 @@ pub trait Ring:
fn coeffs(&self) -> Vec<Self::C>;
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
fn rand(rng: impl Rng, dist: impl Distribution<f64>) -> Self;
}

generalized-fhe/Cargo.toml → gfhe/Cargo.toml

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

generalized-fhe/README.md → gfhe/README.md

@ -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.

generalized-fhe/src/glwe.rs → gfhe/src/glwe.rs

@ -69,14 +69,22 @@ impl GLWE {
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 r: Rq<Q, N> = b - &d * &sk.0;
let r_scaled: Vec<f64> = r
.coeffs()
.iter()
.map(|e| (e.0 as f64 / delta as f64).round())
.collect();
let r = Rq::<Q, N>::from_vec_f64(r_scaled);
let r = r.mul_div_round(T, Q);
// let r_scaled: Vec<f64> = r
// .coeffs()
// .iter()
// // .map(|e| (e.0 as f64 / delta as f64).round())
// .map(|e| e.mul_div_round(T, Q))
// .collect();
// let r = Rq::<Q, N>::from_vec_f64(r_scaled);
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> {
@ -233,4 +241,43 @@ mod tests {
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(())
}
}

generalized-fhe/src/lib.rs → gfhe/src/lib.rs


Loading…
Cancel
Save