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 Params {
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 params: Params,
34 encoder: Encoder,
35}
36
37impl CKKS {
38 pub fn new(params: &Params, delta: C<f64>) -> Self {
39 let encoder = Encoder::new(params.ring.n, delta);
40 Self {
41 params: params.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 params = &self.params;
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, ¶ms.ring)?;
53
54 let mut s = Rq::rand_f64(&mut rng, Xi_key, ¶ms.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, ¶ms.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 params = self.params;
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, ¶ms.ring)?;
77 let e_1 = Rq::rand_f64(&mut rng, Xi_err, ¶ms.ring)?;
78
79 let v = Rq::rand_f64(&mut rng, Xi_key, ¶ms.ring)?;
80
81 // let m: Rq = Rq::from(*m);
82 let m: Rq = m.clone().to_rq(params.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 params = Params {
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(¶ms, 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), ¶ms.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(¶ms.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 params = Params {
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(¶ms, 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 params = Params {
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(¶ms, 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 = 4;
265 let params = Params {
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(¶ms, 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}