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::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, m_raw.to_rq(q));
158        }
159
160        Ok(())
161    }
162
163    #[test]
164    fn test_encode_encrypt_decrypt_decode() -> Result<()> {
165        let q: u64 = 2u64.pow(16) + 1;
166        let n: usize = 16;
167        let t: u64 = 8;
168        let param = Param {
169            ring: RingParam { q, n },
170            t,
171        };
172        let scale_factor = C::<f64>::new(512.0, 0.0); // delta
173
174        let mut rng = rand::rng();
175
176        for _ in 0..1000 {
177            let ckks = CKKS::new(&param, scale_factor);
178            let (sk, pk) = ckks.new_key(&mut rng)?;
179
180            let z: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
181                .take(n / 2)
182                .collect();
183            let m: R = ckks.encoder.encode(&z)?;
184            println!("{}", m);
185
186            // sanity check
187            {
188                let z_decoded = ckks.encoder.decode(&m)?;
189                let rounded_z_decoded: Vec<C<f64>> = z_decoded
190                    .iter()
191                    .map(|c| C::<f64>::new(c.re.round(), c.im.round()))
192                    .collect();
193                assert_eq!(rounded_z_decoded, z);
194            }
195
196            let ct = ckks.encrypt(&mut rng, &pk, &m)?;
197            let m_decrypted = ckks.decrypt(&sk, ct)?;
198            println!("{}", m_decrypted);
199
200            let z_decrypted = ckks.encoder.decode(&m_decrypted)?;
201
202            let rounded_z_decrypted: Vec<C<f64>> = z_decrypted
203                .iter()
204                .map(|&c| C::<f64>::new(c.re.round(), c.im.round()))
205                .collect();
206            assert_eq!(rounded_z_decrypted, z);
207        }
208
209        Ok(())
210    }
211
212    #[test]
213    fn test_add() -> Result<()> {
214        let q: u64 = 2u64.pow(16) + 1;
215        let n: usize = 16;
216        let t: u64 = 8;
217        let param = Param {
218            ring: RingParam { q, n },
219            t,
220        };
221        let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
222
223        let mut rng = rand::rng();
224
225        for _ in 0..1000 {
226            let ckks = CKKS::new(&param, scale_factor);
227
228            let (sk, pk) = ckks.new_key(&mut rng)?;
229
230            let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
231                .take(n / 2)
232                .collect();
233            let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
234                .take(n / 2)
235                .collect();
236            let m0: R = ckks.encoder.encode(&z0)?;
237            let m1: R = ckks.encoder.encode(&z1)?;
238
239            let ct0 = ckks.encrypt(&mut rng, &pk, &m0)?;
240            let ct1 = ckks.encrypt(&mut rng, &pk, &m1)?;
241
242            let ct2 = ckks.add(&ct0, &ct1)?;
243
244            let m2_decrypted = ckks.decrypt(&sk, ct2)?;
245
246            let z_decrypted = ckks.encoder.decode(&m2_decrypted)?;
247            let rounded_z_decrypted: Vec<C<f64>> = z_decrypted
248                .iter()
249                .map(|&c| C::<f64>::new(c.re.round(), c.im.round()))
250                .collect();
251
252            let expected_z2: Vec<C<f64>> = itertools::zip_eq(z0, z1).map(|(a, b)| a + b).collect();
253            assert_eq!(rounded_z_decrypted, expected_z2);
254        }
255
256        Ok(())
257    }
258
259    #[test]
260    fn test_sub() -> Result<()> {
261        let q: u64 = 2u64.pow(16) + 1;
262        let n: usize = 16;
263        let t: u64 = 2;
264        let param = Param {
265            ring: RingParam { q, n },
266            t,
267        };
268        let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
269
270        let mut rng = rand::rng();
271
272        for _ in 0..1000 {
273            let ckks = CKKS::new(&param, scale_factor);
274
275            let (sk, pk) = ckks.new_key(&mut rng)?;
276
277            let z0: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
278                .take(n / 2)
279                .collect();
280            let z1: Vec<C<f64>> = std::iter::repeat_with(|| C::<f64>::rand(&mut rng, t))
281                .take(n / 2)
282                .collect();
283            let m0: R = ckks.encoder.encode(&z0)?;
284            let m1: R = ckks.encoder.encode(&z1)?;
285
286            let ct0 = ckks.encrypt(&mut rng, &pk, &m0)?;
287            let ct1 = ckks.encrypt(&mut rng, &pk, &m1)?;
288
289            let ct2 = ckks.sub(&ct0, &ct1)?;
290
291            let m2_decrypted = ckks.decrypt(&sk, ct2)?;
292
293            let z_decrypted = ckks.encoder.decode(&m2_decrypted)?;
294            let rounded_z_decrypted: Vec<C<f64>> = z_decrypted
295                .iter()
296                .map(|&c| C::<f64>::new(c.re.round(), c.im.round()))
297                .collect();
298
299            let expected_z2: Vec<C<f64>> = itertools::zip_eq(z0, z1).map(|(a, b)| a - b).collect();
300            assert_eq!(rounded_z_decrypted, expected_z2);
301        }
302
303        Ok(())
304    }
305}