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::{Zq, 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}
 34
 35impl<const Q: u64, const N: usize> ops::Add<RLWE<Q, N>> for RLWE<Q, N> {
 36    type Output = Self;
 37    fn add(self, rhs: Self) -> Self {
 38        Self::add(self, rhs)
 39    }
 40}
 41
 42impl<const Q: u64, const N: usize, const T: u64> ops::Add<&PR<T, N>> for &RLWE<Q, N> {
 43    type Output = RLWE<Q, N>;
 44    fn add(self, rhs: &PR<T, N>) -> Self::Output {
 45        // todo!()
 46        BFV::<Q, N, T>::add_const(self, rhs)
 47    }
 48}
 49impl<const Q: u64, const N: usize, const T: u64> ops::Mul<&PR<T, N>> for &RLWE<Q, N> {
 50    type Output = RLWE<Q, N>;
 51    fn mul(self, rhs: &PR<T, N>) -> Self::Output {
 52        // todo!()
 53        BFV::<Q, N, T>::mul_const(&self, rhs)
 54    }
 55}
 56
 57pub struct BFV<const Q: u64, const N: usize, const T: u64> {}
 58
 59impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
 60    const DELTA: u64 = Q / T;
 61
 62    /// generate a new key pair (privK, pubK)
 63    pub fn new_key(mut rng: impl Rng) -> Result<(SecretKey<Q, N>, PublicKey<Q, N>)> {
 64        // WIP: review probabilities
 65
 66        // let Xi_key = Uniform::new(-1_f64, 1_f64);
 67        let Xi_key = Uniform::new(0_u64, 2_u64);
 68        // let Xi_key = Uniform::new(0_u64, 2_u64);
 69        // use rand::distributions::Bernoulli;
 70        // let Xi_key = Bernoulli::new(0.5)?;
 71        let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
 72
 73        // secret key
 74        let s = PR::<Q, N>::rand_u64(&mut rng, Xi_key)?;
 75
 76        // pk = (-a * s + e, a)
 77        let a = PR::<Q, N>::rand_u64(&mut rng, Uniform::new(0_u64, Q))?;
 78        let e = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
 79        let pk: PublicKey<Q, N> = PublicKey((&(-a) * &s) + e, a.clone());
 80        Ok((SecretKey(s), pk))
 81    }
 82
 83    pub fn encrypt(mut rng: impl Rng, pk: &PublicKey<Q, N>, m: &PR<T, N>) -> Result<RLWE<Q, N>> {
 84        let Xi_key = Uniform::new(-1_f64, 1_f64);
 85        let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
 86
 87        let u = PR::<Q, N>::rand_f64(&mut rng, Xi_key)?;
 88        let e_1 = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
 89        let e_2 = PR::<Q, N>::rand_f64(&mut rng, Xi_err)?;
 90
 91        // migrate m's coeffs to the bigger modulus Q (from T)
 92        let m = PR::<Q, N>::from_vec_u64(m.coeffs().iter().map(|m_i| m_i.0).collect());
 93        let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
 94        let c1 = &pk.1 * &u + e_2;
 95        Ok(RLWE::<Q, N>(c0, c1))
 96    }
 97
 98    pub fn decrypt(sk: &SecretKey<Q, N>, c: &RLWE<Q, N>) -> PR<T, N> {
 99        let cs = c.0 + c.1 * sk.0; // done in mod q
100        let r: Vec<u64> = cs
101            .coeffs()
102            .iter()
103            .map(|e| ((T as f64 * e.0 as f64) / Q as f64).round() as u64)
104            .collect();
105        PR::<T, N>::from_vec_u64(r)
106    }
107
108    fn add_const(c: &RLWE<Q, N>, m: &PR<T, N>) -> RLWE<Q, N> {
109        // assuming T<Q, move m from Zq<T> to Zq<Q>
110        let m = m.remodule::<Q>();
111        RLWE::<Q, N>(c.0 + m * Self::DELTA, c.1)
112    }
113    fn mul_const(c: &RLWE<Q, N>, m: &PR<T, N>) -> RLWE<Q, N> {
114        // assuming T<Q, move m from Zq<T> to Zq<Q>
115        let m = m.remodule::<Q>();
116        RLWE::<Q, N>(c.0 * m * Self::DELTA, c.1)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use anyhow::Result;
123    use rand::distributions::Uniform;
124
125    use super::*;
126
127    #[test]
128    fn test_encrypt_decrypt() -> Result<()> {
129        const Q: u64 = 2u64.pow(16) + 1;
130        const N: usize = 32;
131        const T: u64 = 4; // plaintext modulus
132        type S = BFV<Q, N, T>;
133
134        let mut rng = rand::thread_rng();
135
136        let (sk, pk) = S::new_key(&mut rng)?;
137
138        let msg_dist = Uniform::new(0_u64, T);
139        let m = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
140
141        let c = S::encrypt(rng, &pk, &m)?;
142        let m_recovered = S::decrypt(&sk, &c);
143
144        assert_eq!(m, m_recovered);
145
146        Ok(())
147    }
148
149    #[test]
150    fn test_addition() -> Result<()> {
151        const Q: u64 = 2u64.pow(16) + 1;
152        const N: usize = 32;
153        const T: u64 = 4; // plaintext modulus
154        type S = BFV<Q, N, T>;
155
156        let mut rng = rand::thread_rng();
157
158        let (sk, pk) = S::new_key(&mut rng)?;
159
160        let msg_dist = Uniform::new(0_u64, T);
161        let m1 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
162        let m2 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
163
164        let c1 = S::encrypt(&mut rng, &pk, &m1)?;
165        let c2 = S::encrypt(&mut rng, &pk, &m2)?;
166
167        let c3 = c1 + c2;
168
169        let m3_recovered = S::decrypt(&sk, &c3);
170
171        assert_eq!(m1 + m2, m3_recovered);
172
173        Ok(())
174    }
175
176    #[test]
177    fn test_constant_add_mul() -> Result<()> {
178        const Q: u64 = 2u64.pow(16) + 1;
179        const N: usize = 32;
180        const T: u64 = 4; // plaintext modulus
181        type S = BFV<Q, N, T>;
182
183        let mut rng = rand::thread_rng();
184
185        let (sk, pk) = S::new_key(&mut rng)?;
186
187        let msg_dist = Uniform::new(0_u64, T);
188        let m1 = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
189        let m2_const = PR::<T, N>::rand_u64(&mut rng, msg_dist)?;
190
191        let c1 = S::encrypt(&mut rng, &pk, &m1)?;
192
193        let c3_add = &c1 + &m2_const;
194        let c3_mul = &c1 * &m2_const;
195
196        let m3_add_recovered = S::decrypt(&sk, &c3_add);
197        let m3_mul_recovered = S::decrypt(&sk, &c3_mul);
198
199        assert_eq!(m1 + m2_const, m3_add_recovered);
200
201        let mut mul_res = naive_poly_mul::<T>(&m1.coeffs().to_vec(), &m2_const.coeffs().to_vec());
202        arithmetic::ring::modulus::<T, N>(&mut mul_res);
203        dbg!(&mul_res);
204        let mul_res_2 =
205            naive_poly_mul_2::<T, N>(&m1.coeffs().to_vec(), &m2_const.coeffs().to_vec());
206        assert_eq!(mul_res, mul_res_2);
207        let mul_res = PR::<T, N>::from_vec(mul_res);
208        assert_eq!(mul_res.coeffs(), m3_mul_recovered.coeffs());
209
210        Ok(())
211    }
212
213    fn naive_poly_mul<const T: u64>(a: &[Zq<T>], b: &[Zq<T>]) -> Vec<Zq<T>> {
214        let mut result: Vec<Zq<T>> = vec![Zq::zero(); a.len() + b.len() - 1];
215        for (i, &ai) in a.iter().enumerate() {
216            for (j, &bj) in b.iter().enumerate() {
217                result[i + j] = result[i + j] + (ai * bj);
218            }
219        }
220        result
221    }
222    fn naive_poly_mul_2<const T: u64, const N: usize>(
223        poly1: &[Zq<T>],
224        poly2: &[Zq<T>],
225    ) -> Vec<Zq<T>> {
226        let degree1 = poly1.len();
227        let degree2 = poly2.len();
228
229        // The degree of the resulting polynomial will be degree1 + degree2 - 1
230        let mut result = vec![Zq::zero(); degree1 + degree2 - 1];
231
232        // Perform the multiplication
233        for i in 0..degree1 {
234            for j in 0..degree2 {
235                result[i + j] = result[i + j] + poly1[i] * poly2[j];
236            }
237        }
238
239        // Reduce the result modulo x^N + 1
240        let mut reduced_result = vec![Zq::zero(); N];
241
242        for i in 0..result.len() {
243            let mod_index = i % N; // wrap around using modulo N
244            reduced_result[mod_index] += result[i];
245        }
246
247        // Return the reduced polynomial
248        reduced_result
249    }
250}