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, ¶m.ring)?;
53
54 let mut s = Rq::rand_f64(&mut rng, Xi_key, ¶m.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, ¶m.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, ¶m.ring)?;
77 let e_1 = Rq::rand_f64(&mut rng, Xi_err, ¶m.ring)?;
78
79 let v = Rq::rand_f64(&mut rng, Xi_key, ¶m.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(¶m, 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)?, ¶m.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(¶m.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(¶m, 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(¶m, 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(¶m, 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}