mirror of
https://github.com/arnaucube/fhe-study.git
synced 2026-01-24 04:33:52 +01:00
Rm const generics (#2)
* arith: get rid of constant generics. Reason: using constant generics was great for allocating the arrays in the stack, which is faster, but when started to use bigger parameter values, in some cases it was overflowing the stack. This commit removes all the constant generics in all of the `arith` crate, which in some cases slows a bit the performance, but allows for bigger parameter values (on the ones that affect lengths, like N and K). * bfv: get rid of constant generics (reason in previous commit) * ckks: get rid of constant generics (reason in two commits ago) * group ring params under a single struct * gfhe: get rid of constant generics * tfhe: get rid of constant generics * polish & clean a bit * add methods for encoding constants for ct-pt-multiplication
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use arith::{Matrix, Ring, Rq, C, R};
|
||||
use arith::{Matrix, Rq, C, R};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretKey<const Q: u64, const N: usize>(Rq<Q, N>);
|
||||
pub struct SecretKey(Rq);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PublicKey<const Q: u64, const N: usize>(Rq<Q, N>, Rq<Q, N>);
|
||||
pub struct PublicKey(Rq, Rq);
|
||||
|
||||
pub struct Encoder<const Q: u64, const N: usize> {
|
||||
pub struct Encoder {
|
||||
n: usize,
|
||||
scale_factor: C<f64>, // Δ (delta)
|
||||
primitive: C<f64>,
|
||||
basis: Matrix<C<f64>>,
|
||||
@@ -34,13 +35,14 @@ fn vandermonde(n: usize, w: C<f64>) -> Matrix<C<f64>> {
|
||||
}
|
||||
Matrix::<C<f64>>(v)
|
||||
}
|
||||
impl<const Q: u64, const N: usize> Encoder<Q, N> {
|
||||
pub fn new(scale_factor: C<f64>) -> Self {
|
||||
let primitive: C<f64> = primitive_root_of_unity(2 * N);
|
||||
let basis = vandermonde(N, primitive);
|
||||
impl Encoder {
|
||||
pub fn new(n: usize, scale_factor: C<f64>) -> Self {
|
||||
let primitive: C<f64> = primitive_root_of_unity(2 * n);
|
||||
let basis = vandermonde(n, primitive);
|
||||
let basis_t = basis.transpose();
|
||||
|
||||
Self {
|
||||
n,
|
||||
scale_factor,
|
||||
primitive,
|
||||
basis,
|
||||
@@ -52,7 +54,7 @@ impl<const Q: u64, const N: usize> Encoder<Q, N> {
|
||||
/// from $\mathbb{C}^{N/2} \longrightarrow \mathbb{Z_q}[X]/(X^N +1) = R$
|
||||
// TODO use alg.1 from 2018-1043,
|
||||
// or as in 2018-1073: $f(x) = 1N (U^T.conj() m + U^T m.conj())$
|
||||
pub fn encode(&self, z: &[C<f64>]) -> Result<R<N>> {
|
||||
pub fn encode(&self, z: &[C<f64>]) -> Result<R> {
|
||||
// $pi^{-1}: \mathbb{C}^{N/2} \longrightarrow \mathbb{H}$
|
||||
let expanded = self.pi_inv(z);
|
||||
|
||||
@@ -93,10 +95,10 @@ impl<const Q: u64, const N: usize> Encoder<Q, N> {
|
||||
|
||||
// TMP: naive round, maybe do gaussian
|
||||
let coeffs = r.iter().map(|e| e.re.round() as i64).collect::<Vec<i64>>();
|
||||
Ok(R::from_vec(coeffs))
|
||||
Ok(R::from_vec(self.n, coeffs))
|
||||
}
|
||||
|
||||
pub fn decode(&self, p: &R<N>) -> Result<Vec<C<f64>>> {
|
||||
pub fn decode(&self, p: &R) -> Result<Vec<C<f64>>> {
|
||||
let p: Vec<C<f64>> = p
|
||||
.coeffs()
|
||||
.iter()
|
||||
@@ -110,7 +112,7 @@ impl<const Q: u64, const N: usize> Encoder<Q, N> {
|
||||
|
||||
/// pi: \mathbb{H} \longrightarrow \mathbb{C}^{N/2}
|
||||
fn pi(&self, z: &[C<f64>]) -> Vec<C<f64>> {
|
||||
z[..N / 2].to_vec()
|
||||
z[..self.n / 2].to_vec()
|
||||
}
|
||||
/// pi^{-1}: \mathbb{C}^{N/2} \longrightarrow \mathbb{H}
|
||||
fn pi_inv(&self, z: &[C<f64>]) -> Vec<C<f64>> {
|
||||
@@ -154,6 +156,7 @@ mod tests {
|
||||
fn test_encode_decode() -> Result<()> {
|
||||
const Q: u64 = 1024;
|
||||
const N: usize = 32;
|
||||
let n: usize = 32;
|
||||
|
||||
let T = 128; // WIP
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -166,9 +169,9 @@ mod tests {
|
||||
.collect();
|
||||
|
||||
let delta = C::<f64>::new(64.0, 0.0); // delta = scaling factor
|
||||
let encoder = Encoder::<Q, N>::new(delta);
|
||||
let encoder = Encoder::new(n, delta);
|
||||
|
||||
let m: R<N> = encoder.encode(&z)?; // polynomial (encoded vec) \in R
|
||||
let m: R = encoder.encode(&z)?; // polynomial (encoded vec) \in R
|
||||
|
||||
let z_decoded = encoder.decode(&m)?;
|
||||
|
||||
|
||||
183
ckks/src/lib.rs
183
ckks/src/lib.rs
@@ -5,7 +5,7 @@
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![allow(dead_code)] // TMP
|
||||
|
||||
use arith::{Rq, C, R};
|
||||
use arith::{RingParam, Rq, C, R};
|
||||
|
||||
use anyhow::Result;
|
||||
use rand::Rng;
|
||||
@@ -18,35 +18,47 @@ pub use encoder::Encoder;
|
||||
// sigma=3.2 from: https://eprint.iacr.org/2016/421.pdf page 17
|
||||
const ERR_SIGMA: f64 = 3.2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PublicKey<const Q: u64, const N: usize>(Rq<Q, N>, Rq<Q, N>);
|
||||
|
||||
pub struct SecretKey<const Q: u64, const N: usize>(Rq<Q, N>);
|
||||
|
||||
pub struct CKKS<const Q: u64, const N: usize> {
|
||||
encoder: Encoder<Q, N>,
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Param {
|
||||
ring: RingParam,
|
||||
t: u64,
|
||||
}
|
||||
|
||||
impl<const Q: u64, const N: usize> CKKS<Q, N> {
|
||||
pub fn new(delta: C<f64>) -> Self {
|
||||
let encoder = Encoder::<Q, N>::new(delta);
|
||||
Self { encoder }
|
||||
#[derive(Debug)]
|
||||
pub struct PublicKey(Rq, Rq);
|
||||
|
||||
pub struct SecretKey(Rq);
|
||||
|
||||
pub struct CKKS {
|
||||
param: Param,
|
||||
encoder: Encoder,
|
||||
}
|
||||
|
||||
impl CKKS {
|
||||
pub fn new(param: &Param, delta: C<f64>) -> Self {
|
||||
let encoder = Encoder::new(param.ring.n, delta);
|
||||
Self {
|
||||
param: param.clone(),
|
||||
encoder,
|
||||
}
|
||||
}
|
||||
/// generate a new key pair (privK, pubK)
|
||||
pub fn new_key(&self, mut rng: impl Rng) -> Result<(SecretKey<Q, N>, PublicKey<Q, N>)> {
|
||||
pub fn new_key(&self, mut rng: impl Rng) -> Result<(SecretKey, PublicKey)> {
|
||||
let param = &self.param;
|
||||
|
||||
let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
|
||||
|
||||
let e = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
|
||||
let e = Rq::rand_f64(&mut rng, Xi_err, ¶m.ring)?;
|
||||
|
||||
let mut s = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
|
||||
let mut s = Rq::rand_f64(&mut rng, Xi_key, ¶m.ring)?;
|
||||
// since s is going to be multiplied by other Rq elements, already
|
||||
// compute its NTT
|
||||
s.compute_evals();
|
||||
|
||||
let a = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
|
||||
let a = Rq::rand_f64(&mut rng, Xi_key, ¶m.ring)?;
|
||||
|
||||
let pk: PublicKey<Q, N> = PublicKey((&(-a) * &s) + e, a.clone());
|
||||
let pk: PublicKey = PublicKey((&(-a.clone()) * &s) + e, a.clone()); // TODO rm clones
|
||||
Ok((SecretKey(s), pk))
|
||||
}
|
||||
|
||||
@@ -54,64 +66,54 @@ impl<const Q: u64, const N: usize> CKKS<Q, N> {
|
||||
fn encrypt(
|
||||
&self, // TODO maybe rm?
|
||||
mut rng: impl Rng,
|
||||
pk: &PublicKey<Q, N>,
|
||||
m: &R<N>,
|
||||
) -> Result<(Rq<Q, N>, Rq<Q, N>)> {
|
||||
pk: &PublicKey,
|
||||
m: &R,
|
||||
) -> Result<(Rq, Rq)> {
|
||||
let param = self.param;
|
||||
let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
|
||||
|
||||
let e_0 = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
|
||||
let e_1 = Rq::<Q, N>::rand_f64(&mut rng, Xi_err)?;
|
||||
let e_0 = Rq::rand_f64(&mut rng, Xi_err, ¶m.ring)?;
|
||||
let e_1 = Rq::rand_f64(&mut rng, Xi_err, ¶m.ring)?;
|
||||
|
||||
let v = Rq::<Q, N>::rand_f64(&mut rng, Xi_key)?;
|
||||
let v = Rq::rand_f64(&mut rng, Xi_key, ¶m.ring)?;
|
||||
|
||||
let m: Rq<Q, N> = Rq::<Q, N>::from(*m);
|
||||
// let m: Rq = Rq::from(*m);
|
||||
let m: Rq = m.clone().to_rq(param.ring.q); // TODO rm clone
|
||||
|
||||
Ok((m + e_0 + v * pk.0.clone(), v * pk.1.clone() + e_1))
|
||||
Ok((m + e_0 + &v * &pk.0.clone(), &v * &pk.1 + e_1))
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self, // TODO maybe rm?
|
||||
sk: &SecretKey<Q, N>,
|
||||
c: (Rq<Q, N>, Rq<Q, N>),
|
||||
) -> Result<R<N>> {
|
||||
let m = c.0.clone() + c.1 * sk.0;
|
||||
sk: &SecretKey,
|
||||
c: (Rq, Rq),
|
||||
) -> Result<R> {
|
||||
let m = c.0.clone() + &c.1 * &sk.0;
|
||||
Ok(m.mod_centered_q())
|
||||
}
|
||||
|
||||
pub fn encode_and_encrypt(
|
||||
&self,
|
||||
mut rng: impl Rng,
|
||||
pk: &PublicKey<Q, N>,
|
||||
pk: &PublicKey,
|
||||
z: &[C<f64>],
|
||||
) -> Result<(Rq<Q, N>, Rq<Q, N>)> {
|
||||
let m: R<N> = self.encoder.encode(&z)?; // polynomial (encoded vec) \in R
|
||||
) -> Result<(Rq, Rq)> {
|
||||
let m: R = self.encoder.encode(&z)?; // polynomial (encoded vec) \in R
|
||||
|
||||
self.encrypt(&mut rng, pk, &m)
|
||||
}
|
||||
|
||||
pub fn decrypt_and_decode(
|
||||
&self,
|
||||
sk: SecretKey<Q, N>,
|
||||
c: (Rq<Q, N>, Rq<Q, N>),
|
||||
) -> Result<Vec<C<f64>>> {
|
||||
pub fn decrypt_and_decode(&self, sk: SecretKey, c: (Rq, Rq)) -> Result<Vec<C<f64>>> {
|
||||
let d = self.decrypt(&sk, c)?;
|
||||
|
||||
self.encoder.decode(&d)
|
||||
}
|
||||
|
||||
pub fn add(
|
||||
&self,
|
||||
c0: &(Rq<Q, N>, Rq<Q, N>),
|
||||
c1: &(Rq<Q, N>, Rq<Q, N>),
|
||||
) -> Result<(Rq<Q, N>, Rq<Q, N>)> {
|
||||
pub fn add(&self, c0: &(Rq, Rq), c1: &(Rq, Rq)) -> Result<(Rq, Rq)> {
|
||||
Ok((&c0.0 + &c1.0, &c0.1 + &c1.1))
|
||||
}
|
||||
pub fn sub(
|
||||
&self,
|
||||
c0: &(Rq<Q, N>, Rq<Q, N>),
|
||||
c1: &(Rq<Q, N>, Rq<Q, N>),
|
||||
) -> Result<(Rq<Q, N>, Rq<Q, N>)> {
|
||||
pub fn sub(&self, c0: &(Rq, Rq), c1: &(Rq, Rq)) -> Result<(Rq, Rq)> {
|
||||
Ok((&c0.0 - &c1.0, &c0.1 + &c1.1))
|
||||
}
|
||||
}
|
||||
@@ -122,21 +124,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1;
|
||||
const N: usize = 32;
|
||||
const T: u64 = 50;
|
||||
let q: u64 = 2u64.pow(16) + 1;
|
||||
let n: usize = 32;
|
||||
let t: u64 = 50;
|
||||
let param = Param {
|
||||
ring: RingParam { q, n },
|
||||
t,
|
||||
};
|
||||
let scale_factor_u64 = 512_u64; // delta
|
||||
let scale_factor = C::<f64>::new(scale_factor_u64 as f64, 0.0); // delta
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let ckks = CKKS::<Q, N>::new(scale_factor);
|
||||
let ckks = CKKS::new(¶m, scale_factor);
|
||||
|
||||
let (sk, pk) = ckks.new_key(&mut rng)?;
|
||||
|
||||
let m_raw: R<N> = Rq::<Q, N>::rand_f64(&mut rng, Uniform::new(0_f64, T as f64))?.to_r();
|
||||
let m = m_raw * scale_factor_u64;
|
||||
let m_raw: R =
|
||||
Rq::rand_f64(&mut rng, Uniform::new(0_f64, t as f64), ¶m.ring)?.to_r();
|
||||
let m = &m_raw * &scale_factor_u64;
|
||||
|
||||
let ct = ckks.encrypt(&mut rng, &pk, &m)?;
|
||||
let m_decrypted = ckks.decrypt(&sk, ct)?;
|
||||
@@ -146,8 +153,8 @@ mod tests {
|
||||
.iter()
|
||||
.map(|e| (*e as f64 / (scale_factor_u64 as f64)).round() as u64)
|
||||
.collect();
|
||||
let m_decrypted = Rq::<Q, N>::from_vec_u64(m_decrypted);
|
||||
assert_eq!(m_decrypted, Rq::<Q, N>::from(m_raw));
|
||||
let m_decrypted = Rq::from_vec_u64(¶m.ring, m_decrypted);
|
||||
assert_eq!(m_decrypted, m_raw.to_rq(q));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -155,21 +162,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_encode_encrypt_decrypt_decode() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1;
|
||||
const N: usize = 16;
|
||||
const T: u64 = 8;
|
||||
let q: u64 = 2u64.pow(16) + 1;
|
||||
let n: usize = 16;
|
||||
let t: u64 = 8;
|
||||
let param = Param {
|
||||
ring: RingParam { q, n },
|
||||
t,
|
||||
};
|
||||
let scale_factor = C::<f64>::new(512.0, 0.0); // delta
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let ckks = CKKS::<Q, N>::new(scale_factor);
|
||||
let ckks = CKKS::new(¶m, scale_factor);
|
||||
let (sk, pk) = ckks.new_key(&mut rng)?;
|
||||
|
||||
let z: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, T))
|
||||
.take(N / 2)
|
||||
let z: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
|
||||
.take(n / 2)
|
||||
.collect();
|
||||
let m: R<N> = ckks.encoder.encode(&z)?;
|
||||
let m: R = ckks.encoder.encode(&z)?;
|
||||
println!("{}", m);
|
||||
|
||||
// sanity check
|
||||
@@ -200,26 +211,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_add() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1;
|
||||
const N: usize = 16;
|
||||
const T: u64 = 8;
|
||||
let q: u64 = 2u64.pow(16) + 1;
|
||||
let n: usize = 16;
|
||||
let t: u64 = 8;
|
||||
let param = Param {
|
||||
ring: RingParam { q, n },
|
||||
t,
|
||||
};
|
||||
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let ckks = CKKS::<Q, N>::new(scale_factor);
|
||||
let ckks = CKKS::new(¶m, scale_factor);
|
||||
|
||||
let (sk, pk) = ckks.new_key(&mut rng)?;
|
||||
|
||||
let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, T))
|
||||
.take(N / 2)
|
||||
let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
|
||||
.take(n / 2)
|
||||
.collect();
|
||||
let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, T))
|
||||
.take(N / 2)
|
||||
let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
|
||||
.take(n / 2)
|
||||
.collect();
|
||||
let m0: R<N> = ckks.encoder.encode(&z0)?;
|
||||
let m1: R<N> = ckks.encoder.encode(&z1)?;
|
||||
let m0: R = ckks.encoder.encode(&z0)?;
|
||||
let m1: R = ckks.encoder.encode(&z1)?;
|
||||
|
||||
let ct0 = ckks.encrypt(&mut rng, &pk, &m0)?;
|
||||
let ct1 = ckks.encrypt(&mut rng, &pk, &m1)?;
|
||||
@@ -243,26 +258,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sub() -> Result<()> {
|
||||
const Q: u64 = 2u64.pow(16) + 1;
|
||||
const N: usize = 16;
|
||||
const T: u64 = 8;
|
||||
let q: u64 = 2u64.pow(16) + 1;
|
||||
let n: usize = 16;
|
||||
let t: u64 = 2;
|
||||
let param = Param {
|
||||
ring: RingParam { q, n },
|
||||
t,
|
||||
};
|
||||
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let ckks = CKKS::<Q, N>::new(scale_factor);
|
||||
let ckks = CKKS::new(¶m, scale_factor);
|
||||
|
||||
let (sk, pk) = ckks.new_key(&mut rng)?;
|
||||
|
||||
let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, T))
|
||||
.take(N / 2)
|
||||
let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
|
||||
.take(n / 2)
|
||||
.collect();
|
||||
let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, T))
|
||||
.take(N / 2)
|
||||
let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
|
||||
.take(n / 2)
|
||||
.collect();
|
||||
let m0: R<N> = ckks.encoder.encode(&z0)?;
|
||||
let m1: R<N> = ckks.encoder.encode(&z1)?;
|
||||
let m0: R = ckks.encoder.encode(&z0)?;
|
||||
let m1: R = ckks.encoder.encode(&z1)?;
|
||||
|
||||
let ct0 = ckks.encrypt(&mut rng, &pk, &m0)?;
|
||||
let ct1 = ckks.encrypt(&mut rng, &pk, &m1)?;
|
||||
|
||||
Reference in New Issue
Block a user