1#![allow(non_snake_case)]
2#![allow(non_upper_case_globals)]
3
4use std::alloc::Allocator;
5use std::alloc::Global;
6use std::marker::PhantomData;
7use std::sync::Arc;
8use std::ops::Range;
9use std::fmt::Display;
10
11use feanor_math::algorithms::eea::signed_lcm;
12use feanor_math::algorithms::int_factor::is_prime_power;
13use feanor_math::primitive_int::StaticRingBase;
14use feanor_math::ring::*;
15use feanor_math::rings::finite::FiniteRing;
16use feanor_math::rings::zn::*;
17use feanor_math::rings::zn::zn_64::*;
18use feanor_math::primitive_int::StaticRing;
19use feanor_math::integer::*;
20use feanor_math::homomorphism::Homomorphism;
21use feanor_math::rings::extension::FreeAlgebraStore;
22use feanor_math::seq::*;
23use feanor_math::ordered::OrderedRingStore;
24use feanor_math::rings::finite::FiniteRingStore;
25use tracing::instrument;
26
27use crate::ciphertext_ring::perform_rns_op;
28use crate::ciphertext_ring::perform_rns_op_to_plaintext_ring;
29use crate::ciphertext_ring::BGFVCiphertextRing;
30use crate::circuit::evaluator::DefaultCircuitEvaluator;
31use crate::circuit::Coefficient;
32use crate::circuit::PlaintextCircuit;
33use crate::cyclotomic::*;
34use crate::gadget_product::GadgetProductLhsOperand;
35use crate::gadget_product::GadgetProductRhsOperand;
36use crate::ntt::{HERingNegacyclicNTT, HERingConvolution};
37use crate::ciphertext_ring::double_rns_managed::*;
38use crate::number_ring::hypercube::isomorphism::*;
39use crate::number_ring::hypercube::structure::HypercubeStructure;
40use crate::number_ring::{largest_prime_leq_congruent_to_one, sample_primes, extend_sampled_primes, HECyclotomicNumberRing, HENumberRing};
41use crate::number_ring::quotient::*;
42use crate::number_ring::pow2_cyclotomic::*;
43use crate::number_ring::odd_cyclotomic::*;
44use crate::ciphertext_ring::single_rns_ring::SingleRNSRingBase;
45use crate::rnsconv::bfv_rescale::{AlmostExactRescaling, AlmostExactRescalingConvert};
46use crate::rnsconv::shared_lift::AlmostExactSharedBaseConversion;
47use crate::DefaultCiphertextAllocator;
48use crate::*;
49
50use rand::{Rng, CryptoRng};
51use rand_distr::StandardNormal;
52
53pub mod bootstrap;
57
58pub type NumberRing<Params: BFVCiphertextParams> = <Params::CiphertextRing as BGFVCiphertextRing>::NumberRing;
59pub type PlaintextRing<Params: BFVCiphertextParams> = NumberRingQuotient<NumberRing<Params>, Zn, Global>;
60pub type SecretKey<Params: BFVCiphertextParams> = El<CiphertextRing<Params>>;
61pub type KeySwitchKey<'a, Params: BFVCiphertextParams> = (GadgetProductOperand<'a, Params>, GadgetProductOperand<'a, Params>);
62pub type RelinKey<'a, Params: BFVCiphertextParams> = KeySwitchKey<'a, Params>;
63pub type CiphertextRing<Params: BFVCiphertextParams> = RingValue<Params::CiphertextRing>;
64pub type Ciphertext<Params: BFVCiphertextParams> = (El<CiphertextRing<Params>>, El<CiphertextRing<Params>>);
65pub type GadgetProductOperand<'a, Params: BFVCiphertextParams> = GadgetProductRhsOperand<Params::CiphertextRing>;
66
67const ZZbig: BigIntRing = BigIntRing::RING;
68const ZZ: StaticRing<i64> = StaticRing::<i64>::RING;
69
70pub trait BFVCiphertextParams {
110
111 type CiphertextRing: BGFVCiphertextRing + CyclotomicRing + FiniteRing;
115
116 fn ciphertext_modulus_bits(&self) -> Range<usize>;
120
121 fn number_ring(&self) -> NumberRing<Self>;
126
127 fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>);
132
133 #[instrument(skip_all)]
137 fn create_plaintext_ring(&self, modulus: i64) -> PlaintextRing<Self> {
138 NumberRingQuotientBase::new(self.number_ring(), Zn::new(modulus as u64))
139 }
140
141 #[instrument(skip_all)]
154 fn gen_sk<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, hwt: Option<usize>) -> SecretKey<Self> {
155 assert!(hwt.is_none() || hwt.unwrap() * 3 <= C.rank() * 2, "it does not make sense to take more than 2/3 of secret key entries in {{-1, 1}}");
156 if let Some(hwt) = hwt {
157 let mut result_data = (0..C.rank()).map(|_| 0).collect::<Vec<_>>();
158 for _ in 0..hwt {
159 let mut i = rng.next_u32() as usize % C.rank();
160 while result_data[i] != 0 {
161 i = rng.next_u32() as usize % C.rank();
162 }
163 result_data[i] = (rng.next_u32() % 2) as i32 * 2 - 1;
164 }
165 let result = C.from_canonical_basis(result_data.into_iter().map(|c| C.base_ring().int_hom().map(c)));
166 return result;
167 } else {
168 let result = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.next_u32() % 3) as i32 - 1)));
169 return result;
170 }
171 }
172
173 #[instrument(skip_all)]
177 fn enc_sym_zero<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
178 let a = C.random_element(|| rng.next_u64());
179 let mut b = C.negate(C.mul_ref(&a, &sk));
180 let e = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.sample::<f64, _>(StandardNormal) * 3.2).round() as i32)));
181 C.add_assign(&mut b, e);
182 return (b, a);
183 }
184
185 #[instrument(skip_all)]
192 fn transparent_zero(C: &CiphertextRing<Self>) -> Ciphertext<Self> {
193 (C.zero(), C.zero())
194 }
195
196 #[instrument(skip_all)]
200 fn enc_sym<R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, rng: R, m: &El<PlaintextRing<Self>>, sk: &SecretKey<Self>) -> Ciphertext<Self> {
201 Self::hom_add_plain(P, C, m, Self::enc_sym_zero(C, rng, sk))
202 }
203
204 #[instrument(skip_all)]
208 fn enc_sk(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>) -> Ciphertext<Self> {
209 let Delta = ZZbig.rounded_div(
210 ZZbig.clone_el(C.base_ring().modulus()),
211 &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
212 );
213 (C.zero(), C.inclusion().map(C.base_ring().coerce(&ZZbig, Delta)))
214 }
215
216 #[instrument(skip_all)]
221 fn remove_noise(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, c: &El<CiphertextRing<Self>>) -> El<PlaintextRing<Self>> {
222 let coefficients = C.wrt_canonical_basis(c);
223 let Delta = ZZbig.rounded_div(
224 ZZbig.clone_el(C.base_ring().modulus()),
225 &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
226 );
227 let modulo = P.base_ring().can_hom(&ZZbig).unwrap();
228 return P.from_canonical_basis((0..coefficients.len()).map(|i| modulo.map(ZZbig.rounded_div(C.base_ring().smallest_lift(coefficients.at(i)), &Delta))));
229 }
230
231 #[instrument(skip_all)]
239 fn dec(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>, sk: &SecretKey<Self>) -> El<PlaintextRing<Self>> {
240 let (c0, c1) = ct;
241 let noisy_m = C.add(c0, C.mul_ref_snd(c1, sk));
242 return Self::remove_noise(P, C, &noisy_m);
243 }
244
245 #[instrument(skip_all)]
250 fn dec_println(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) {
251 let m = Self::dec(P, C, Self::clone_ct(C, ct), sk);
252 println!("ciphertext (noise budget: {}):", Self::noise_budget(P, C, ct, sk));
253 P.println(&m);
254 println!();
255 }
256
257 #[instrument(skip_all)]
262 fn dec_println_slots(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) {
263 let (p, _e) = is_prime_power(ZZ, P.base_ring().modulus()).unwrap();
264 let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(P.n() as u64), p);
265 let H = HypercubeIsomorphism::new::<false>(P, hypercube);
266 let m = Self::dec(P, C, Self::clone_ct(C, ct), sk);
267 println!("ciphertext (noise budget: {}):", Self::noise_budget(P, C, ct, sk));
268 for a in H.get_slot_values(&m) {
269 H.slot_ring().println(&a);
270 }
271 println!();
272 }
273
274 #[instrument(skip_all)]
282 fn hom_add(C: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: &Ciphertext<Self>) -> Ciphertext<Self> {
283 let (lhs0, lhs1) = lhs;
284 let (rhs0, rhs1) = rhs;
285 return (C.add_ref(&lhs0, &rhs0), C.add_ref(&lhs1, &rhs1));
286 }
287
288 #[instrument(skip_all)]
296 fn hom_sub(C: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: &Ciphertext<Self>) -> Ciphertext<Self> {
297 let (lhs0, lhs1) = lhs;
298 let (rhs0, rhs1) = rhs;
299 return (C.sub_ref(&lhs0, rhs0), C.sub_ref(&lhs1, rhs1));
300 }
301
302 #[instrument(skip_all)]
306 fn clone_ct(C: &CiphertextRing<Self>, ct: &Ciphertext<Self>) -> Ciphertext<Self> {
307 (C.clone_el(&ct.0), C.clone_el(&ct.1))
308 }
309
310 #[instrument(skip_all)]
318 fn hom_add_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
319 let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
320 let mut m = C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
321 let Delta = C.base_ring().coerce(&ZZbig, ZZbig.rounded_div(
322 ZZbig.clone_el(C.base_ring().modulus()),
323 &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
324 ));
325 C.inclusion().mul_assign_ref_map(&mut m, &Delta);
326 return (C.add(ct.0, m), ct.1);
327 }
328
329 #[instrument(skip_all)]
337 fn hom_mul_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
338 let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
339 let m = C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
340 let (c0, c1) = ct;
341 return (C.mul_ref_snd(c0, &m), C.mul(c1, m));
342 }
343
344 #[instrument(skip_all)]
352 fn hom_mul_plain_i64(_P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: i64, ct: Ciphertext<Self>) -> Ciphertext<Self> {
353 (C.int_hom().mul_map(ct.0, m as i32), C.int_hom().mul_map(ct.1, m as i32))
354 }
355
356 #[instrument(skip_all)]
364 fn noise_budget(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) -> usize {
365 let (c0, c1) = Self::clone_ct(C, ct);
366 let noisy_m = C.add(c0, C.mul_ref_snd(c1, sk));
367 let coefficients = C.wrt_canonical_basis(&noisy_m);
368 let Delta = ZZbig.rounded_div(
369 ZZbig.clone_el(C.base_ring().modulus()),
370 &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
371 );
372 let log2_size_of_noise = <_ as Iterator>::max((0..coefficients.len()).map(|i| {
373 let c = C.base_ring().smallest_lift(coefficients.at(i));
374 let size = ZZbig.abs_log2_ceil(&ZZbig.sub_ref_fst(&c, ZZbig.mul_ref_snd(ZZbig.rounded_div(ZZbig.clone_el(&c), &Delta), &Delta)));
375 return size.unwrap_or(0);
376 })).unwrap();
377 return ZZbig.abs_log2_ceil(C.base_ring().modulus()).unwrap().saturating_sub(log2_size_of_noise + P.base_ring().integer_ring().abs_log2_ceil(P.base_ring().modulus()).unwrap() + 1);
378 }
379
380 #[instrument(skip_all)]
390 fn gen_rk<'a, R: Rng + CryptoRng>(C: &'a CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>, digits: usize) -> RelinKey<'a, Self>
391 where Self: 'a
392 {
393 Self::gen_switch_key(C, rng, &C.pow(C.clone_el(sk), 2), sk, digits)
394 }
395
396 #[instrument(skip_all)]
404 fn hom_mul<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: Ciphertext<Self>, rk: &RelinKey<'a, Self>) -> Ciphertext<Self>
405 where Self: 'a
406 {
407 let (c00, c01) = lhs;
408 let (c10, c11) = rhs;
409
410 let lift_to_Cmul = Self::create_lift_to_Cmul(C, Cmul);
411 let lift = |c| perform_rns_op(Cmul.get_ring(), C.get_ring(), &c, &lift_to_Cmul);
412 let c00_lifted = lift(c00);
413 let c01_lifted = lift(c01);
414 let c10_lifted = lift(c10);
415 let c11_lifted = lift(c11);
416
417 let [lifted0, lifted1, lifted2] = Cmul.get_ring().two_by_two_convolution([&c00_lifted, &c01_lifted], [&c10_lifted, &c11_lifted]);
418
419 let scale_down_to_C = Self::create_scale_down_to_C(P, C, Cmul);
420 let scale_down = |c: El<CiphertextRing<Self>>| perform_rns_op(C.get_ring(), Cmul.get_ring(), &c, &scale_down_to_C);
421 let res0 = scale_down(lifted0);
422 let res1 = scale_down(lifted1);
423 let res2 = scale_down(lifted2);
424
425 let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &res2, rk.0.gadget_vector_digits());
426 let (s0, s1) = rk;
427 return (C.add_ref(&res0, &op.gadget_product(s0, C.get_ring())), C.add_ref(&res1, &op.gadget_product(s1, C.get_ring())));
428
429 }
430
431 #[instrument(skip_all)]
439 fn hom_square<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>, val: Ciphertext<Self>, rk: &RelinKey<'a, Self>) -> Ciphertext<Self>
440 where Self: 'a
441 {
442 let (c0, c1) = val;
443
444 let lift_to_Cmul = Self::create_lift_to_Cmul(C, Cmul);
445 let lift = |c| perform_rns_op(Cmul.get_ring(), C.get_ring(), &c, &lift_to_Cmul);
446 let c0_lifted = lift(c0);
447 let c1_lifted = lift(c1);
448
449 let [lifted0, lifted1, lifted2] = Cmul.get_ring().two_by_two_convolution([&c0_lifted, &c1_lifted], [&c0_lifted, &c1_lifted]);
450
451 let scale_down_to_C = Self::create_scale_down_to_C(P, C, Cmul);
452 let scale_down = |c: El<CiphertextRing<Self>>| perform_rns_op(C.get_ring(), Cmul.get_ring(), &c, &scale_down_to_C);
453 let res0 = scale_down(lifted0);
454 let res1 = scale_down(lifted1);
455 let res2 = scale_down(lifted2);
456
457 let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &res2, rk.0.gadget_vector_digits());
458 let (s0, s1) = rk;
459 return (C.add_ref(&res0, &op.gadget_product(s0, C.get_ring())), C.add_ref(&res1, &op.gadget_product(s1, C.get_ring())));
460
461 }
462
463 #[instrument(skip_all)]
476 fn gen_switch_key<'a, R: Rng + CryptoRng>(C: &'a CiphertextRing<Self>, mut rng: R, old_sk: &SecretKey<Self>, new_sk: &SecretKey<Self>, digits: usize) -> KeySwitchKey<'a, Self>
477 where Self: 'a
478 {
479 let mut res0 = GadgetProductRhsOperand::new(C.get_ring(), digits);
480 let mut res1 = GadgetProductRhsOperand::new(C.get_ring(), digits);
481 for digit_i in 0..digits {
482 let (c0, c1) = Self::enc_sym_zero(C, &mut rng, new_sk);
483 let digit_range = res0.gadget_vector_digits().at(digit_i).clone();
484 let factor = C.base_ring().get_ring().from_congruence((0..C.base_ring().len()).map(|i2| {
485 let Fp = C.base_ring().at(i2);
486 if digit_range.contains(&i2) { Fp.one() } else { Fp.zero() }
487 }));
488 let mut payload = C.clone_el(&old_sk);
489 C.inclusion().mul_assign_ref_map(&mut payload, &factor);
490 C.add_assign_ref(&mut payload, &c0);
491 res0.set_rns_factor(C.get_ring(), digit_i, payload);
492 res1.set_rns_factor(C.get_ring(), digit_i, c1);
493 }
494 return (res0, res1);
495 }
496
497 #[instrument(skip_all)]
502 fn key_switch<'a>(C: &CiphertextRing<Self>, ct: Ciphertext<Self>, switch_key: &KeySwitchKey<'a, Self>) -> Ciphertext<Self>
503 where Self: 'a
504 {
505 let (c0, c1) = ct;
506 let (s0, s1) = switch_key;
507 let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &c1, switch_key.0.gadget_vector_digits());
508 return (
509 C.add_ref_snd(c0, &op.gadget_product(s0, C.get_ring())),
510 op.gadget_product(s1, C.get_ring())
511 );
512 }
513
514 #[instrument(skip_all)]
519 fn mod_switch_to_plaintext(target: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>) -> (El<PlaintextRing<Self>>, El<PlaintextRing<Self>>) {
520 let mod_switch = AlmostExactRescaling::new_with(
521 C.base_ring().as_iter().map(|Zp| *Zp).collect(),
522 vec![*target.base_ring()],
523 (0..C.base_ring().len()).collect(),
524 Global
525 );
526 let (c0, c1) = ct;
527 return (
528 perform_rns_op_to_plaintext_ring(target, C.get_ring(), &c0, &mod_switch),
529 perform_rns_op_to_plaintext_ring(target, C.get_ring(), &c1, &mod_switch)
530 );
531 }
532
533 #[instrument(skip_all)]
543 fn gen_gk<'a, R: Rng + CryptoRng>(C: &'a CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>, g: CyclotomicGaloisGroupEl, digits: usize) -> KeySwitchKey<'a, Self>
544 where Self: 'a
545 {
546 Self::gen_switch_key(C, rng, &C.get_ring().apply_galois_action(sk, g), sk, digits)
547 }
548
549 #[instrument(skip_all)]
554 fn hom_galois<'a>(C: &CiphertextRing<Self>, ct: Ciphertext<Self>, g: CyclotomicGaloisGroupEl, gk: &KeySwitchKey<'a, Self>) -> Ciphertext<Self>
555 where Self: 'a
556 {
557 Self::key_switch(C, (
558 C.get_ring().apply_galois_action(&ct.0, g),
559 C.get_ring().apply_galois_action(&ct.1, g)
560 ), gk)
561 }
562
563 #[instrument(skip_all)]
569 fn hom_galois_many<'a, 'b, V>(C: &CiphertextRing<Self>, ct: Ciphertext<Self>, gs: &[CyclotomicGaloisGroupEl], gks: V) -> Vec<Ciphertext<Self>>
570 where V: VectorFn<&'b KeySwitchKey<'a, Self>>,
571 KeySwitchKey<'a, Self>: 'b,
572 'a: 'b,
573 Self: 'a
574 {
575 let digits = gks.at(0).0.gadget_vector_digits();
576 let has_same_digits = |gk: &GadgetProductRhsOperand<_>| gk.gadget_vector_digits().len() == digits.len() && gk.gadget_vector_digits().iter().zip(digits.iter()).all(|(l, r)| l == r);
577 assert!(gks.iter().all(|gk| has_same_digits(&gk.0) && has_same_digits(&gk.1)));
578 let (c0, c1) = ct;
579 let c1_op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &c1, digits);
580 let c1_op_gs = c1_op.apply_galois_action_many(C.get_ring(), gs);
581 let c0_gs = C.get_ring().apply_galois_action_many(&c0, gs).into_iter();
582 assert_eq!(gks.len(), c1_op_gs.len());
583 assert_eq!(gks.len(), c0_gs.len());
584 return c0_gs.zip(c1_op_gs.iter()).enumerate().map(|(i, (c0_g, c1_g))| {
585 let (s0, s1) = gks.at(i);
586 let r0 = c1_g.gadget_product(s0, C.get_ring());
587 let r1 = c1_g.gadget_product(s1, C.get_ring());
588 return (C.add_ref(&r0, &c0_g), r1);
589 }).collect();
590 }
591
592 fn create_lift_to_Cmul(C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>) -> AlmostExactSharedBaseConversion {
593 AlmostExactSharedBaseConversion::new_with(
594 C.base_ring().as_iter().map(|R| Zn::new(*R.modulus() as u64)).collect::<Vec<_>>(),
595 Vec::new(),
596 Cmul.base_ring().as_iter().skip(C.base_ring().len()).map(|R| Zn::new(*R.modulus() as u64)).collect::<Vec<_>>(),
597 Global
598 )
599 }
600
601 fn create_scale_down_to_C(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>) -> AlmostExactRescalingConvert {
602 AlmostExactRescalingConvert::new_with(
603 Cmul.base_ring().as_iter().map(|R| Zn::new(*R.modulus() as u64)).collect::<Vec<_>>(),
604 vec![ Zn::new(*P.base_ring().modulus() as u64) ],
605 (0..C.base_ring().len()).collect(),
606 Global
607 )
608 }
609}
610
611impl<NumberRing> PlaintextCircuit<NumberRingQuotientBase<NumberRing, Zn>>
612 where NumberRing: HENumberRing
613{
614 #[instrument(skip_all)]
615 pub fn evaluate_bfv<Params>(&self,
616 P: &PlaintextRing<Params>,
617 C: &CiphertextRing<Params>,
618 Cmul: Option<&CiphertextRing<Params>>,
619 inputs: &[Ciphertext<Params>],
620 rk: Option<&RelinKey<Params>>,
621 gks: &[(CyclotomicGaloisGroupEl, KeySwitchKey<Params>)],
622 _key_switches: &mut usize
623 ) -> Vec<Ciphertext<Params>>
624 where Params: BFVCiphertextParams,
625 Params::CiphertextRing: BGFVCiphertextRing<NumberRing = NumberRing>
626 {
627 assert!(!self.has_multiplication_gates() || Cmul.is_some());
628 assert_eq!(Cmul.is_some(), rk.is_some());
629 let galois_group = C.galois_group();
630 return self.evaluate_generic(
631 inputs,
632 DefaultCircuitEvaluator::new(
633 |lhs, rhs| Params::hom_mul(P, C, Cmul.unwrap(), lhs, rhs, rk.unwrap()),
634 |x| match x {
635 Coefficient::Zero => Params::transparent_zero(C),
636 x => Params::hom_add_plain(P, C, &x.clone(P).to_ring_el(P), Params::transparent_zero(C))
637 },
638 |dst, x, ct| Params::hom_add(C, dst, &Params::hom_mul_plain(P, C, &x.clone(P).to_ring_el(P), Params::clone_ct(C, ct))),
639 ).with_square(
640 |x| Params::hom_square(P, C, Cmul.unwrap(), x, rk.unwrap()),
641 ).with_gal(
642 |x, gs| if gs.len() == 1 {
643 vec![Params::hom_galois(C, x, gs[0], &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, gs[0])).next().unwrap().1)]
644 } else {
645 Params::hom_galois_many(C, x, gs, gs.as_fn().map_fn(|expected_g| &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, *expected_g)).next().unwrap().1))
646 }
647 )
648 );
649 }
650}
651
652impl PlaintextCircuit<StaticRingBase<i64>> {
653
654 #[instrument(skip_all)]
655 pub fn evaluate_bfv<Params>(&self,
656 P: &PlaintextRing<Params>,
657 C: &CiphertextRing<Params>,
658 Cmul: Option<&CiphertextRing<Params>>,
659 inputs: &[Ciphertext<Params>],
660 rk: Option<&RelinKey<Params>>,
661 gks: &[(CyclotomicGaloisGroupEl, KeySwitchKey<Params>)],
662 _key_switches: &mut usize
663 ) -> Vec<Ciphertext<Params>>
664 where Params: BFVCiphertextParams
665 {
666 assert!(!self.has_multiplication_gates() || Cmul.is_some());
667 assert_eq!(Cmul.is_some(), rk.is_some());
668 const ZZ: StaticRing<i64> = StaticRing::<i64>::RING;
669 let galois_group = C.galois_group();
670 return self.evaluate_generic(
671 inputs,
672 DefaultCircuitEvaluator::new(
673 |lhs, rhs| Params::hom_mul(P, C, Cmul.unwrap(), lhs, rhs, rk.unwrap()),
674 |x| match x {
675 Coefficient::Zero => Params::transparent_zero(C),
676 x => Params::hom_add_plain(P, C, &P.int_hom().map(x.clone(ZZ).to_ring_el(ZZ) as i32), Params::transparent_zero(C))
677 },
678 |dst, x, ct| Params::hom_add(C, dst, &Params::hom_mul_plain_i64(P, C, x.to_ring_el(ZZ), Params::clone_ct(C, ct))),
679 ).with_square(
680 |x| Params::hom_square(P, C, Cmul.unwrap(), x, rk.unwrap()),
681 ).with_gal(
682 |x, gs| if gs.len() == 1 {
683 vec![Params::hom_galois(C, x, gs[0], &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, gs[0])).next().unwrap().1)]
684 } else {
685 Params::hom_galois_many(C, x, gs, gs.as_fn().map_fn(|expected_g| &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, *expected_g)).next().unwrap().1))
686 }
687 )
688 );
689 }
690}
691
692#[derive(Debug)]
700pub struct Pow2BFV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator, C: Send + Sync + HERingNegacyclicNTT<Zn> = DefaultNegacyclicNTT> {
701 pub log2_q_min: usize,
702 pub log2_q_max: usize,
703 pub log2_N: usize,
704 pub ciphertext_allocator: A,
705 pub negacyclic_ntt: PhantomData<C>
706}
707
708impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> Display for Pow2BFV<A, C> {
709
710 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
711 write!(f, "BFV(n = 2^{}, log2(q) in {}..{})", self.log2_N + 1, self.log2_q_min, self.log2_q_max)
712 }
713}
714
715impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> Clone for Pow2BFV<A, C> {
716
717 fn clone(&self) -> Self {
718 Self {
719 log2_q_min: self.log2_q_min,
720 log2_q_max: self.log2_q_max,
721 log2_N: self.log2_N,
722 ciphertext_allocator: self.ciphertext_allocator.clone(),
723 negacyclic_ntt: PhantomData
724 }
725 }
726}
727
728impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> BFVCiphertextParams for Pow2BFV<A, C> {
729
730 type CiphertextRing = ManagedDoubleRNSRingBase<Pow2CyclotomicNumberRing<C>, A>;
731
732 fn number_ring(&self) -> Pow2CyclotomicNumberRing<C> {
733 Pow2CyclotomicNumberRing::new_with(2 << self.log2_N)
734 }
735
736 fn ciphertext_modulus_bits(&self) -> Range<usize> {
737 assert!(self.log2_q_min < self.log2_q_max);
738 self.log2_q_min..self.log2_q_max
739 }
740
741 #[instrument(skip_all)]
742 fn enc_sym_zero<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
743 let a = C.random_element(|| rng.next_u64());
744 let mut b = C.negate(C.mul_ref(&a, &sk));
745 let e = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.sample::<f64, _>(StandardNormal) * 3.2).round() as i32)));
746 C.add_assign(&mut b, e);
747 return small_basis_repr::<Self, _, _>(C, (b, a));
748 }
749
750 #[instrument(skip_all)]
751 fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>) {
752 let log2_q = self.ciphertext_modulus_bits();
753 let number_ring = self.number_ring();
754 let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
755
756 let mut C_rns_base = sample_primes(log2_q.start, log2_q.end, 56, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
757 C_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
758
759 let mut Cmul_rns_base = extend_sampled_primes(&C_rns_base, log2_q.end * 2 + 10, log2_q.end * 2 + 67, 57, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
760 assert!(ZZbig.is_gt(&Cmul_rns_base[Cmul_rns_base.len() - 1], &C_rns_base[C_rns_base.len() - 1]));
761 Cmul_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
762
763 let C_rns_base = zn_rns::Zn::new(C_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>(), ZZbig);
764 let Cmul_rns_base = zn_rns::Zn::new(Cmul_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect(), ZZbig);
765
766 let Cmul = ManagedDoubleRNSRingBase::new_with(
767 number_ring,
768 Cmul_rns_base,
769 self.ciphertext_allocator.clone()
770 );
771
772 let dropped_indices = (0..Cmul.base_ring().len()).filter(|i| C_rns_base.as_iter().all(|Zp| Zp.get_ring() != Cmul.base_ring().at(*i).get_ring())).collect::<Vec<_>>();
773 let C = RingValue::from(Cmul.get_ring().drop_rns_factor(&dropped_indices));
774 debug_assert!(C.base_ring().get_ring() == C_rns_base.get_ring());
775 return (C, Cmul);
776 }
777}
778
779#[derive(Clone, Debug)]
785pub struct CompositeBFV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator> {
786 pub log2_q_min: usize,
787 pub log2_q_max: usize,
788 pub n1: usize,
789 pub n2: usize,
790 pub ciphertext_allocator: A
791}
792
793impl<A: Allocator + Clone + Send + Sync> BFVCiphertextParams for CompositeBFV<A> {
794
795 type CiphertextRing = ManagedDoubleRNSRingBase<CompositeCyclotomicNumberRing, A>;
796
797 fn ciphertext_modulus_bits(&self) -> Range<usize> {
798 self.log2_q_min..self.log2_q_max
799 }
800
801 fn number_ring(&self) -> CompositeCyclotomicNumberRing {
802 CompositeCyclotomicNumberRing::new(self.n1, self.n2)
803 }
804
805 #[instrument(skip_all)]
806 fn enc_sym_zero<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
807 let a = C.random_element(|| rng.next_u64());
808 let mut b = C.negate(C.mul_ref(&a, &sk));
809 let e = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.sample::<f64, _>(StandardNormal) * 3.2).round() as i32)));
810 C.add_assign(&mut b, e);
811 return small_basis_repr::<Self, _, _>(C, (b, a));
812 }
813
814 #[instrument(skip_all)]
815 fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>) {
816 let log2_q = self.ciphertext_modulus_bits();
817 let number_ring = self.number_ring();
818 let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
819
820 let mut C_rns_base = sample_primes(log2_q.start, log2_q.end, 56, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
821 C_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
822
823 let mut Cmul_rns_base = extend_sampled_primes(&C_rns_base, log2_q.end * 2, log2_q.end * 2 + 57, 57, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
824 assert!(ZZbig.is_gt(&Cmul_rns_base[Cmul_rns_base.len() - 1], &C_rns_base[C_rns_base.len() - 1]));
825 Cmul_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
826
827 let C_rns_base = zn_rns::Zn::new(C_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>(), ZZbig);
828 let Cmul_rns_base = zn_rns::Zn::new(Cmul_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect(), ZZbig);
829
830 let Cmul = ManagedDoubleRNSRingBase::new_with(
831 number_ring,
832 Cmul_rns_base,
833 self.ciphertext_allocator.clone()
834 );
835
836 let dropped_indices = (0..Cmul.base_ring().len()).filter(|i| C_rns_base.as_iter().all(|Zp| Zp.get_ring() != Cmul.base_ring().at(*i).get_ring())).collect::<Vec<_>>();
837 let C = RingValue::from(Cmul.get_ring().drop_rns_factor(&dropped_indices));
838 debug_assert!(C.base_ring().get_ring() == C_rns_base.get_ring());
839 return (C, Cmul);
840 }
841}
842
843#[derive(Clone, Debug)]
853pub struct CompositeSingleRNSBFV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator, C: Send + Sync + HERingConvolution<Zn> = DefaultConvolution> {
854 pub log2_q_min: usize,
855 pub log2_q_max: usize,
856 pub n1: usize,
857 pub n2: usize,
858 pub ciphertext_allocator: A,
859 pub convolution: PhantomData<C>
860}
861
862impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingConvolution<Zn>> BFVCiphertextParams for CompositeSingleRNSBFV<A, C> {
863
864 type CiphertextRing = SingleRNSRingBase<CompositeCyclotomicNumberRing, A, C>;
865
866 fn ciphertext_modulus_bits(&self) -> Range<usize> {
867 self.log2_q_min..self.log2_q_max
868 }
869
870 fn number_ring(&self) -> CompositeCyclotomicNumberRing {
871 CompositeCyclotomicNumberRing::new(self.n1, self.n2)
872 }
873
874 #[instrument(skip_all)]
875 fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>) {
876 let log2_q = self.ciphertext_modulus_bits();
877 let number_ring = self.number_ring();
878 let required_root_of_unity = signed_lcm(
879 number_ring.mod_p_required_root_of_unity() as i64,
880 1 << ZZ.abs_log2_ceil(&(number_ring.n() as i64 * 2)).unwrap(),
881 ZZ
882 );
883
884 let mut C_rns_base = sample_primes(log2_q.start, log2_q.end, 56, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
885 C_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
886
887 let mut Cmul_rns_base = extend_sampled_primes(&C_rns_base, log2_q.end * 2, log2_q.end * 2 + 57, 57, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
888 assert!(ZZbig.is_gt(&Cmul_rns_base[Cmul_rns_base.len() - 1], &C_rns_base[C_rns_base.len() - 1]));
889 Cmul_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
890
891 let max_log2_n = 1 + ZZ.abs_log2_ceil(&((self.n1 * self.n2) as i64)).unwrap();
892 let C_rns_base = C_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>();
893 let Cmul_rns_base = Cmul_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>();
894
895 let C_convolutions = C_rns_base.iter().map(|Zp| C::new(*Zp, max_log2_n)).map(Arc::new).collect::<Vec<_>>();
896 let Cmul_convolutions = Cmul_rns_base.iter().map(|Zp| match C_rns_base.iter().enumerate().filter(|(_, C_Zp)| C_Zp.get_ring() == Zp.get_ring()).next() {
897 Some((i, _)) => C_convolutions.at(i).clone(),
898 None => Arc::new(C::new(*Zp, max_log2_n))
899 }).collect();
900
901 let C = SingleRNSRingBase::new_with(
902 self.number_ring(),
903 zn_rns::Zn::new(C_rns_base.clone(), ZZbig),
904 self.ciphertext_allocator.clone(),
905 C_convolutions
906 );
907 let Cmul = SingleRNSRingBase::new_with(
908 number_ring,
909 zn_rns::Zn::new(Cmul_rns_base.clone(), ZZbig),
910 self.ciphertext_allocator.clone(),
911 Cmul_convolutions
912 );
913 return (C, Cmul);
914 }
915}
916
917pub fn small_basis_repr<Params, NumberRing, A>(C: &CiphertextRing<Params>, ct: Ciphertext<Params>) -> Ciphertext<Params>
918 where Params: BFVCiphertextParams<CiphertextRing = ManagedDoubleRNSRingBase<NumberRing, A>>,
919 NumberRing: HECyclotomicNumberRing,
920 A: Allocator + Clone
921{
922 return (
923 C.get_ring().from_small_basis_repr(C.get_ring().to_small_basis(&ct.0).map(|x| C.get_ring().unmanaged_ring().get_ring().clone_el_non_fft(x)).unwrap_or_else(|| C.get_ring().unmanaged_ring().get_ring().zero_non_fft())),
924 C.get_ring().from_small_basis_repr(C.get_ring().to_small_basis(&ct.1).map(|x| C.get_ring().unmanaged_ring().get_ring().clone_el_non_fft(x)).unwrap_or_else(|| C.get_ring().unmanaged_ring().get_ring().zero_non_fft())),
925 );
926}
927
928#[cfg(test)]
929use tracing_subscriber::prelude::*;
930#[cfg(test)]
931use feanor_mempool::dynsize::DynLayoutMempool;
932#[cfg(test)]
933use feanor_mempool::AllocArc;
934#[cfg(test)]
935use feanor_math::assert_el_eq;
936#[cfg(test)]
937use std::ptr::Alignment;
938#[cfg(test)]
939use std::fmt::Debug;
940#[cfg(test)]
941use crate::log_time;
942#[cfg(test)]
943use rand::thread_rng;
944
945#[test]
946fn test_pow2_bfv_enc_dec() {
947 let mut rng = thread_rng();
948
949 let params = Pow2BFV {
950 log2_q_min: 500,
951 log2_q_max: 520,
952 log2_N: 7,
953 ciphertext_allocator: DefaultCiphertextAllocator::default(),
954 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
955 };
956 let t = 257;
957
958 let P = params.create_plaintext_ring(t);
959 let (C, _Cmul) = params.create_ciphertext_rings();
960
961 let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
962
963 let input = P.int_hom().map(2);
964 let ctxt = Pow2BFV::enc_sym(&P, &C, &mut rng, &input, &sk);
965 let output = Pow2BFV::dec(&P, &C, Pow2BFV::clone_ct(&C, &ctxt), &sk);
966 assert_el_eq!(&P, input, output);
967}
968
969#[test]
970fn test_pow2_bfv_hom_galois() {
971 let mut rng = thread_rng();
972
973 let params = Pow2BFV {
974 log2_q_min: 500,
975 log2_q_max: 520,
976 log2_N: 7,
977 ciphertext_allocator: DefaultCiphertextAllocator::default(),
978 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
979 };
980 let t = 3;
981 let digits = 3;
982
983 let P = params.create_plaintext_ring(t);
984 let (C, _Cmul) = params.create_ciphertext_rings();
985 let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
986 let gk = Pow2BFV::gen_gk(&C, &mut rng, &sk, P.galois_group().from_representative(3), digits);
987
988 let input = P.canonical_gen();
989 let ctxt = Pow2BFV::enc_sym(&P, &C, &mut rng, &input, &sk);
990 let result_ctxt = Pow2BFV::hom_galois(&C, ctxt, P.galois_group().from_representative(3), &gk);
991 let result = Pow2BFV::dec(&P, &C, result_ctxt, &sk);
992
993 assert_el_eq!(&P, &P.pow(P.canonical_gen(), 3), &result);
994}
995
996#[test]
997fn test_pow2_bfv_mul() {
998 let mut rng = thread_rng();
999
1000 let params = Pow2BFV {
1001 log2_q_min: 500,
1002 log2_q_max: 520,
1003 log2_N: 10,
1004 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1005 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1006 };
1007 let t = 257;
1008 let digits = 3;
1009
1010 let P = params.create_plaintext_ring(t);
1011 let (C, Cmul) = params.create_ciphertext_rings();
1012 let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
1013 let rk = Pow2BFV::gen_rk(&C, &mut rng, &sk, digits);
1014
1015 let input = P.int_hom().map(2);
1016 let ctxt = Pow2BFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1017 let result_ctxt = Pow2BFV::hom_mul(&P, &C, &Cmul, Pow2BFV::clone_ct(&C, &ctxt), Pow2BFV::clone_ct(&C, &ctxt), &rk);
1018 let result = Pow2BFV::dec(&P, &C, result_ctxt, &sk);
1019
1020 assert_el_eq!(&P, &P.int_hom().map(4), &result);
1021}
1022
1023#[test]
1024fn test_composite_bfv_mul() {
1025 let mut rng = thread_rng();
1026
1027 let params = CompositeBFV {
1028 log2_q_min: 500,
1029 log2_q_max: 520,
1030 n1: 17,
1031 n2: 97,
1032 ciphertext_allocator: DefaultCiphertextAllocator::default()
1033 };
1034 let t = 8;
1035 let digits = 3;
1036
1037 let P = params.create_plaintext_ring(t);
1038 let (C, Cmul) = params.create_ciphertext_rings();
1039 let sk = CompositeBFV::gen_sk(&C, &mut rng, None);
1040 let rk = CompositeBFV::gen_rk(&C, &mut rng, &sk, digits);
1041
1042 let input = P.int_hom().map(2);
1043 let ctxt = CompositeBFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1044 let result_ctxt = CompositeBFV::hom_mul(&P, &C, &Cmul, CompositeBFV::clone_ct(&C, &ctxt), CompositeBFV::clone_ct(&C, &ctxt), &rk);
1045 let result = CompositeBFV::dec(&P, &C, result_ctxt, &sk);
1046
1047 assert_el_eq!(&P, &P.int_hom().map(4), &result);
1048}
1049
1050#[test]
1051fn test_composite_bfv_hom_galois() {
1052 let mut rng = thread_rng();
1053
1054 let params = CompositeSingleRNSBFV {
1055 log2_q_min: 500,
1056 log2_q_max: 520,
1057 n1: 7,
1058 n2: 11,
1059 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1060 convolution: PhantomData::<DefaultConvolution>
1061 };
1062 let t = 3;
1063 let digits = 3;
1064
1065 let P = params.create_plaintext_ring(t);
1066 let (C, _Cmul) = params.create_ciphertext_rings();
1067 let sk = CompositeSingleRNSBFV::gen_sk(&C, &mut rng, None);
1068 let gk = CompositeSingleRNSBFV::gen_gk(&C, &mut rng, &sk, P.galois_group().from_representative(3), digits);
1069
1070 let input = P.canonical_gen();
1071 let ctxt = CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1072 let result_ctxt = CompositeSingleRNSBFV::hom_galois(&C, ctxt, P.galois_group().from_representative(3), &gk);
1073 let result = CompositeSingleRNSBFV::dec(&P, &C, result_ctxt, &sk);
1074
1075 assert_el_eq!(&P, &P.pow(P.canonical_gen(), 3), &result);
1076}
1077
1078#[test]
1079fn test_single_rns_composite_bfv_mul() {
1080 let mut rng = thread_rng();
1081
1082 let params = CompositeSingleRNSBFV {
1083 log2_q_min: 500,
1084 log2_q_max: 520,
1085 n1: 7,
1086 n2: 11,
1087 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1088 convolution: PhantomData::<DefaultConvolution>
1089 };
1090 let t = 3;
1091 let digits = 3;
1092
1093 let P = params.create_plaintext_ring(t);
1094 let (C, Cmul) = params.create_ciphertext_rings();
1095
1096 let sk = CompositeSingleRNSBFV::gen_sk(&C, &mut rng, None);
1097 let rk = CompositeSingleRNSBFV::gen_rk(&C, &mut rng, &sk, digits);
1098
1099 let input = P.int_hom().map(2);
1100 let ctxt = CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1101 let result_ctxt = CompositeSingleRNSBFV::hom_mul(&P, &C, &Cmul, CompositeSingleRNSBFV::clone_ct(&C, &ctxt), CompositeSingleRNSBFV::clone_ct(&C, &ctxt), &rk);
1102 let result = CompositeSingleRNSBFV::dec(&P, &C, result_ctxt, &sk);
1103
1104 assert_el_eq!(&P, &P.int_hom().map(4), &result);
1105}
1106
1107#[test]
1108#[ignore]
1109fn measure_time_pow2_bfv() {
1110 let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1111 tracing_subscriber::registry().with(chrome_layer).init();
1112
1113 let mut rng = thread_rng();
1114
1115 let params = Pow2BFV {
1116 log2_q_min: 790,
1117 log2_q_max: 800,
1118 log2_N: 15,
1119 ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1120 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1121 };
1122 let t = 257;
1123 let digits = 3;
1124
1125 let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1126 params.create_plaintext_ring(t)
1127 );
1128 let (C, Cmul) = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1129 params.create_ciphertext_rings()
1130 );
1131
1132 let sk = log_time::<_, _, true, _>("GenSK", |[]|
1133 Pow2BFV::gen_sk(&C, &mut rng, None)
1134 );
1135
1136 let m = P.int_hom().map(2);
1137 let ct = log_time::<_, _, true, _>("EncSym", |[]|
1138 Pow2BFV::enc_sym(&P, &C, &mut rng, &m, &sk)
1139 );
1140
1141 let res = log_time::<_, _, true, _>("HomAddPlain", |[]|
1142 Pow2BFV::hom_add_plain(&P, &C, &m, Pow2BFV::clone_ct(&C, &ct))
1143 );
1144 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1145
1146 let res = log_time::<_, _, true, _>("HomAdd", |[]|
1147 Pow2BFV::hom_add(&C, Pow2BFV::clone_ct(&C, &ct), &ct)
1148 );
1149 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1150
1151 let res = log_time::<_, _, true, _>("HomMulPlain", |[]|
1152 Pow2BFV::hom_mul_plain(&P, &C, &m, Pow2BFV::clone_ct(&C, &ct))
1153 );
1154 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1155
1156 let rk = log_time::<_, _, true, _>("GenRK", |[]|
1157 Pow2BFV::gen_rk(&C, &mut rng, &sk, digits)
1158 );
1159 let ct2 = Pow2BFV::enc_sym(&P, &C, &mut rng, &m, &sk);
1160 let res = log_time::<_, _, true, _>("HomMul", |[]|
1161 Pow2BFV::hom_mul(&P, &C, &Cmul, ct, ct2, &rk)
1162 );
1163 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1164}
1165
1166#[test]
1167#[ignore]
1168fn measure_time_double_rns_composite_bfv() {
1169 let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1170 tracing_subscriber::registry().with(chrome_layer).init();
1171
1172 let mut rng = thread_rng();
1173
1174 let params = CompositeBFV {
1175 log2_q_min: 1090,
1176 log2_q_max: 1100,
1177 n1: 127,
1178 n2: 337,
1179 ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1180 };
1181 let t = 4;
1182 let digits = 3;
1183
1184 let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1185 params.create_plaintext_ring(t)
1186 );
1187 let (C, Cmul) = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1188 params.create_ciphertext_rings()
1189 );
1190
1191 let sk = log_time::<_, _, true, _>("GenSK", |[]|
1192 CompositeBFV::gen_sk(&C, &mut rng, None)
1193 );
1194
1195 let m = P.int_hom().map(3);
1196 let ct = log_time::<_, _, true, _>("EncSym", |[]|
1197 CompositeBFV::enc_sym(&P, &C, &mut rng, &m, &sk)
1198 );
1199 assert_el_eq!(&P, &P.int_hom().map(3), &CompositeBFV::dec(&P, &C, CompositeBFV::clone_ct(&C, &ct), &sk));
1200
1201 let res = log_time::<_, _, true, _>("HomAdd", |[]|
1202 CompositeBFV::hom_add(&C, CompositeBFV::clone_ct(&C, &ct), &ct)
1203 );
1204 assert_el_eq!(&P, &P.int_hom().map(2), &CompositeBFV::dec(&P, &C, res, &sk));
1205
1206 let res = log_time::<_, _, true, _>("HomAddPlain", |[]|
1207 CompositeBFV::hom_add_plain(&P, &C, &m, CompositeBFV::clone_ct(&C, &ct))
1208 );
1209 assert_el_eq!(&P, &P.int_hom().map(2), &CompositeBFV::dec(&P, &C, res, &sk));
1210
1211 let res = log_time::<_, _, true, _>("HomMulPlain", |[]|
1212 CompositeBFV::hom_mul_plain(&P, &C, &m, CompositeBFV::clone_ct(&C, &ct))
1213 );
1214 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBFV::dec(&P, &C, res, &sk));
1215
1216 let rk = log_time::<_, _, true, _>("GenRK", |[]|
1217 CompositeBFV::gen_rk(&C, &mut rng, &sk, digits)
1218 );
1219 let ct2 = CompositeBFV::enc_sym(&P, &C, &mut rng, &m, &sk);
1220 let res = log_time::<_, _, true, _>("HomMul", |[]|
1221 CompositeBFV::hom_mul(&P, &C, &Cmul, ct, ct2, &rk)
1222 );
1223 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBFV::dec(&P, &C, res, &sk));
1224}
1225
1226#[test]
1227#[ignore]
1228fn measure_time_single_rns_composite_bfv() {
1229 let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1230 tracing_subscriber::registry().with(chrome_layer).init();
1231
1232 let mut rng = thread_rng();
1233
1234 let params = CompositeSingleRNSBFV {
1235 log2_q_min: 1090,
1236 log2_q_max: 1100,
1237 n1: 127,
1238 n2: 337,
1239 ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1240 convolution: PhantomData::<DefaultConvolution>
1241 };
1242 let t = 4;
1243 let digits = 3;
1244
1245 let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1246 params.create_plaintext_ring(t)
1247 );
1248 let (C, Cmul) = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1249 params.create_ciphertext_rings()
1250 );
1251
1252 let sk = log_time::<_, _, true, _>("GenSK", |[]|
1253 CompositeSingleRNSBFV::gen_sk(&C, &mut rng, None)
1254 );
1255
1256 let m = P.int_hom().map(3);
1257 let ct = log_time::<_, _, true, _>("EncSym", |[]|
1258 CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &m, &sk)
1259 );
1260
1261 let res = log_time::<_, _, true, _>("HomAddPlain", |[]|
1262 CompositeSingleRNSBFV::hom_add_plain(&P, &C, &m, CompositeSingleRNSBFV::clone_ct(&C, &ct))
1263 );
1264 assert_el_eq!(&P, &P.int_hom().map(2), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1265
1266 let res = log_time::<_, _, true, _>("HomAdd", |[]|
1267 CompositeSingleRNSBFV::hom_add(&C, CompositeSingleRNSBFV::clone_ct(&C, &ct), &ct)
1268 );
1269 assert_el_eq!(&P, &P.int_hom().map(2), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1270
1271 let res = log_time::<_, _, true, _>("HomMulPlain", |[]|
1272 CompositeSingleRNSBFV::hom_mul_plain(&P, &C, &m, CompositeSingleRNSBFV::clone_ct(&C, &ct))
1273 );
1274 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1275
1276 let rk = log_time::<_, _, true, _>("GenRK", |[]|
1277 CompositeSingleRNSBFV::gen_rk(&C, &mut rng, &sk, digits)
1278 );
1279 let ct2 = CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &m, &sk);
1280 let res = log_time::<_, _, true, _>("HomMul", |[]|
1281 CompositeSingleRNSBFV::hom_mul(&P, &C, &Cmul, ct, ct2, &rk)
1282 );
1283 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1284}