mirror of
https://github.com/arnaucube/fhe-study.git
synced 2026-01-24 04:33:52 +01:00
add BFV newkey, encrypt, decrypt, and homomorphic addition impl
This commit is contained in:
@@ -136,7 +136,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let uniform_distr = Uniform::new(0_f64, Q as f64);
|
let uniform_distr = Uniform::new(0_f64, Q as f64);
|
||||||
let a = PR::<Q, N>::rand(&mut rng, uniform_distr)?;
|
let a = PR::<Q, N>::rand_f64(&mut rng, uniform_distr)?;
|
||||||
// let a = PR::<Q, N>::new_from_u64(vec![36, 21, 9, 19]);
|
// let a = PR::<Q, N>::new_from_u64(vec![36, 21, 9, 19]);
|
||||||
|
|
||||||
// let a_padded_coeffs: [Zq<Q>; 2 * N] =
|
// let a_padded_coeffs: [Zq<Q>; 2 * N] =
|
||||||
@@ -181,7 +181,7 @@ mod tests {
|
|||||||
let ntt = NTT::<Q, N>::new()?;
|
let ntt = NTT::<Q, N>::new()?;
|
||||||
|
|
||||||
let rng = rand::thread_rng();
|
let rng = rand::thread_rng();
|
||||||
let a = PR::<Q, { 2 * N }>::rand(rng, Uniform::new(0_f64, (Q - 1) as f64))?;
|
let a = PR::<Q, { 2 * N }>::rand_f64(rng, Uniform::new(0_f64, (Q - 1) as f64))?;
|
||||||
let a = a.coeffs;
|
let a = a.coeffs;
|
||||||
dbg!(&a);
|
dbg!(&a);
|
||||||
let a_ntt = matrix_vec_product(&ntt.ntt, &a.to_vec())?;
|
let a_ntt = matrix_vec_product(&ntt.ntt, &a.to_vec())?;
|
||||||
@@ -189,6 +189,7 @@ mod tests {
|
|||||||
let a_intt = matrix_vec_product(&ntt.intt, &a_ntt)?;
|
let a_intt = matrix_vec_product(&ntt.intt, &a_ntt)?;
|
||||||
dbg!(&a_intt);
|
dbg!(&a_intt);
|
||||||
assert_eq!(a_intt, a);
|
assert_eq!(a_intt, a);
|
||||||
|
// TODO bench
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//! Implementation of the NTT & iNTT, following the CT & GS algorighms, more
|
//! Implementation of the NTT & iNTT, following the CT & GS algorighms, more details in
|
||||||
//! details in https://github.com/arnaucube/math/blob/master/notes_ntt.pdf .
|
//! https://eprint.iacr.org/2017/727.pdf, some notes at
|
||||||
|
//! https://github.com/arnaucube/math/blob/master/notes_ntt.pdf .
|
||||||
use crate::zq::Zq;
|
use crate::zq::Zq;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -14,7 +15,8 @@ impl<const Q: u64, const N: usize> NTT<Q, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const Q: u64, const N: usize> NTT<Q, N> {
|
impl<const Q: u64, const N: usize> NTT<Q, N> {
|
||||||
/// implements the Cooley-Tukey (CT) algorithm. Details at section 3.1 of
|
/// implements the Cooley-Tukey (CT) algorithm. Details at
|
||||||
|
/// https://eprint.iacr.org/2017/727.pdf, also some notes at section 3.1 of
|
||||||
/// https://github.com/arnaucube/math/blob/master/notes_ntt.pdf
|
/// https://github.com/arnaucube/math/blob/master/notes_ntt.pdf
|
||||||
pub fn ntt(a: [Zq<Q>; N]) -> [Zq<Q>; N] {
|
pub fn ntt(a: [Zq<Q>; N]) -> [Zq<Q>; N] {
|
||||||
let mut t = N / 2;
|
let mut t = N / 2;
|
||||||
@@ -38,7 +40,8 @@ impl<const Q: u64, const N: usize> NTT<Q, N> {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
/// implements the Gentleman-Sande (GS) algorithm. Details at section 3.2 of
|
/// implements the Cooley-Tukey (CT) algorithm. Details at
|
||||||
|
/// https://eprint.iacr.org/2017/727.pdf, also some notes at section 3.2 of
|
||||||
/// https://github.com/arnaucube/math/blob/master/notes_ntt.pdf
|
/// https://github.com/arnaucube/math/blob/master/notes_ntt.pdf
|
||||||
pub fn intt(a: [Zq<Q>; N]) -> [Zq<Q>; N] {
|
pub fn intt(a: [Zq<Q>; N]) -> [Zq<Q>; N] {
|
||||||
let mut t = 1;
|
let mut t = 1;
|
||||||
|
|||||||
@@ -63,13 +63,20 @@ impl<const Q: u64, const N: usize> PR<Q, N> {
|
|||||||
evals: None,
|
evals: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Result<Self> {
|
pub fn rand_f64(mut rng: impl Rng, dist: impl Distribution<f64>) -> Result<Self> {
|
||||||
let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng)));
|
let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_f64(dist.sample(&mut rng)));
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
coeffs,
|
coeffs,
|
||||||
evals: None,
|
evals: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn rand_u64(mut rng: impl Rng, dist: impl Distribution<u64>) -> Result<Self> {
|
||||||
|
let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::new(dist.sample(&mut rng)));
|
||||||
|
Ok(Self {
|
||||||
|
coeffs,
|
||||||
|
evals: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
// WIP. returns random v \in {0,1}. // TODO {-1, 0, 1}
|
// WIP. returns random v \in {0,1}. // TODO {-1, 0, 1}
|
||||||
pub fn rand_bin(mut rng: impl Rng, dist: impl Distribution<bool>) -> Result<Self> {
|
pub fn rand_bin(mut rng: impl Rng, dist: impl Distribution<bool>) -> Result<Self> {
|
||||||
let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_bool(dist.sample(&mut rng)));
|
let coeffs: [Zq<Q>; N] = array::from_fn(|_| Zq::from_bool(dist.sample(&mut rng)));
|
||||||
|
|||||||
11
bfv/Cargo.toml
Normal file
11
bfv/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "bfv"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
rand_distr = { workspace = true }
|
||||||
|
|
||||||
|
arithmetic = { path="../arithmetic" }
|
||||||
152
bfv/src/lib.rs
Normal file
152
bfv/src/lib.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
//! Implementation of BFV https://eprint.iacr.org/2012/144.pdf
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(clippy::upper_case_acronyms)]
|
||||||
|
#![allow(dead_code)] // TMP
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use rand::Rng;
|
||||||
|
use rand_distr::{Normal, Uniform};
|
||||||
|
use std::ops;
|
||||||
|
|
||||||
|
use arithmetic::PR;
|
||||||
|
|
||||||
|
// error deviation for the Gaussian(Normal) distribution
|
||||||
|
// sigma=3.2 from: https://eprint.iacr.org/2022/162.pdf page 5
|
||||||
|
const ERR_SIGMA: f64 = 3.2;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SecretKey<const Q: u64, const N: usize>(PR<Q, N>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PublicKey<const Q: u64, const N: usize>(PR<Q, N>, PR<Q, N>);
|
||||||
|
|
||||||
|
// RLWE ciphertext
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RLWE<const Q: u64, const N: usize>(PR<Q, N>, PR<Q, N>);
|
||||||
|
|
||||||
|
impl<const Q: u64, const N: usize> RLWE<Q, N> {
|
||||||
|
fn add(lhs: Self, rhs: Self) -> Self {
|
||||||
|
RLWE::<Q, N>(lhs.0 + rhs.0, lhs.1 + rhs.1)
|
||||||
|
}
|
||||||
|
fn mul(lhs: Self, rhs: Self) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const Q: u64, const N: usize> ops::Add<RLWE<Q, N>> for RLWE<Q, N> {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Self::add(self, rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BFV<const Q: u64, const N: usize, const T: u64> {}
|
||||||
|
|
||||||
|
impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
||||||
|
const DELTA: u64 = Q / T;
|
||||||
|
|
||||||
|
/// generate a new key pair (privK, pubK)
|
||||||
|
pub fn new_key(mut rng: impl Rng) -> Result<(SecretKey<Q, N>, PublicKey<Q, N>)> {
|
||||||
|
// WIP: review probabilities
|
||||||
|
|
||||||
|
// let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||||
|
let Xi_key = Uniform::new(0_u64, 2_u64);
|
||||||
|
// let Xi_key = Uniform::new(0_u64, 2_u64);
|
||||||
|
// use rand::distributions::Bernoulli;
|
||||||
|
// let Xi_key = Bernoulli::new(0.5)?;
|
||||||
|
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
|
||||||
|
|
||||||
|
// secret key
|
||||||
|
let s = PR::<Q, N>::rand_u64(&mut rng, Xi_key)?;
|
||||||
|
|
||||||
|
// pk = (-a * s + e, a)
|
||||||
|
let a = PR::<Q, N>::rand_u64(&mut rng, Uniform::new(0_u64, Q))?;
|
||||||
|
let e = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
|
||||||
|
let pk: PublicKey<Q, N> = PublicKey((&(-a) * &s) + e, a.clone());
|
||||||
|
Ok((SecretKey(s), pk))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(mut rng: impl Rng, pk: &PublicKey<Q, N>, m: &PR<T, N>) -> Result<RLWE<Q, N>> {
|
||||||
|
let Xi_key = Uniform::new(-1_f64, 1_f64);
|
||||||
|
let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
|
||||||
|
|
||||||
|
let u = PR::<Q, N>::rand_f64(&mut rng, Xi_key)?;
|
||||||
|
let e_1 = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
|
||||||
|
let e_2 = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
|
||||||
|
|
||||||
|
// migrate m's coeffs to the bigger modulus Q (from T)
|
||||||
|
let m = PR::<Q, N>::from_vec_u64(m.coeffs().iter().map(|m_i| m_i.0).collect());
|
||||||
|
let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
|
||||||
|
let c1 = &pk.1 * &u + e_2;
|
||||||
|
Ok(RLWE::<Q, N>(c0, c1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(sk: &SecretKey<Q, N>, c: &RLWE<Q, N>) -> PR<T, N> {
|
||||||
|
let cs = c.0 + c.1 * sk.0; // done in mod q
|
||||||
|
let r: Vec<u64> = cs
|
||||||
|
.coeffs()
|
||||||
|
.iter()
|
||||||
|
.map(|e| ((T as f64 * e.0 as f64) / Q as f64).round() as u64)
|
||||||
|
.collect();
|
||||||
|
PR::<T, N>::from_vec_u64(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use anyhow::Result;
|
||||||
|
use rand::distributions::Uniform;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encrypt_decrypt() -> Result<()> {
|
||||||
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
|
const N: usize = 32;
|
||||||
|
const T: u64 = 4; // plaintext modulus
|
||||||
|
type S = BFV<Q, N, T>;
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let (sk, pk) = S::new_key(&mut rng)?;
|
||||||
|
|
||||||
|
let msg_dist = Uniform::new(0_u64, T);
|
||||||
|
let m = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
|
||||||
|
|
||||||
|
let c = S::encrypt(rng, &pk, &m)?;
|
||||||
|
let m_recovered = S::decrypt(&sk, &c);
|
||||||
|
|
||||||
|
assert_eq!(m, m_recovered);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_addition() -> Result<()> {
|
||||||
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
|
const N: usize = 32;
|
||||||
|
const T: u64 = 4; // plaintext modulus
|
||||||
|
type S = BFV<Q, N, T>;
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let (sk, pk) = S::new_key(&mut rng)?;
|
||||||
|
|
||||||
|
let msg_dist = Uniform::new(0_u64, T);
|
||||||
|
let m1 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
|
||||||
|
let m2 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
|
||||||
|
|
||||||
|
let c1 = S::encrypt(&mut rng, &pk, &m1)?;
|
||||||
|
let c2 = S::encrypt(&mut rng, &pk, &m2)?;
|
||||||
|
|
||||||
|
let c3 = c1 + c2;
|
||||||
|
|
||||||
|
let m3_recovered = S::decrypt(&sk, &c3);
|
||||||
|
|
||||||
|
assert_eq!(m1 + m2, m3_recovered);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user