arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
fhe-study/bfv/src/lib.rs
bfv/src/lib.rsRaw
  1//! Implementation of BFV https://eprint.iacr.org/2012/144.pdf
  2#![allow(non_snake_case)]
  3#![allow(non_upper_case_globals)]
  4#![allow(non_camel_case_types)]
  5#![allow(clippy::upper_case_acronyms)]
  6#![allow(dead_code)] // TMP
  7
  8use anyhow::{anyhow, Result};
  9use rand::Rng;
 10use rand_distr::{Normal, Uniform};
 11use std::ops;
 12
 13use arithmetic::PR;
 14
 15// error deviation for the Gaussian(Normal) distribution
 16// sigma=3.2 from: https://eprint.iacr.org/2022/162.pdf page 5
 17const ERR_SIGMA: f64 = 3.2;
 18
 19#[derive(Clone, Debug)]
 20pub struct SecretKey<const Q: u64, const N: usize>(PR<Q, N>);
 21
 22#[derive(Clone, Debug)]
 23pub struct PublicKey<const Q: u64, const N: usize>(PR<Q, N>, PR<Q, N>);
 24
 25// RLWE ciphertext
 26#[derive(Clone, Debug)]
 27pub struct RLWE<const Q: u64, const N: usize>(PR<Q, N>, PR<Q, N>);
 28
 29impl<const Q: u64, const N: usize> RLWE<Q, N> {
 30    fn add(lhs: Self, rhs: Self) -> Self {
 31        RLWE::<Q, N>(lhs.0 + rhs.0, lhs.1 + rhs.1)
 32    }
 33    fn mul(lhs: Self, rhs: Self) -> Self {
 34        todo!()
 35    }
 36}
 37
 38impl<const Q: u64, const N: usize> ops::Add<RLWE<Q, N>> for RLWE<Q, N> {
 39    type Output = Self;
 40    fn add(self, rhs: Self) -> Self {
 41        Self::add(self, rhs)
 42    }
 43}
 44
 45pub struct BFV<const Q: u64, const N: usize, const T: u64> {}
 46
 47impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
 48    const DELTA: u64 = Q / T;
 49
 50    /// generate a new key pair (privK, pubK)
 51    pub fn new_key(mut rng: impl Rng) -> Result<(SecretKey<Q, N>, PublicKey<Q, N>)> {
 52        // WIP: review probabilities
 53
 54        // let Xi_key = Uniform::new(-1_f64, 1_f64);
 55        let Xi_key = Uniform::new(0_u64, 2_u64);
 56        // let Xi_key = Uniform::new(0_u64, 2_u64);
 57        // use rand::distributions::Bernoulli;
 58        // let Xi_key = Bernoulli::new(0.5)?;
 59        let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
 60
 61        // secret key
 62        let s = PR::<Q, N>::rand_u64(&mut rng, Xi_key)?;
 63
 64        // pk = (-a * s + e, a)
 65        let a = PR::<Q, N>::rand_u64(&mut rng, Uniform::new(0_u64, Q))?;
 66        let e = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
 67        let pk: PublicKey<Q, N> = PublicKey((&(-a) * &s) + e, a.clone());
 68        Ok((SecretKey(s), pk))
 69    }
 70
 71    pub fn encrypt(mut rng: impl Rng, pk: &PublicKey<Q, N>, m: &PR<T, N>) -> Result<RLWE<Q, N>> {
 72        let Xi_key = Uniform::new(-1_f64, 1_f64);
 73        let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
 74
 75        let u = PR::<Q, N>::rand_f64(&mut rng, Xi_key)?;
 76        let e_1 = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
 77        let e_2 = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
 78
 79        // migrate m's coeffs to the bigger modulus Q (from T)
 80        let m = PR::<Q, N>::from_vec_u64(m.coeffs().iter().map(|m_i| m_i.0).collect());
 81        let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
 82        let c1 = &pk.1 * &u + e_2;
 83        Ok(RLWE::<Q, N>(c0, c1))
 84    }
 85
 86    pub fn decrypt(sk: &SecretKey<Q, N>, c: &RLWE<Q, N>) -> PR<T, N> {
 87        let cs = c.0 + c.1 * sk.0; // done in mod q
 88        let r: Vec<u64> = cs
 89            .coeffs()
 90            .iter()
 91            .map(|e| ((T as f64 * e.0 as f64) / Q as f64).round() as u64)
 92            .collect();
 93        PR::<T, N>::from_vec_u64(r)
 94    }
 95}
 96
 97#[cfg(test)]
 98mod tests {
 99    use anyhow::Result;
100    use rand::distributions::Uniform;
101
102    use super::*;
103
104    #[test]
105    fn test_encrypt_decrypt() -> Result<()> {
106        const Q: u64 = 2u64.pow(16) + 1;
107        const N: usize = 32;
108        const T: u64 = 4; // plaintext modulus
109        type S = BFV<Q, N, T>;
110
111        let mut rng = rand::thread_rng();
112
113        let (sk, pk) = S::new_key(&mut rng)?;
114
115        let msg_dist = Uniform::new(0_u64, T);
116        let m = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
117
118        let c = S::encrypt(rng, &pk, &m)?;
119        let m_recovered = S::decrypt(&sk, &c);
120
121        assert_eq!(m, m_recovered);
122
123        Ok(())
124    }
125
126    #[test]
127    fn test_addition() -> Result<()> {
128        const Q: u64 = 2u64.pow(16) + 1;
129        const N: usize = 32;
130        const T: u64 = 4; // plaintext modulus
131        type S = BFV<Q, N, T>;
132
133        let mut rng = rand::thread_rng();
134
135        let (sk, pk) = S::new_key(&mut rng)?;
136
137        let msg_dist = Uniform::new(0_u64, T);
138        let m1 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
139        let m2 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
140
141        let c1 = S::encrypt(&mut rng, &pk, &m1)?;
142        let c2 = S::encrypt(&mut rng, &pk, &m2)?;
143
144        let c3 = c1 + c2;
145
146        let m3_recovered = S::decrypt(&sk, &c3);
147
148        assert_eq!(m1 + m2, m3_recovered);
149
150        Ok(())
151    }
152}