arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
fhe-study/ckks/src/lib.rs
ckks/src/lib.rsRaw
  1//! Implementation of CKKS https://eprint.iacr.org/2016/421.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 arith::{RingParam, Rq, C, R};
  9
 10use anyhow::Result;
 11use rand::Rng;
 12use rand_distr::{Normal, Uniform};
 13
 14pub mod encoder;
 15pub use encoder::Encoder;
 16
 17// error deviation for the Gaussian(Normal) distribution
 18// sigma=3.2 from: https://eprint.iacr.org/2016/421.pdf page 17
 19const ERR_SIGMA: f64 = 3.2;
 20
 21#[derive(Clone, Copy, Debug)]
 22pub struct Param {
 23    ring: RingParam,
 24    t: u64,
 25}
 26
 27#[derive(Debug)]
 28pub struct PublicKey(Rq, Rq);
 29
 30pub struct SecretKey(Rq);
 31
 32pub struct CKKS {
 33    param: Param,
 34    encoder: Encoder,
 35}
 36
 37impl CKKS {
 38    pub fn new(param: &Param, delta: C<f64>) -> Self {
 39        let encoder = Encoder::new(param.ring.n, delta);
 40        Self {
 41            param: param.clone(),
 42            encoder,
 43        }
 44    }
 45    /// generate a new key pair (privK, pubK)
 46    pub fn new_key(&self, mut rng: impl Rng) -> Result<(SecretKey, PublicKey)> {
 47        let param = &self.param;
 48
 49        let Xi_key = Uniform::new(-1_f64, 1_f64);
 50        let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
 51
 52        let e = Rq::rand_f64(&mut rng, Xi_err, &param.ring)?;
 53
 54        let mut s = Rq::rand_f64(&mut rng, Xi_key, &param.ring)?;
 55        // since s is going to be multiplied by other Rq elements, already
 56        // compute its NTT
 57        s.compute_evals();
 58
 59        let a = Rq::rand_f64(&mut rng, Xi_key, &param.ring)?;
 60
 61        let pk: PublicKey = PublicKey((&(-a.clone()) * &s) + e, a.clone()); // TODO rm clones
 62        Ok((SecretKey(s), pk))
 63    }
 64
 65    // encrypts a plaintext \in R=Z_Q[X]/(X^N+1)
 66    fn encrypt(
 67        &self, // TODO maybe rm?
 68        mut rng: impl Rng,
 69        pk: &PublicKey,
 70        m: &R,
 71    ) -> Result<(Rq, Rq)> {
 72        let param = self.param;
 73        let Xi_key = Uniform::new(-1_f64, 1_f64);
 74        let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
 75
 76        let e_0 = Rq::rand_f64(&mut rng, Xi_err, &param.ring)?;
 77        let e_1 = Rq::rand_f64(&mut rng, Xi_err, &param.ring)?;
 78
 79        let v = Rq::rand_f64(&mut rng, Xi_key, &param.ring)?;
 80
 81        // let m: Rq = Rq::from(*m);
 82        let m: Rq = m.clone().to_rq(param.ring.q); // TODO rm clone
 83
 84        Ok((m + e_0 + &v * &pk.0.clone(), &v * &pk.1 + e_1))
 85    }
 86
 87    fn decrypt(
 88        &self, // TODO maybe rm?
 89        sk: &SecretKey,
 90        c: (Rq, Rq),
 91    ) -> Result<R> {
 92        let m = c.0.clone() + &c.1 * &sk.0;
 93        Ok(m.mod_centered_q())
 94    }
 95
 96    pub fn encode_and_encrypt(
 97        &self,
 98        mut rng: impl Rng,
 99        pk: &PublicKey,
100        z: &[C<f64>],
101    ) -> Result<(Rq, Rq)> {
102        let m: R = self.encoder.encode(&z)?; // polynomial (encoded vec) \in R
103
104        self.encrypt(&mut rng, pk, &m)
105    }
106
107    pub fn decrypt_and_decode(&self, sk: SecretKey, c: (Rq, Rq)) -> Result<Vec<C<f64>>> {
108        let d = self.decrypt(&sk, c)?;
109
110        self.encoder.decode(&d)
111    }
112
113    pub fn add(&self, c0: &(Rq, Rq), c1: &(Rq, Rq)) -> Result<(Rq, Rq)> {
114        Ok((&c0.0 + &c1.0, &c0.1 + &c1.1))
115    }
116    pub fn sub(&self, c0: &(Rq, Rq), c1: &(Rq, Rq)) -> Result<(Rq, Rq)> {
117        Ok((&c0.0 - &c1.0, &c0.1 + &c1.1))
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_encrypt_decrypt() -> Result<()> {
127        let q: u64 = 2u64.pow(16) + 1;
128        let n: usize = 32;
129        let t: u64 = 50;
130        let param = Param {
131            ring: RingParam { q, n },
132            t,
133        };
134        let scale_factor_u64 = 512_u64; // delta
135        let scale_factor = C::<f64>::new(scale_factor_u64 as f64, 0.0); // delta
136
137        let mut rng = rand::thread_rng();
138
139        for _ in 0..1000 {
140            let ckks = CKKS::new(&param, scale_factor);
141
142            let (sk, pk) = ckks.new_key(&mut rng)?;
143
144            let m_raw: R =
145                Rq::rand_f64(&mut rng, Uniform::new(0_f64, t as f64), &param.ring)?.to_r();
146            let m = &m_raw * &scale_factor_u64;
147
148            let ct = ckks.encrypt(&mut rng, &pk, &m)?;
149            let m_decrypted = ckks.decrypt(&sk, ct)?;
150
151            let m_decrypted: Vec<u64> = m_decrypted
152                .coeffs()
153                .iter()
154                .map(|e| (*e as f64 / (scale_factor_u64 as f64)).round() as u64)
155                .collect();
156            let m_decrypted = Rq::from_vec_u64(&param.ring, m_decrypted);
157            // assert_eq!(m_decrypted, Rq::from(m_raw));
158            assert_eq!(m_decrypted, m_raw.to_rq(q));
159        }
160
161        Ok(())
162    }
163
164    #[test]
165    fn test_encode_encrypt_decrypt_decode() -> Result<()> {
166        let q: u64 = 2u64.pow(16) + 1;
167        let n: usize = 16;
168        let t: u64 = 8;
169        let param = Param {
170            ring: RingParam { q, n },
171            t,
172        };
173        let scale_factor = C::<f64>::new(512.0, 0.0); // delta
174
175        let mut rng = rand::thread_rng();
176
177        for _ in 0..1000 {
178            let ckks = CKKS::new(&param, scale_factor);
179            let (sk, pk) = ckks.new_key(&mut rng)?;
180
181            let z: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
182                .take(n / 2)
183                .collect();
184            let m: R = ckks.encoder.encode(&z)?;
185            println!("{}", m);
186
187            // sanity check
188            {
189                let z_decoded = ckks.encoder.decode(&m)?;
190                let rounded_z_decoded: Vec<C<f64>> = z_decoded
191                    .iter()
192                    .map(|c| C::<f64>::new(c.re.round(), c.im.round()))
193                    .collect();
194                assert_eq!(rounded_z_decoded, z);
195            }
196
197            let ct = ckks.encrypt(&mut rng, &pk, &m)?;
198            let m_decrypted = ckks.decrypt(&sk, ct)?;
199            println!("{}", m_decrypted);
200
201            let z_decrypted = ckks.encoder.decode(&m_decrypted)?;
202
203            let rounded_z_decrypted: Vec<C<f64>> = z_decrypted
204                .iter()
205                .map(|&c| C::<f64>::new(c.re.round(), c.im.round()))
206                .collect();
207            assert_eq!(rounded_z_decrypted, z);
208        }
209
210        Ok(())
211    }
212
213    #[test]
214    fn test_add() -> Result<()> {
215        let q: u64 = 2u64.pow(16) + 1;
216        let n: usize = 16;
217        let t: u64 = 8;
218        let param = Param {
219            ring: RingParam { q, n },
220            t,
221        };
222        let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
223
224        let mut rng = rand::thread_rng();
225
226        for _ in 0..1000 {
227            let ckks = CKKS::new(&param, scale_factor);
228
229            let (sk, pk) = ckks.new_key(&mut rng)?;
230
231            let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
232                .take(n / 2)
233                .collect();
234            let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
235                .take(n / 2)
236                .collect();
237            let m0: R = ckks.encoder.encode(&z0)?;
238            let m1: R = ckks.encoder.encode(&z1)?;
239
240            let ct0 = ckks.encrypt(&mut rng, &pk, &m0)?;
241            let ct1 = ckks.encrypt(&mut rng, &pk, &m1)?;
242
243            let ct2 = ckks.add(&ct0, &ct1)?;
244
245            let m2_decrypted = ckks.decrypt(&sk, ct2)?;
246
247            let z_decrypted = ckks.encoder.decode(&m2_decrypted)?;
248            let rounded_z_decrypted: Vec<C<f64>> = z_decrypted
249                .iter()
250                .map(|&c| C::<f64>::new(c.re.round(), c.im.round()))
251                .collect();
252
253            let expected_z2: Vec<C<f64>> = itertools::zip_eq(z0, z1).map(|(a, b)| a + b).collect();
254            assert_eq!(rounded_z_decrypted, expected_z2);
255        }
256
257        Ok(())
258    }
259
260    #[test]
261    fn test_sub() -> Result<()> {
262        let q: u64 = 2u64.pow(16) + 1;
263        let n: usize = 16;
264        let t: u64 = 2;
265        let param = Param {
266            ring: RingParam { q, n },
267            t,
268        };
269        let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
270
271        let mut rng = rand::thread_rng();
272
273        for _ in 0..1000 {
274            let ckks = CKKS::new(&param, scale_factor);
275
276            let (sk, pk) = ckks.new_key(&mut rng)?;
277
278            let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
279                .take(n / 2)
280                .collect();
281            let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
282                .take(n / 2)
283                .collect();
284            let m0: R = ckks.encoder.encode(&z0)?;
285            let m1: R = ckks.encoder.encode(&z1)?;
286
287            let ct0 = ckks.encrypt(&mut rng, &pk, &m0)?;
288            let ct1 = ckks.encrypt(&mut rng, &pk, &m1)?;
289
290            let ct2 = ckks.sub(&ct0, &ct1)?;
291
292            let m2_decrypted = ckks.decrypt(&sk, ct2)?;
293
294            let z_decrypted = ckks.encoder.decode(&m2_decrypted)?;
295            let rounded_z_decrypted: Vec<C<f64>> = z_decrypted
296                .iter()
297                .map(|&c| C::<f64>::new(c.re.round(), c.im.round()))
298                .collect();
299
300            let expected_z2: Vec<C<f64>> = itertools::zip_eq(z0, z1).map(|(a, b)| a - b).collect();
301            assert_eq!(rounded_z_decrypted, expected_z2);
302        }
303
304        Ok(())
305    }
306}