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::Result;
9use rand::Rng;
10use rand_distr::{Normal, Uniform};
11use std::ops;
12
13use arith::{Ring, Rq, R};
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, Copy, Debug)]
20pub struct Params {
21 q: u64,
22 n: usize,
23 t: u64,
24 p: u64,
25}
26
27#[derive(Clone, Debug)]
28pub struct SecretKey(Rq);
29
30#[derive(Clone, Debug)]
31pub struct PublicKey(Rq, Rq);
32
33/// Relinearization key
34#[derive(Clone, Debug)]
35pub struct RLK(Rq, Rq);
36
37// RLWE ciphertext
38#[derive(Clone, Debug)]
39pub struct RLWE(Rq, Rq);
40
41impl RLWE {
42 fn add(lhs: Self, rhs: Self) -> Self {
43 RLWE(lhs.0 + rhs.0, lhs.1 + rhs.1)
44 }
45 pub fn remodule(&self, p: u64) -> RLWE {
46 let x = self.0.remodule(p);
47 let y = self.1.remodule(p);
48 RLWE(x, y)
49 }
50
51 fn tensor(t: u64, a: &Self, b: &Self) -> (Rq, Rq, Rq) {
52 let (q, n) = (a.0.q, a.0.n);
53 // expand Q->PQ // TODO rm
54
55 // get the coefficients in Z, ie. interpret a,b \in R (instead of R_q)
56 let a0: R = a.0.clone().to_r(); // TODO rm clone()
57 let a1: R = a.1.clone().to_r();
58 let b0: R = b.0.clone().to_r();
59 let b1: R = b.1.clone().to_r();
60
61 // tensor (\in R) (2021-204 p.9)
62 // NOTE: here can use *, but at first versions want to make it explicit
63 // that we're using the naive mul. TODO use *.
64 use arith::ring_n::naive_mul;
65 let c0: Vec<i64> = naive_mul(&a0, &b0);
66 let c1_l: Vec<i64> = naive_mul(&a0, &b1);
67 let c1_r = naive_mul(&a1, &b0);
68 let c1: Vec<i64> = itertools::zip_eq(c1_l, c1_r).map(|(l, r)| l + r).collect();
69 let c2: Vec<i64> = naive_mul(&a1, &b1);
70
71 // scale down, then reduce module Q, so result is \in R_q
72 let c0: Rq = arith::ring_n::mul_div_round(q, n, c0, t, q);
73 let c1: Rq = arith::ring_n::mul_div_round(q, n, c1, t, q);
74 let c2: Rq = arith::ring_n::mul_div_round(q, n, c2, t, q);
75
76 (c0, c1, c2)
77 }
78 /// ciphertext multiplication
79 fn mul(t: u64, rlk: &RLK, a: &Self, b: &Self) -> Self {
80 let (c0, c1, c2) = Self::tensor(t, a, b);
81 BFV::relinearize_204(&rlk, &c0, &c1, &c2)
82 }
83}
84// naive mul in the ring Rq, reusing the ring_n::naive_mul and then applying mod(X^N +1)
85fn tmp_naive_mul(a: Rq, b: Rq) -> Rq {
86 Rq::from_vec_i64(a.q, a.n, arith::ring_n::naive_mul(&a.to_r(), &b.to_r()))
87}
88
89impl ops::Add<RLWE> for RLWE {
90 type Output = Self;
91 fn add(self, rhs: Self) -> Self {
92 Self::add(self, rhs)
93 }
94}
95
96impl ops::Add<&Rq> for &RLWE {
97 type Output = RLWE;
98 fn add(self, rhs: &Rq) -> Self::Output {
99 BFV::add_const(self, rhs)
100 }
101}
102
103pub struct BFV {}
104
105impl BFV {
106 // const DELTA: u64 = Q / T; // floor
107
108 /// generate a new key pair (privK, pubK)
109 pub fn new_key(mut rng: impl Rng, params: &Params) -> Result<(SecretKey, PublicKey)> {
110 // WIP: review probabilities
111
112 // let Xi_key = Uniform::new(-1_f64, 1_f64);
113 let Xi_key = Uniform::new(0_u64, 2_u64);
114 let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
115
116 // secret key
117 // let mut s = Rq::rand_f64(&mut rng, Xi_key)?;
118 let mut s = Rq::rand_u64(&mut rng, Xi_key, params.q, params.n)?;
119 // since s is going to be multiplied by other Rq elements, already
120 // compute its NTT
121 s.compute_evals();
122
123 // pk = (-a * s + e, a)
124 let a = Rq::rand_u64(&mut rng, Uniform::new(0_u64, params.q), params.q, params.n)?;
125 let e = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
126 let pk: PublicKey = PublicKey(&(&(-a.clone()) * &s) + &e, a.clone()); // TODO rm clones
127 Ok((SecretKey(s), pk))
128 }
129
130 // note: m is modulus t
131 pub fn encrypt(mut rng: impl Rng, params: &Params, pk: &PublicKey, m: &Rq) -> Result<RLWE> {
132 // assert params & inputs
133 debug_assert_eq!(params.q, pk.0.q);
134 debug_assert_eq!(params.n, pk.0.n);
135 debug_assert_eq!(params.t, m.q);
136 debug_assert_eq!(params.n, m.n);
137
138 let Xi_key = Uniform::new(-1_f64, 1_f64);
139 // let Xi_key = Uniform::new(0_u64, 2_u64);
140 let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
141
142 let u = Rq::rand_f64(&mut rng, Xi_key, params.q, params.n)?;
143 // let u = Rq::rand_u64(&mut rng, Xi_key)?;
144 let e_1 = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
145 let e_2 = Rq::rand_f64(&mut rng, Xi_err, params.q, params.n)?;
146
147 // migrate m's coeffs to the bigger modulus Q (from T)
148 let m = m.remodule(params.q);
149 let c0 = &pk.0 * &u + e_1 + m * (params.q / params.t); // floor(q/t)=DELTA
150 let c1 = &pk.1 * &u + e_2;
151 Ok(RLWE(c0, c1))
152 }
153
154 pub fn decrypt(params: &Params, sk: &SecretKey, c: &RLWE) -> Rq {
155 debug_assert_eq!(params.q, sk.0.q);
156 debug_assert_eq!(params.n, sk.0.n);
157 debug_assert_eq!(params.q, c.0.q);
158 debug_assert_eq!(params.n, c.0.n);
159
160 let cs: Rq = &c.0 + &(&c.1 * &sk.0); // done in mod q
161
162 // same but with naive_mul:
163 // let c1s = arith::ring_n::naive_mul(&c.1.to_r(), &sk.0.to_r());
164 // let c1s = Rq::from_vec_i64(c1s);
165 // let cs = c.0 + c1s;
166
167 let r: Rq = cs.mul_div_round(params.t, params.q);
168 r.remodule(params.t)
169 }
170
171 fn add_const(c: &RLWE, m: &Rq) -> RLWE {
172 let q = c.0.q;
173 let t = m.q;
174
175 // assuming T<Q, move m from Zq<T> to Zq<Q>
176 let m = m.remodule(c.0.q);
177 // TODO rm clones
178 RLWE(c.0.clone() + m * (q / t), c.1.clone()) // floor(q/t)=DELTA
179 }
180 fn mul_const(rlk: &RLK, c: &RLWE, m: &Rq) -> RLWE {
181 // let pq = rlk.0.q;
182 let q = c.0.q;
183 let n = c.0.n;
184 let t = m.q;
185
186 // assuming T<Q, move m from Zq<T> to Zq<Q>
187 let m = m.remodule(q);
188
189 // encrypt m*Delta without noise, and then perform normal ciphertext multiplication
190 let md = RLWE(m * (q / t), Rq::zero((q, n))); // floor(q/t)=DELTA
191 RLWE::mul(t, &rlk, &c, &md)
192 }
193
194 fn rlk_key(mut rng: impl Rng, params: &Params, s: &SecretKey) -> Result<RLK> {
195 let pq = params.p * params.q;
196 let n = params.n;
197
198 // TODO review using Xi' instead of Xi
199 let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
200 // let Xi_err = Normal::new(0_f64, 0.0)?;
201 let s = s.0.remodule(pq);
202 let a = Rq::rand_u64(&mut rng, Uniform::new(0_u64, pq), pq, n)?;
203 let e = Rq::rand_f64(&mut rng, Xi_err, pq, n)?;
204
205 // let rlk: RLK<PQ, N> = RLK::<PQ, N>(-(&a * &s + e) + (s * s) * P, a.clone());
206 // TODO rm clones
207 let rlk: RLK = RLK(
208 -(tmp_naive_mul(a.clone(), s.clone()) + e)
209 + tmp_naive_mul(s.clone(), s.clone()) * params.p,
210 a.clone(),
211 );
212
213 Ok(rlk)
214 }
215
216 fn relinearize(rlk: &RLK, c0: &Rq, c1: &Rq, c2: &Rq) -> RLWE {
217 let pq = rlk.0.q;
218 let q = c0.q;
219 let p = pq / q;
220 let n = c0.n;
221
222 let c2rlk0: Vec<f64> = (c2.clone().to_r() * rlk.0.clone().to_r())
223 .coeffs()
224 .iter()
225 .map(|e| (*e as f64 / p as f64).round())
226 .collect();
227
228 let c2rlk1: Vec<f64> = (c2.clone().to_r() * rlk.1.clone().to_r()) // TODO rm clones
229 .coeffs()
230 .iter()
231 .map(|e| (*e as f64 / p as f64).round())
232 .collect();
233
234 let r0 = Rq::from_vec_f64(q, n, c2rlk0);
235 let r1 = Rq::from_vec_f64(q, n, c2rlk1);
236
237 let res = RLWE(c0 + &r0, c1 + &r1);
238 res
239 }
240 fn relinearize_204(rlk: &RLK, c0: &Rq, c1: &Rq, c2: &Rq) -> RLWE {
241 let pq = rlk.0.q;
242 let q = c0.q;
243 let p = pq / q;
244 let n = c0.n;
245 // TODO (in debug) check that all Ns match
246
247 // let c2rlk0: Rq<PQ, N> = c2.remodule::<PQ>() * rlk.0.remodule::<PQ>();
248 // let c2rlk1: Rq<PQ, N> = c2.remodule::<PQ>() * rlk.1.remodule::<PQ>();
249 // let r0: Rq<Q, N> = c2rlk0.mul_div_round(1, P).remodule::<Q>();
250 // let r1: Rq<Q, N> = c2rlk1.mul_div_round(1, P).remodule::<Q>();
251
252 use arith::ring_n::naive_mul;
253 let c2rlk0: Vec<i64> = naive_mul(&c2.clone().to_r(), &rlk.0.clone().to_r()); // TODO rm clones
254 let c2rlk1: Vec<i64> = naive_mul(&c2.clone().to_r(), &rlk.1.clone().to_r());
255 let r0: Rq = arith::ring_n::mul_div_round(q, n, c2rlk0, 1, p);
256 let r1: Rq = arith::ring_n::mul_div_round(q, n, c2rlk1, 1, p);
257
258 let res = RLWE(c0 + &r0, c1 + &r1);
259 res
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use anyhow::Result;
266 use rand::distributions::Uniform;
267
268 use super::*;
269
270 #[test]
271 fn test_encrypt_decrypt() -> Result<()> {
272 let params = Params {
273 q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
274 n: 512,
275 t: 32, // plaintext modulus
276 p: 0, // unused in this test
277 };
278
279 let mut rng = rand::thread_rng();
280
281 for _ in 0..100 {
282 let (sk, pk) = BFV::new_key(&mut rng, ¶ms)?;
283
284 let msg_dist = Uniform::new(0_u64, params.t);
285 let m = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
286
287 let c = BFV::encrypt(&mut rng, ¶ms, &pk, &m)?;
288 let m_recovered = BFV::decrypt(¶ms, &sk, &c);
289
290 assert_eq!(m, m_recovered);
291 }
292
293 Ok(())
294 }
295
296 #[test]
297 fn test_addition() -> Result<()> {
298 let params = Params {
299 q: 2u64.pow(16) + 1, // q prime, and 2^q + 1 shape
300 n: 128,
301 t: 32, // plaintext modulus
302 p: 0, // unused in this test
303 };
304
305 let mut rng = rand::thread_rng();
306
307 for _ in 0..100 {
308 let (sk, pk) = BFV::new_key(&mut rng, ¶ms)?;
309
310 let msg_dist = Uniform::new(0_u64, params.t);
311 let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
312 let m2 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
313
314 let c1 = BFV::encrypt(&mut rng, ¶ms, &pk, &m1)?;
315 let c2 = BFV::encrypt(&mut rng, ¶ms, &pk, &m2)?;
316
317 let c3 = c1 + c2;
318
319 let m3_recovered = BFV::decrypt(¶ms, &sk, &c3);
320
321 assert_eq!(m1 + m2, m3_recovered);
322 }
323
324 Ok(())
325 }
326
327 #[test]
328 fn test_constant_add_mul() -> Result<()> {
329 let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
330 let params = Params {
331 q,
332 n: 16,
333 t: 8, // plaintext modulus
334 p: q * q,
335 };
336
337 let mut rng = rand::thread_rng();
338
339 let (sk, pk) = BFV::new_key(&mut rng, ¶ms)?;
340
341 let msg_dist = Uniform::new(0_u64, params.t);
342 let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
343 let m2_const = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
344 let c1 = BFV::encrypt(&mut rng, ¶ms, &pk, &m1)?;
345
346 let c3_add = &c1 + &m2_const;
347
348 let m3_add_recovered = BFV::decrypt(¶ms, &sk, &c3_add);
349 assert_eq!(&m1 + &m2_const, m3_add_recovered);
350
351 // test multiplication of a ciphertext by a constant
352 let rlk = BFV::rlk_key(&mut rng, ¶ms, &sk)?;
353
354 let c3_mul = BFV::mul_const(&rlk, &c1, &m2_const);
355
356 let m3_mul_recovered = BFV::decrypt(¶ms, &sk, &c3_mul);
357 assert_eq!(
358 (m1.to_r() * m2_const.to_r()).to_rq(params.t).coeffs(),
359 m3_mul_recovered.coeffs()
360 );
361
362 Ok(())
363 }
364
365 /*
366 // TMP WIP
367 #[test]
368 #[ignore]
369 fn test_params() -> Result<()> {
370 const Q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
371 const N: usize = 32;
372 const T: u64 = 8; // plaintext modulus
373
374 const P: u64 = Q * Q;
375 const PQ: u64 = P * Q;
376 const DELTA: u64 = Q / T; // floor
377
378 let mut rng = rand::thread_rng();
379
380 let Xi_key = Uniform::new(0_f64, 1_f64);
381 let Xi_err = Normal::new(0_f64, ERR_SIGMA)?;
382
383 let s = Rq::rand_f64(&mut rng, Xi_key)?;
384 let e = Rq::rand_f64(&mut rng, Xi_err)?;
385 let u = Rq::rand_f64(&mut rng, Xi_key)?;
386 let e_0 = Rq::rand_f64(&mut rng, Xi_err)?;
387 let e_1 = Rq::rand_f64(&mut rng, Xi_err)?;
388 let m = Rq::rand_u64(&mut rng, Uniform::new(0_u64, T))?;
389
390 // v_fresh
391 let v: Rq<Q, N> = u * e + e_1 * s + e_0;
392
393 let q: f64 = Q as f64;
394 let t: f64 = T as f64;
395 let n: f64 = N as f64;
396 let delta: f64 = DELTA as f64;
397
398 // r_t(q)/t should be equal to q/t-Δ
399 assert_eq!(
400 // r_t(q)/t, where r_t(q)=q mod t
401 (q % t) / t,
402 // Δt/Q = q - r_t(Q)/Q, so r_t(Q)=q - Δt
403 (q / t) - delta
404 );
405 let rt: f64 = (q % t) / t;
406 dbg!(&rt);
407
408 dbg!(v.infinity_norm());
409 let bound: f64 = (q / (2_f64 * t)) - (rt / 2_f64);
410 dbg!(bound);
411 assert!((v.infinity_norm() as f64) < bound);
412 let max_v_infnorm = bound - 1.0;
413
414 // addition noise
415 let v_add: Rq<Q, N> = v + v + u * rt;
416 let v_add: Rq<Q, N> = v_add + v_add + u * rt;
417 assert!((v_add.infinity_norm() as f64) < bound);
418
419 // multiplication noise
420 let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
421 let c = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m.remodule::<T>())?;
422 let b_key: f64 = 1_f64;
423 // ef: expansion factor
424 let ef: f64 = 2.0 * n.sqrt();
425 let bound: f64 = ((ef * t) / 2.0)
426 * ((2.0 * max_v_infnorm * max_v_infnorm) / q
427 + (4.0 + ef * b_key) * (max_v_infnorm + max_v_infnorm)
428 + rt * (ef * b_key + 5.0))
429 + (1.0 + ef * b_key + ef * ef * b_key * b_key) / 2.0;
430 dbg!(&bound);
431
432 let k: Vec<f64> = (c.0 + c.1 * s - m * delta - v)
433 .coeffs()
434 .iter()
435 .map(|e_i| e_i.0 as f64 / q)
436 .collect();
437 let k = Rq::from_vec_f64(k);
438 let v_tensor_0 = (v * v)
439 .coeffs()
440 .iter()
441 .map(|e_i| (e_i.0 as f64 * t) / q)
442 .collect::<Vec<f64>>();
443 let v_tensor_0 = Rq::from_vec_f64(v_tensor_0);
444 let v_tensor_1 = ((m * v) + (m * v))
445 .coeffs()
446 .iter()
447 .map(|e_i| (e_i.0 as f64 * t * delta) / q)
448 .collect::<Vec<f64>>();
449 let v_tensor_1 = Rq::from_vec_f64(v_tensor_1);
450 let v_tensor_2: Rq<Q, N> = (v * k + v * k) * t;
451 let rm: f64 = (ef * t) / 2.0;
452 let rm: Rq<Q, N> = Rq::from_vec_f64(vec![rm; N]);
453 let v_tensor_3: Rq<Q, N> = (m * k
454 + m * k
455 + rm
456 + Rq::from_vec_f64(
457 ((m * m) * DELTA)
458 .coeffs()
459 .iter()
460 .map(|e_i| e_i.0 as f64 / q)
461 .collect::<Vec<f64>>(),
462 ))
463 * rt;
464 let v_tensor = v_tensor_0 + v_tensor_1 + v_tensor_2 - v_tensor_3;
465
466 let v_r = (1.0 + ef * b_key + ef * ef * b_key * b_key) / 2.0;
467 let v_mult_norm = v_tensor.infinity_norm() as f64 + v_r;
468 dbg!(&v_mult_norm);
469 dbg!(&bound);
470 assert!(v_mult_norm < bound);
471
472 // let m1 = Rq::<T, N>::zero();
473 // let m2 = Rq::<T, N>::zero();
474 // let (_, pk) = BFV::<Q, N, T>::new_key(&mut rng)?;
475 // let c1 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m1)?;
476 // let c2 = BFV::<Q, N, T>::encrypt(&mut rng, &pk, &m2)?;
477 // let (c_a, c_b, c_c) = RLWE::tensor::<PQ, T>(&c1, &c2);
478 // dbg!(&c_a.infinity_norm());
479 // dbg!(&c_b.infinity_norm());
480 // dbg!(&c_c.infinity_norm());
481 // assert!((c_a.infinity_norm() as f64) < bound);
482 // assert!((c_b.infinity_norm() as f64) < bound);
483 // assert!((c_c.infinity_norm() as f64) < bound);
484 // WIP
485
486 Ok(())
487 }
488 */
489
490 #[test]
491 fn test_tensor() -> Result<()> {
492 let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
493 let params = Params {
494 q,
495 n: 16,
496 t: 2, // plaintext modulus
497 p: q * q,
498 };
499 let mut rng = rand::thread_rng();
500
501 let msg_dist = Uniform::new(0_u64, params.t);
502 for _ in 0..1_000 {
503 let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
504 let m2 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
505
506 test_tensor_opt(&mut rng, ¶ms, m1, m2)?;
507 }
508
509 Ok(())
510 }
511 fn test_tensor_opt(mut rng: impl Rng, params: &Params, m1: Rq, m2: Rq) -> Result<()> {
512 let (sk, pk) = BFV::new_key(&mut rng, ¶ms)?;
513
514 let c1 = BFV::encrypt(&mut rng, ¶ms, &pk, &m1)?;
515 let c2 = BFV::encrypt(&mut rng, ¶ms, &pk, &m2)?;
516
517 let (c_a, c_b, c_c) = RLWE::tensor(params.t, &c1, &c2);
518 // let (c_a, c_b, c_c) = RLWE::tensor_new::<PQ, T>(&c1, &c2);
519
520 // decrypt non-relinearized mul result
521 let m3: Rq = c_a + &c_b * &sk.0 + &c_c * &(&sk.0 * &sk.0);
522
523 // let m3: Rq<Q, N> = c_a
524 // + Rq::from_vec_i64(arith::ring_n::naive_mul(&c_b.to_r(), &sk.0.to_r()))
525 // + Rq::from_vec_i64(arith::ring_n::naive_mul(
526 // &c_c.to_r(),
527 // &R::<N>::from_vec(arith::ring_n::naive_mul(&sk.0.to_r(), &sk.0.to_r())),
528 // ));
529 let m3: Rq = m3.mul_div_round(params.t, params.q); // descale
530 let m3 = m3.remodule(params.t);
531
532 let naive = (m1.clone().to_r() * m2.clone().to_r()).to_rq(params.t); // TODO rm clones
533 assert_eq!(
534 m3.coeffs().to_vec(),
535 naive.coeffs().to_vec(),
536 "\n\nfor testing:\nlet m1 = Rq::<T, N>::from_vec_u64(vec!{:?});\nlet m2 = Rq::<T, N>::from_vec_u64(vec!{:?});\n",
537 m1.coeffs(),
538 m2.coeffs()
539 );
540
541 Ok(())
542 }
543
544 #[test]
545 fn test_mul_relin() -> Result<()> {
546 let q: u64 = 2u64.pow(16) + 1; // q prime, and 2^q + 1 shape
547 let params = Params {
548 q,
549 n: 16,
550 t: 2, // plaintext modulus
551 p: q * q,
552 };
553
554 let mut rng = rand::thread_rng();
555 let msg_dist = Uniform::new(0_u64, params.t);
556
557 for _ in 0..1_000 {
558 let m1 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
559 let m2 = Rq::rand_u64(&mut rng, msg_dist, params.t, params.n)?;
560
561 test_mul_relin_opt(&mut rng, ¶ms, m1, m2)?;
562 }
563
564 Ok(())
565 }
566
567 fn test_mul_relin_opt(mut rng: impl Rng, params: &Params, m1: Rq, m2: Rq) -> Result<()> {
568 let (sk, pk) = BFV::new_key(&mut rng, ¶ms)?;
569
570 let rlk = BFV::rlk_key(&mut rng, ¶ms, &sk)?;
571
572 let c1 = BFV::encrypt(&mut rng, ¶ms, &pk, &m1)?;
573 let c2 = BFV::encrypt(&mut rng, ¶ms, &pk, &m2)?;
574
575 let c3 = RLWE::mul(params.t, &rlk, &c1, &c2); // uses relinearize internally
576
577 let m3 = BFV::decrypt(¶ms, &sk, &c3);
578
579 let naive = (m1.clone().to_r() * m2.clone().to_r()).to_rq(params.t); // TODO rm clones
580 assert_eq!(
581 m3.coeffs().to_vec(),
582 naive.coeffs().to_vec(),
583 "\n\nfor testing:\nlet m1 = Rq::<T, N>::from_vec_u64(vec!{:?});\nlet m2 = Rq::<T, N>::from_vec_u64(vec!{:?});\n",
584 m1.coeffs(),
585 m2.coeffs()
586 );
587
588 Ok(())
589 }
590}