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, 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, &params)?;
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, &params, &pk, &m)?;
288            let m_recovered = BFV::decrypt(&params, &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, &params)?;
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, &params, &pk, &m1)?;
315            let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
316
317            let c3 = c1 + c2;
318
319            let m3_recovered = BFV::decrypt(&params, &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, &params)?;
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, &params, &pk, &m1)?;
345
346        let c3_add = &c1 + &m2_const;
347
348        let m3_add_recovered = BFV::decrypt(&params, &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, &params, &sk)?;
353
354        let c3_mul = BFV::mul_const(&rlk, &c1, &m2_const);
355
356        let m3_mul_recovered = BFV::decrypt(&params, &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, &params, 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, &params)?;
513
514        let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
515        let c2 = BFV::encrypt(&mut rng, &params, &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, &params, 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, &params)?;
569
570        let rlk = BFV::rlk_key(&mut rng, &params, &sk)?;
571
572        let c1 = BFV::encrypt(&mut rng, &params, &pk, &m1)?;
573        let c2 = BFV::encrypt(&mut rng, &params, &pk, &m2)?;
574
575        let c3 = RLWE::mul(params.t, &rlk, &c1, &c2); // uses relinearize internally
576
577        let m3 = BFV::decrypt(&params, &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}