arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
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, &param.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), &param.ring)?;
136        let e = Rq::rand_f64(&mut rng, Xi_err, &param.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, &param.ring)?;
153        // let u = Rq::rand_u64(&mut rng, Xi_key)?;
154        let e_1 = Rq::rand_f64(&mut rng, Xi_err, &param.ring)?;
155        let e_2 = Rq::rand_f64(&mut rng, Xi_err, &param.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(&param, c2rlk0);
246        let r1 = Rq::from_vec_f64(&param, 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, &param)?;
296
297            let msg_dist = Uniform::new(0_u64, param.t);
298            let m = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
299
300            let c = BFV::encrypt(&mut rng, &param, &pk, &m)?;
301            let m_recovered = BFV::decrypt(&param, &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, &param)?;
324
325            let msg_dist = Uniform::new(0_u64, param.t);
326            let m1 = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
327            let m2 = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
328
329            let c1 = BFV::encrypt(&mut rng, &param, &pk, &m1)?;
330            let c2 = BFV::encrypt(&mut rng, &param, &pk, &m2)?;
331
332            let c3 = c1 + c2;
333
334            let m3_recovered = BFV::decrypt(&param, &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, &param)?;
354
355        let msg_dist = Uniform::new(0_u64, param.t);
356        let m1 = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
357        let m2_const = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
358        let c1 = BFV::encrypt(&mut rng, &param, &pk, &m1)?;
359
360        let c3_add = &c1 + &m2_const;
361
362        let m3_add_recovered = BFV::decrypt(&param, &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, &param, &sk)?;
367
368        let c3_mul = BFV::mul_const(&rlk, &c1, &m2_const);
369
370        let m3_mul_recovered = BFV::decrypt(&param, &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, &param.pt())?;
517            let m2 = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
518
519            test_tensor_opt(&mut rng, &param, 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, &param)?;
526
527        let c1 = BFV::encrypt(&mut rng, &param, &pk, &m1)?;
528        let c2 = BFV::encrypt(&mut rng, &param, &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, &param.pt())?;
571            let m2 = Rq::rand_u64(&mut rng, msg_dist, &param.pt())?;
572
573            test_mul_relin_opt(&mut rng, &param, 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, &param)?;
581
582        let rlk = BFV::rlk_key(&mut rng, &param, &sk)?;
583
584        let c1 = BFV::encrypt(&mut rng, &param, &pk, &m1)?;
585        let c2 = BFV::encrypt(&mut rng, &param, &pk, &m2)?;
586
587        let c3 = RLWE::mul(param.t, &rlk, &c1, &c2); // uses relinearize internally
588
589        let m3 = BFV::decrypt(&param, &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}