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