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, 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, &params)?;
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, &params, &pk, &m)?;
286            let m_recovered = BFV::decrypt(&params, &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, &params)?;
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, &params, &pk, &m1)?;
313            let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
314
315            let c3 = c1 + c2;
316
317            let m3_recovered = BFV::decrypt(&params, &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, &params)?;
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, &params, &pk, &m1)?;
343
344        let c3_add = &c1 + &m2_const;
345
346        let m3_add_recovered = BFV::decrypt(&params, &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, &params, &sk)?;
351
352        let c3_mul = BFV::mul_const(&rlk, &c1, &m2_const);
353
354        let m3_mul_recovered = BFV::decrypt(&params, &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, &params, 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, &params)?;
511
512        let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
513        let c2 = BFV::encrypt(&mut rng, &params, &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, &params, 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, &params)?;
567
568        let rlk = BFV::rlk_key(&mut rng, &params, &sk)?;
569
570        let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
571        let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
572
573        let c3 = RLWE::mul(params.t, &rlk, &c1, &c2); // uses relinearize internally
574
575        let m3 = BFV::decrypt(&params, &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}