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