1use std::alloc::{Allocator, Global};
2use std::fmt::Display;
3use std::marker::PhantomData;
4use std::sync::Arc;
5
6use feanor_math::algorithms::eea::signed_gcd;
7use feanor_math::algorithms::int_factor::is_prime_power;
8use feanor_math::algorithms::rational_reconstruction::reduce_2d_modular_relation_basis;
9use feanor_math::homomorphism::Homomorphism;
10use feanor_math::integer::{int_cast, BigIntRing, IntegerRingStore};
11use feanor_math::matrix::OwnedMatrix;
12use feanor_math::ordered::OrderedRingStore;
13use feanor_math::primitive_int::StaticRing;
14use feanor_math::ring::*;
15use feanor_math::rings::extension::*;
16use feanor_math::rings::finite::{FiniteRing, FiniteRingStore};
17use feanor_math::rings::zn::zn_64::Zn;
18use feanor_math::rings::zn::zn_rns;
19use feanor_math::rings::zn::ZnRingStore;
20use feanor_math::divisibility::DivisibilityRingStore;
21use feanor_math::seq::*;
22use tracing::instrument;
23
24use crate::ciphertext_ring::double_rns_managed::ManagedDoubleRNSRingBase;
25use crate::ciphertext_ring::single_rns_ring::*;
26use crate::ciphertext_ring::perform_rns_op_to_plaintext_ring;
27use crate::ciphertext_ring::BGFVCiphertextRing;
28use crate::{cyclotomic::*, ZZi64};
29use crate::gadget_product::digits::{RNSFactorIndexList, RNSGadgetVectorDigitIndices};
30use crate::gadget_product::{GadgetProductLhsOperand, GadgetProductRhsOperand};
31use crate::ntt::{HERingConvolution, HERingNegacyclicNTT};
32use crate::number_ring::hypercube::isomorphism::*;
33use crate::number_ring::hypercube::structure::HypercubeStructure;
34use crate::number_ring::odd_cyclotomic::CompositeCyclotomicNumberRing;
35use crate::number_ring::{sample_primes, largest_prime_leq_congruent_to_one, HECyclotomicNumberRing, HENumberRing};
36use crate::number_ring::pow2_cyclotomic::Pow2CyclotomicNumberRing;
37use crate::number_ring::quotient::{NumberRingQuotient, NumberRingQuotientBase};
38use crate::rnsconv::bgv_rescale::{CongruencePreservingAlmostExactBaseConversion, CongruencePreservingRescaling};
39use crate::rnsconv::RNSOperation;
40use crate::{DefaultCiphertextAllocator, DefaultConvolution, DefaultNegacyclicNTT};
41
42use rand_distr::StandardNormal;
43use rand::*;
44
45pub type NumberRing<Params: BGVCiphertextParams> = <Params::CiphertextRing as BGFVCiphertextRing>::NumberRing;
46pub type CiphertextRing<Params: BGVCiphertextParams> = RingValue<Params::CiphertextRing>;
47pub type PlaintextRing<Params: BGVCiphertextParams> = NumberRingQuotient<NumberRing<Params>, Zn>;
48pub type SecretKey<Params: BGVCiphertextParams> = El<CiphertextRing<Params>>;
49pub type RelinKey<'a, Params: BGVCiphertextParams> = KeySwitchKey<'a, Params>;
50
51pub struct KeySwitchKey<'a, Params: ?Sized + BGVCiphertextParams> {
58 k0: GadgetProductRhsOperand<Params::CiphertextRing>,
59 k1: GadgetProductRhsOperand<Params::CiphertextRing>,
60 special_modulus_factor_count: usize,
61 ring: PhantomData<&'a CiphertextRing<Params>>
62}
63
64impl<'a, Params: ?Sized + BGVCiphertextParams> KeySwitchKey<'a, Params> {
65
66 pub fn params(&self) -> KeySwitchKeyParams {
70 KeySwitchKeyParams {
71 digits_without_special: self.k0.gadget_vector_digits().to_owned(),
72 special_modulus_factor_count: self.special_modulus_factor_count
73 }
74 }
75
76 pub fn k0<'b>(&'b self) -> &'b GadgetProductRhsOperand<Params::CiphertextRing> {
81 &self.k0
82 }
83
84 pub fn k1<'b>(&'b self) -> &'b GadgetProductRhsOperand<Params::CiphertextRing> {
89 &self.k1
90 }
91}
92
93#[derive(Clone)]
116pub struct KeySwitchKeyParams {
117 pub special_modulus_factor_count: usize,
125 pub digits_without_special: Box<RNSGadgetVectorDigitIndices>
133}
134
135impl KeySwitchKeyParams {
136
137 pub fn default(digit_count: usize, special_modulus_factor_count: usize, rns_base_len: usize) -> Self {
142 KeySwitchKeyParams {
143 special_modulus_factor_count: special_modulus_factor_count,
144 digits_without_special: RNSGadgetVectorDigitIndices::select_digits(digit_count, rns_base_len - special_modulus_factor_count)
145 }
146 }
147
148 pub fn expected_rns_base_len(&self) -> usize {
153 self.digits_without_special.rns_base_len() + self.special_modulus_factor_count
154 }
155}
156
157pub mod noise_estimator;
165pub mod modswitch;
170pub mod bootstrap;
174
175const ZZbig: BigIntRing = BigIntRing::RING;
176const ZZ: StaticRing<i64> = StaticRing::<i64>::RING;
177
178pub struct Ciphertext<Params: ?Sized + BGVCiphertextParams> {
184 pub implicit_scale: El<Zn>,
189 pub c0: El<CiphertextRing<Params>>,
190 pub c1: El<CiphertextRing<Params>>
191}
192
193pub fn equalize_implicit_scale(Zt: &Zn, implicit_scale_quotient: El<Zn>) -> (i64, i64) {
197 let (u, v) = reduce_2d_modular_relation_basis(Zt, implicit_scale_quotient);
198 let ZZ_to_Zt = Zt.can_hom(&StaticRing::<i64>::RING).unwrap();
199 if Zt.is_unit(&ZZ_to_Zt.map(u[0])) {
200 return (u[1], u[0]);
201 } else {
202 assert!(Zt.is_unit(&ZZ_to_Zt.map(v[0])), "handling this situation in the case of plaintext moduli with multiple different prime factors is not implemented");
203 return (v[1], v[0]);
204 }
205}
206
207pub trait BGVCiphertextParams {
223
224 type CiphertextRing: BGFVCiphertextRing + CyclotomicRing + FiniteRing;
229
230 fn max_rns_base(&self) -> zn_rns::Zn<Zn, BigIntRing>;
238
239 fn create_ciphertext_ring(&self, rns_base: zn_rns::Zn<Zn, BigIntRing>) -> CiphertextRing<Self>;
247
248 fn create_initial_ciphertext_ring(&self) -> CiphertextRing<Self> {
255 self.create_ciphertext_ring(self.max_rns_base())
256 }
257
258 fn number_ring(&self) -> NumberRing<Self>;
263
264 #[instrument(skip_all)]
268 fn create_plaintext_ring(&self, modulus: i64) -> PlaintextRing<Self> {
269 NumberRingQuotientBase::new(self.number_ring(), Zn::new(modulus as u64))
270 }
271
272 #[instrument(skip_all)]
278 fn gen_sk<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, hwt: Option<usize>) -> SecretKey<Self> {
279 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}}");
280 if let Some(hwt) = hwt {
281 let mut result_data = (0..C.rank()).map(|_| 0).collect::<Vec<_>>();
282 for _ in 0..hwt {
283 let mut i = rng.next_u32() as usize % C.rank();
284 while result_data[i] != 0 {
285 i = rng.next_u32() as usize % C.rank();
286 }
287 result_data[i] = (rng.next_u32() % 2) as i32 * 2 - 1;
288 }
289 let result = C.from_canonical_basis(result_data.into_iter().map(|c| C.base_ring().int_hom().map(c)));
290 return result;
291 } else {
292 let result = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.next_u32() % 3) as i32 - 1)));
293 return result;
294 }
295 }
296
297 #[instrument(skip_all)]
303 fn rlwe_sample<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> (El<CiphertextRing<Self>>, El<CiphertextRing<Self>>) {
304 let a = C.random_element(|| rng.next_u64());
305 let mut b = C.negate(C.mul_ref(&a, &sk));
306 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)));
307 C.add_assign(&mut b, e);
308 return (a, b);
309 }
310
311 #[instrument(skip_all)]
317 fn enc_sym_zero<R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
318 let t = C.base_ring().coerce(&ZZ, *P.base_ring().modulus());
319 let (a, b) = Self::rlwe_sample(C, rng, sk);
320 return Ciphertext {
321 c0: C.inclusion().mul_ref_snd_map(b, &t),
322 c1: C.inclusion().mul_ref_snd_map(a, &t),
323 implicit_scale: P.base_ring().one()
324 };
325 }
326
327 #[instrument(skip_all)]
335 fn transparent_zero(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>) -> Ciphertext<Self> {
336 return Ciphertext {
337 c0: C.zero(),
338 c1: C.zero(),
339 implicit_scale: P.base_ring().one()
340 };
341 }
342
343 #[instrument(skip_all)]
347 fn dec_println(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) {
348 let m = Self::dec(P, C, Self::clone_ct(P, C, ct), sk);
349 println!("ciphertext (noise budget: {} / {}):", Self::noise_budget(P, C, ct, sk), ZZbig.abs_log2_ceil(C.base_ring().modulus()).unwrap());
350 P.println(&m);
351 println!();
352 }
353
354 #[instrument(skip_all)]
359 fn dec_println_slots(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>, cache_dir: Option<&str>) {
360 let (p, _e) = is_prime_power(ZZ, P.base_ring().modulus()).unwrap();
361 let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(P.n() as u64), p);
362 let H = if let Some(dir) = cache_dir {
363 HypercubeIsomorphism::new_cache_file::<false>(P, hypercube, dir)
364 } else {
365 HypercubeIsomorphism::new::<false>(P, hypercube)
366 };
367 let m = Self::dec(P, C, Self::clone_ct(P, C, ct), sk);
368 println!("ciphertext (noise budget: {} / {}):", Self::noise_budget(P, C, ct, sk), ZZbig.abs_log2_ceil(C.base_ring().modulus()).unwrap());
369 for a in H.get_slot_values(&m) {
370 H.slot_ring().println(&a);
371 }
372 println!();
373 }
374
375 #[instrument(skip_all)]
385 fn hom_add_plain_encoded(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<CiphertextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
386 assert!(P.base_ring().is_unit(&ct.implicit_scale));
387 let implicit_scale = C.base_ring().coerce(&ZZ, P.base_ring().smallest_lift(ct.implicit_scale));
388 let result = Ciphertext {
389 c0: C.add(ct.c0, C.inclusion().mul_ref_map(m, &implicit_scale)),
390 c1: ct.c1,
391 implicit_scale: ct.implicit_scale
392 };
393 assert!(P.base_ring().is_unit(&result.implicit_scale));
394 return result;
395 }
396
397 #[instrument(skip_all)]
401 fn hom_add_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
402 Self::hom_add_plain_encoded(P, C, &Self::encode_plain(P, C, m), ct)
403 }
404
405 #[instrument(skip_all)]
411 fn enc_sym<R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, rng: R, m: &El<PlaintextRing<Self>>, sk: &SecretKey<Self>) -> Ciphertext<Self> {
412 Self::hom_add_plain(P, C, m, Self::enc_sym_zero(P, C, rng, sk))
413 }
414
415 #[instrument(skip_all)]
419 fn dec(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>, sk: &SecretKey<Self>) -> El<PlaintextRing<Self>> {
420 let noisy_m = C.add(ct.c0, C.mul_ref_snd(ct.c1, sk));
421 let mod_t = P.base_ring().can_hom(&ZZbig).unwrap();
422 return P.inclusion().mul_map(
423 P.from_canonical_basis(C.wrt_canonical_basis(&noisy_m).iter().map(|x| mod_t.map(C.base_ring().smallest_lift(x)))),
424 P.base_ring().invert(&ct.implicit_scale).unwrap()
425 );
426 }
427
428 #[instrument(skip_all)]
438 fn hom_mul_plain_encoded(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<CiphertextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
439 assert!(P.base_ring().is_unit(&ct.implicit_scale));
440 let result = Ciphertext {
441 c0: C.mul_ref_snd(ct.c0, m),
442 c1: C.mul_ref_snd(ct.c1, m),
443 implicit_scale: ct.implicit_scale
444 };
445 assert!(P.base_ring().is_unit(&result.implicit_scale));
446 return result;
447 }
448
449 #[instrument(skip_all)]
456 fn encode_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>) -> El<CiphertextRing<Self>> {
457 let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
458 return C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
459 }
460
461 #[instrument(skip_all)]
465 fn hom_mul_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
466 Self::hom_mul_plain_encoded(P, C, &Self::encode_plain(P, C, m), ct)
467 }
468
469 #[instrument(skip_all)]
473 fn hom_mul_plain_i64(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: i64, mut ct: Ciphertext<Self>) -> Ciphertext<Self> {
474 assert!(P.base_ring().is_unit(&ct.implicit_scale));
475 C.int_hom().mul_assign_map(&mut ct.c0, m as i32);
486 C.int_hom().mul_assign_map(&mut ct.c1, m as i32);
487 assert!(P.base_ring().is_unit(&ct.implicit_scale));
488 return ct;
489 }
490
491 fn merge_implicit_scale(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
496 let mut result = Self::hom_mul_plain_i64(P, C, P.base_ring().smallest_lift(P.base_ring().invert(&ct.implicit_scale).unwrap()), ct);
497 result.implicit_scale = P.base_ring().one();
498 return result;
499 }
500
501 #[instrument(skip_all)]
505 fn clone_ct(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>) -> Ciphertext<Self> {
506 assert!(P.base_ring().is_unit(&ct.implicit_scale));
507 Ciphertext {
508 c0: C.clone_el(&ct.c0),
509 c1: C.clone_el(&ct.c1),
510 implicit_scale: P.base_ring().clone_el(&ct.implicit_scale)
511 }
512 }
513
514 #[instrument(skip_all)]
522 fn noise_budget(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) -> usize {
523 let ct = Self::clone_ct(P, C, ct);
524 let noisy_m = C.add(ct.c0, C.mul_ref_snd(ct.c1, sk));
525 let coefficients = C.wrt_canonical_basis(&noisy_m);
526 let size_of_critical_quantity = <_ as Iterator>::max((0..coefficients.len()).map(|i| {
527 let c = C.base_ring().smallest_lift(coefficients.at(i));
528 let size = ZZbig.abs_log2_ceil(&c);
529 return size.unwrap_or(0);
530 })).unwrap();
531 return ZZbig.abs_log2_ceil(C.base_ring().modulus()).unwrap().saturating_sub(size_of_critical_quantity + 1);
532 }
533
534 #[instrument(skip_all)]
542 fn gen_switch_key<'a, R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &'a CiphertextRing<Self>, mut rng: R, old_sk: &SecretKey<Self>, new_sk: &SecretKey<Self>, params: &KeySwitchKeyParams) -> KeySwitchKey<'a, Self>
543 where Self: 'a
544 {
545 assert_eq!(C.base_ring().len(), params.expected_rns_base_len());
546 let special_modulus_factor_count = params.special_modulus_factor_count;
547 assert!(special_modulus_factor_count < C.base_ring().len());
548 let digits: &RNSGadgetVectorDigitIndices = ¶ms.digits_without_special;
549 let special_modulus = ZZbig.prod(C.base_ring().as_iter().rev().take(special_modulus_factor_count).map(|rns_factor| int_cast(*rns_factor.modulus(), ZZbig, ZZi64)));
550 let mut res0 = GadgetProductRhsOperand::new_with(C.get_ring(), digits.to_owned());
551 let mut res1 = GadgetProductRhsOperand::new_with(C.get_ring(), digits.to_owned());
552 for digit_i in 0..digits.len() {
553 let base = Self::enc_sym_zero(P, C, &mut rng, new_sk);
554 let digit_range = res0.gadget_vector_digits().at(digit_i).clone();
555 let factor = C.base_ring().mul(
556 C.base_ring().get_ring().from_congruence((0..C.base_ring().len()).map(|i2| {
557 let Fp = C.base_ring().at(i2);
558 if digit_range.contains(&i2) { Fp.one() } else { Fp.zero() }
559 })),
560 C.base_ring().coerce(&ZZbig, ZZbig.clone_el(&special_modulus))
561 );
562 if !C.base_ring().is_zero(&factor) {
563 let mut payload = C.clone_el(&old_sk);
564 C.inclusion().mul_assign_ref_map(&mut payload, &factor);
565 C.add_assign(&mut payload, base.c0);
566 res0.set_rns_factor(C.get_ring(), digit_i, payload);
567 res1.set_rns_factor(C.get_ring(), digit_i, base.c1);
568 }
569 }
570 return KeySwitchKey {
571 k0: res0,
572 k1: res1,
573 ring: PhantomData,
574 special_modulus_factor_count: special_modulus_factor_count
575 };
576 }
577
578 #[instrument(skip_all)]
588 fn key_switch<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, C_special: &CiphertextRing<Self>, ct: Ciphertext<Self>, switch_key: &KeySwitchKey<'a, Self>) -> Ciphertext<Self>
589 where Self: 'a
590 {
591 let special_modulus_factor_count = switch_key.special_modulus_factor_count;
592 let special_modulus_factors = RNSFactorIndexList::from(((C_special.base_ring().len() - special_modulus_factor_count)..C_special.base_ring().len()).collect(), C_special.base_ring().len());
593 assert_rns_factor_drop_correct::<Self>(C, C_special, &special_modulus_factors);
594 assert!(switch_key.k0.gadget_vector_digits() == switch_key.k1.gadget_vector_digits());
595
596 if special_modulus_factor_count == 0 {
597 let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &ct.c1, switch_key.k0.gadget_vector_digits());
598 return Ciphertext {
599 c0: C.add_ref_snd(ct.c0, &op.gadget_product(&switch_key.k0, C.get_ring())),
600 c1: op.gadget_product(&switch_key.k1, C.get_ring()),
601 implicit_scale: ct.implicit_scale
602 };
603 } else {
604 let op = GadgetProductLhsOperand::from_element_map_ring_with(
605 C.get_ring(),
606 &ct.c1,
607 switch_key.k0.gadget_vector_digits(),
608 C_special.get_ring()
609 );
610 let switched = Ciphertext {
614 c0: op.gadget_product(&switch_key.k0, C_special.get_ring()),
615 c1: op.gadget_product(&switch_key.k1, C_special.get_ring()),
616 implicit_scale: P.base_ring().one()
617 };
618 let mut result = Self::mod_switch_down_ct(P, C, C_special, &special_modulus_factors, switched);
619 C.add_assign(&mut result.c0, ct.c0);
620 result.implicit_scale = ct.implicit_scale;
621 return result;
622 }
623 }
624
625 #[instrument(skip_all)]
632 fn gen_rk<'a, R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &'a CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>, params: &KeySwitchKeyParams) -> RelinKey<'a, Self>
633 where Self: 'a
634 {
635 Self::gen_switch_key(P, C, rng, &C.pow(C.clone_el(sk), 2), sk, params)
636 }
637
638 #[instrument(skip_all)]
651 fn hom_mul<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, C_special: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: Ciphertext<Self>, rk: &RelinKey<'a, Self>) -> Ciphertext<Self>
652 where Self: 'a
653 {
654 assert!(P.base_ring().is_unit(&lhs.implicit_scale));
655 assert!(P.base_ring().is_unit(&rhs.implicit_scale));
656
657 let [res0, res1, res2] = C.get_ring().two_by_two_convolution([&lhs.c0, &lhs.c1], [&rhs.c0, &rhs.c1]);
658
659 let mut result = Self::key_switch(P, C, C_special, Ciphertext {
660 c0: C.zero(),
661 c1: res2,
662 implicit_scale: P.base_ring().mul(lhs.implicit_scale, rhs.implicit_scale)
663 }, rk);
664 C.add_assign(&mut result.c0, res0);
665 C.add_assign(&mut result.c1, res1);
666 assert!(P.base_ring().is_unit(&result.implicit_scale));
667 return result;
668 }
669
670 #[instrument(skip_all)]
683 fn hom_square<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, C_special: &CiphertextRing<Self>, val: Ciphertext<Self>, rk: &RelinKey<'a, Self>) -> Ciphertext<Self>
684 where Self: 'a
685 {
686 assert!(P.base_ring().is_unit(&val.implicit_scale));
687
688 let [res0, res1, res2] = C.get_ring().two_by_two_convolution([&val.c0, &val.c1], [&val.c0, &val.c1]);
689
690 let mut result = Self::key_switch(P, C, C_special, Ciphertext {
691 c0: C.zero(),
692 c1: res2,
693 implicit_scale: P.base_ring().pow(val.implicit_scale, 2)
694 }, rk);
695 C.add_assign(&mut result.c0, res0);
696 C.add_assign(&mut result.c1, res1);
697 assert!(P.base_ring().is_unit(&result.implicit_scale));
698 return result;
699 }
700
701 #[instrument(skip_all)]
705 fn hom_add(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, mut lhs: Ciphertext<Self>, mut rhs: Ciphertext<Self>) -> Ciphertext<Self> {
706 assert!(P.base_ring().is_unit(&lhs.implicit_scale));
707 assert!(P.base_ring().is_unit(&rhs.implicit_scale));
708
709 let Zt = P.base_ring();
710 let (a, b) = equalize_implicit_scale(Zt, Zt.checked_div(&lhs.implicit_scale, &rhs.implicit_scale).unwrap());
711
712 debug_assert!(!Zt.eq_el(&lhs.implicit_scale, &rhs.implicit_scale) || (a == 1 && b == 1));
713 if a != 1 {
714 C.int_hom().mul_assign_map(&mut rhs.c0, a as i32);
715 C.int_hom().mul_assign_map(&mut rhs.c1, a as i32);
716 P.base_ring().int_hom().mul_assign_map(&mut rhs.implicit_scale, a as i32);
717 }
718 if b != 1 {
719 C.int_hom().mul_assign_map(&mut lhs.c0, b as i32);
720 C.int_hom().mul_assign_map(&mut lhs.c1, b as i32);
721 P.base_ring().int_hom().mul_assign_map(&mut lhs.implicit_scale, b as i32);
722 }
723
724 assert!(Zt.eq_el(&lhs.implicit_scale, &rhs.implicit_scale));
725 let result = Ciphertext {
726 c0: C.add(lhs.c0, rhs.c0),
727 c1: C.add(lhs.c1, rhs.c1),
728 implicit_scale: lhs.implicit_scale
729 };
730 assert!(P.base_ring().is_unit(&result.implicit_scale));
731 return result;
732 }
733
734 fn hom_sub(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: Ciphertext<Self>) -> Ciphertext<Self> {
738 Self::hom_add(P, C, lhs, Ciphertext { c0: rhs.c0, c1: rhs.c1, implicit_scale: P.base_ring().negate(rhs.implicit_scale) })
739 }
740
741 #[instrument(skip_all)]
750 fn hom_galois<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, C_special: &CiphertextRing<Self>, ct: Ciphertext<Self>, g: CyclotomicGaloisGroupEl, gk: &KeySwitchKey<'a, Self>) -> Ciphertext<Self>
751 where Self: 'a
752 {
753 Self::key_switch(P, C, C_special, Ciphertext {
754 c0: C.get_ring().apply_galois_action(&ct.c0, g),
755 c1: C.get_ring().apply_galois_action(&ct.c1, g),
756 implicit_scale: ct.implicit_scale
757 }, gk)
758 }
759
760 #[instrument(skip_all)]
770 fn hom_galois_many<'a, 'b, V>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, C_special: &CiphertextRing<Self>, ct: Ciphertext<Self>, gs: &[CyclotomicGaloisGroupEl], gks: V) -> Vec<Ciphertext<Self>>
771 where V: VectorFn<&'b KeySwitchKey<'a, Self>>,
772 KeySwitchKey<'a, Self>: 'b,
773 'a: 'b,
774 Self: 'a
775 {
776 assert!(P.base_ring().is_unit(&ct.implicit_scale));
777 assert_eq!(gs.len(), gks.len());
778 if gs.len() == 0 {
779 return Vec::new();
780 }
781
782 let special_modulus_factor_count = gks.at(0).special_modulus_factor_count;
783 let special_modulus_factors = RNSFactorIndexList::from(((C_special.base_ring().len() - special_modulus_factor_count)..C_special.base_ring().len()).collect(), C_special.base_ring().len());
784 assert_rns_factor_drop_correct::<Self>(C, C_special, &special_modulus_factors);
785 assert!(gks.iter().all(|gk| gk.special_modulus_factor_count == special_modulus_factor_count), "hom_galois_many() requires all Galois keys to use the same parameters");
786
787 let digits = gks.at(0).k0.gadget_vector_digits();
788 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);
789 assert!(gks.iter().all(|gk| has_same_digits(&gk.k0) && has_same_digits(&gk.k1)), "hom_galois_many() requires all Galois keys to use the same parameters");
790
791 let c1_op = GadgetProductLhsOperand::from_element_map_ring_with(
792 C.get_ring(),
793 &ct.c1,
794 &digits,
795 C_special.get_ring()
796 );
797 let c1_op_gs = c1_op.apply_galois_action_many(C_special.get_ring(), gs);
798 let c0_gs = C.get_ring().apply_galois_action_many(&ct.c0, gs).into_iter();
799 assert_eq!(gks.len(), c1_op_gs.len());
800 assert_eq!(gks.len(), c0_gs.len());
801 return c0_gs.zip(c1_op_gs.iter()).enumerate().map(|(i, (c0_g, c1_g))| if special_modulus_factor_count == 0 {
802 return Ciphertext {
803 c0: C.add_ref_snd(c0_g, &c1_g.gadget_product(&gks.at(i).k0, C.get_ring())),
804 c1: c1_g.gadget_product(&gks.at(i).k1, C.get_ring()),
805 implicit_scale: ct.implicit_scale
806 };
807 } else {
808 let switched = Ciphertext {
812 c0: c1_g.gadget_product(&gks.at(i).k0, C_special.get_ring()),
813 c1: c1_g.gadget_product(&gks.at(i).k1, C_special.get_ring()),
814 implicit_scale: P.base_ring().one()
815 };
816 let mut result = Self::mod_switch_down_ct(P, C, C_special, &special_modulus_factors, switched);
817 C.add_assign(&mut result.c0, c0_g);
818 result.implicit_scale = ct.implicit_scale;
819 return result;
820 }).collect();
821 }
822
823 #[instrument(skip_all)]
833 fn mod_switch_down_C(C: &CiphertextRing<Self>, drop_moduli: &RNSFactorIndexList) -> CiphertextRing<Self> {
834 RingValue::from(C.get_ring().drop_rns_factor(&drop_moduli))
835 }
836
837 #[instrument(skip_all)]
847 fn mod_switch_down_sk(Cnew: &CiphertextRing<Self>, Cold: &CiphertextRing<Self>, drop_moduli: &RNSFactorIndexList, sk: &SecretKey<Self>) -> SecretKey<Self> {
848 assert_rns_factor_drop_correct::<Self>(Cnew, Cold, drop_moduli);
849 if drop_moduli.len() == 0 {
850 Cnew.clone_el(sk)
851 } else {
852 Cnew.get_ring().drop_rns_factor_element(Cold.get_ring(), &drop_moduli, Cold.clone_el(sk))
853 }
854 }
855
856 #[instrument(skip_all)]
864 fn mod_switch_down_rk<'a, 'b>(Cnew: &'b CiphertextRing<Self>, Cold: &CiphertextRing<Self>, drop_moduli: &RNSFactorIndexList, rk: &RelinKey<'a, Self>) -> RelinKey<'b, Self> {
865 assert_rns_factor_drop_correct::<Self>(Cnew, Cold, drop_moduli);
866 if drop_moduli.len() == 0 {
867 KeySwitchKey {
868 k0: rk.k0.clone(Cnew.get_ring()),
869 k1: rk.k1.clone(Cnew.get_ring()),
870 ring: PhantomData,
871 special_modulus_factor_count: rk.special_modulus_factor_count
872 }
873 } else {
874 assert!(drop_moduli.num_within(&((Cold.base_ring().len() - rk.special_modulus_factor_count)..Cold.base_ring().len())) == 0, "Cannot drop RNS factors belonging to the special modulus");
875 KeySwitchKey {
876 k0: rk.k0.clone(Cold.get_ring()).modulus_switch(Cnew.get_ring(), &drop_moduli, Cold.get_ring()),
877 k1: rk.k1.clone(Cold.get_ring()).modulus_switch(Cnew.get_ring(), &drop_moduli, Cold.get_ring()),
878 ring: PhantomData,
879 special_modulus_factor_count: rk.special_modulus_factor_count
880 }
881 }
882 }
883
884 #[instrument(skip_all)]
892 fn mod_switch_down_gk<'a, 'b>(Cnew: &'b CiphertextRing<Self>, Cold: &CiphertextRing<Self>, drop_moduli: &RNSFactorIndexList, gk: &KeySwitchKey<'a, Self>) -> KeySwitchKey<'b, Self> {
893 Self::mod_switch_down_rk(Cnew, Cold, drop_moduli, gk)
894 }
895
896 fn mod_switch_down_compute_implicit_scale_factor(P: &PlaintextRing<Self>, q_new: &El<BigIntRing>, q_old: &El<BigIntRing>) -> El<Zn> {
901 let ZZbig_to_Zt = P.base_ring().can_hom(&ZZbig).unwrap();
902 let result = P.base_ring().checked_div(
903 &ZZbig_to_Zt.map_ref(q_new),
904 &ZZbig_to_Zt.map_ref(q_old)
905 ).unwrap();
906 assert!(P.base_ring().is_unit(&result));
907 return result;
908 }
909
910 #[instrument(skip_all)]
920 fn mod_switch_down_ct(P: &PlaintextRing<Self>, Cnew: &CiphertextRing<Self>, Cold: &CiphertextRing<Self>, drop_moduli: &RNSFactorIndexList, ct: Ciphertext<Self>) -> Ciphertext<Self> {
921 assert_rns_factor_drop_correct::<Self>(Cnew, Cold, drop_moduli);
922 assert!(P.base_ring().is_unit(&ct.implicit_scale));
923
924 if drop_moduli.len() == 0 {
925 return ct;
926 } else {
927
928 let compute_delta = CongruencePreservingAlmostExactBaseConversion::new_with(
929 drop_moduli.iter().map(|i| *Cold.base_ring().at(*i)).collect(),
930 Cnew.base_ring().as_iter().cloned().collect(),
931 *P.base_ring(),
932 Global
933 );
934 let mod_switch_ring_element = |x: El<CiphertextRing<Self>>| {
935 let mut mod_b_part_of_x = OwnedMatrix::zero(drop_moduli.len(), Cold.get_ring().small_generating_set_len(), Cold.base_ring().at(0));
939 Cold.get_ring().partial_representation_wrt_small_generating_set(&x, &drop_moduli, mod_b_part_of_x.data_mut());
940 let mut delta = OwnedMatrix::zero(Cnew.base_ring().len(), Cnew.get_ring().small_generating_set_len(), Cnew.base_ring().at(0));
942 compute_delta.apply(mod_b_part_of_x.data(), delta.data_mut());
943 let delta = Cnew.get_ring().from_representation_wrt_small_generating_set(delta.data());
944 return Cnew.inclusion().mul_map(
947 Cnew.sub(
948 Cnew.get_ring().drop_rns_factor_element(Cold.get_ring(), &drop_moduli, x),
949 delta
950 ),
951 Cnew.base_ring().invert(&Cnew.base_ring().coerce(&ZZbig, ZZbig.prod(drop_moduli.iter().map(|i| int_cast(*Cold.base_ring().at(*i).modulus(), ZZbig, ZZ))))).unwrap()
952 )
953 };
954
955 let result = Ciphertext {
956 c0: mod_switch_ring_element(ct.c0),
957 c1: mod_switch_ring_element(ct.c1),
958 implicit_scale: P.base_ring().mul(ct.implicit_scale, Self::mod_switch_down_compute_implicit_scale_factor(P, Cnew.base_ring().modulus(), Cold.base_ring().modulus()))
959 };
960 assert!(P.base_ring().is_unit(&result.implicit_scale));
961 return result;
962 }
963 }
964
965 #[instrument(skip_all)]
972 fn gen_gk<'a, R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &'a CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>, g: CyclotomicGaloisGroupEl, params: &KeySwitchKeyParams) -> KeySwitchKey<'a, Self>
973 where Self: 'a
974 {
975 Self::gen_switch_key(P, C, rng, &C.get_ring().apply_galois_action(sk, g), sk, params)
976 }
977
978 #[instrument(skip_all)]
984 fn change_plaintext_modulus(Pnew: &PlaintextRing<Self>, Pold: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
985 assert!(Pold.base_ring().is_unit(&ct.implicit_scale));
986
987 let x = C.base_ring().checked_div(
988 &C.base_ring().coerce(&StaticRing::<i64>::RING, *Pnew.base_ring().modulus()),
989 &C.base_ring().coerce(&StaticRing::<i64>::RING, *Pold.base_ring().modulus()),
990 ).unwrap();
991 let new_implicit_scale = Pnew.base_ring().coerce(&StaticRing::<i64>::RING, Pold.base_ring().smallest_positive_lift(ct.implicit_scale));
992 let result = Ciphertext {
993 c0: C.inclusion().mul_ref_snd_map(ct.c0, &x),
994 c1: C.inclusion().mul_ref_snd_map(ct.c1, &x),
995 implicit_scale: new_implicit_scale
996 };
997 assert!(Pnew.base_ring().is_unit(&result.implicit_scale));
998 return result;
999 }
1000
1001 #[instrument(skip_all)]
1007 fn enc_sk(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>) -> Ciphertext<Self> {
1008 Ciphertext {
1009 c0: C.zero(),
1010 c1: C.one(),
1011 implicit_scale: P.base_ring().one()
1012 }
1013 }
1014
1015 #[instrument(skip_all)]
1023 fn mod_switch_to_plaintext(P: &PlaintextRing<Self>, target: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>) -> (El<PlaintextRing<Self>>, El<PlaintextRing<Self>>) {
1024 assert!(signed_gcd(*P.base_ring().modulus(), *target.base_ring().modulus(), ZZ) == 1, "can only mod-switch to ciphertext moduli that are coprime to t");
1025 assert!(P.base_ring().is_unit(&ct.implicit_scale));
1026
1027 let mod_switch = CongruencePreservingRescaling::new_with(
1028 C.base_ring().as_iter().map(|Zp| *Zp).collect(),
1029 vec![*target.base_ring()],
1030 (0..C.base_ring().len()).collect(),
1031 *P.base_ring(),
1032 Global
1033 );
1034 let c0 = C.inclusion().mul_map(ct.c0, C.base_ring().coerce(&ZZ, P.base_ring().smallest_lift(P.base_ring().invert(&P.base_ring().mul(
1035 ct.implicit_scale,
1036 Self::mod_switch_down_compute_implicit_scale_factor(P, &int_cast(*target.base_ring().modulus(), ZZbig, ZZ), C.base_ring().modulus())
1037 )).unwrap())));
1038 let c1 = C.inclusion().mul_map(ct.c1, C.base_ring().coerce(&ZZ, P.base_ring().smallest_lift(P.base_ring().invert(&P.base_ring().mul(
1039 ct.implicit_scale,
1040 Self::mod_switch_down_compute_implicit_scale_factor(P, &int_cast(*target.base_ring().modulus(), ZZbig, ZZ), C.base_ring().modulus())
1041 )).unwrap())));
1042 return (
1043 perform_rns_op_to_plaintext_ring(target, C.get_ring(), &c0, &mod_switch),
1044 perform_rns_op_to_plaintext_ring(target, C.get_ring(), &c1, &mod_switch)
1045 );
1046 }
1047}
1048
1049#[derive(Debug)]
1050pub struct Pow2BGV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator, C: Send + Sync + HERingNegacyclicNTT<Zn> = DefaultNegacyclicNTT> {
1051 pub log2_q_min: usize,
1052 pub log2_q_max: usize,
1053 pub log2_N: usize,
1054 pub ciphertext_allocator: A,
1055 pub negacyclic_ntt: PhantomData<C>
1056}
1057
1058impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> Clone for Pow2BGV<A, C> {
1059
1060 fn clone(&self) -> Self {
1061 Self {
1062 log2_q_min: self.log2_q_min,
1063 log2_q_max: self.log2_q_max,
1064 log2_N: self.log2_N,
1065 ciphertext_allocator: self.ciphertext_allocator.clone(),
1066 negacyclic_ntt: PhantomData
1067 }
1068 }
1069}
1070
1071impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> Display for Pow2BGV<A, C> {
1072
1073 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1074 write!(f, "BGV(n = 2^{}, log2(q) in {}..{})", self.log2_N + 1, self.log2_q_min, self.log2_q_max)
1075 }
1076}
1077
1078impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> BGVCiphertextParams for Pow2BGV<A, C> {
1079
1080 type CiphertextRing = ManagedDoubleRNSRingBase<Pow2CyclotomicNumberRing<C>, A>;
1081
1082 fn number_ring(&self) -> Pow2CyclotomicNumberRing<C> {
1083 Pow2CyclotomicNumberRing::new_with(2 << self.log2_N)
1084 }
1085
1086 #[instrument(skip_all)]
1087 fn enc_sym_zero<R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
1088 let t = C.base_ring().coerce(&ZZ, *P.base_ring().modulus());
1089 let (a, b) = Self::rlwe_sample(C, rng, sk);
1090 let result = Ciphertext {
1091 c0: C.inclusion().mul_ref_snd_map(b, &t),
1092 c1: C.inclusion().mul_ref_snd_map(a, &t),
1093 implicit_scale: P.base_ring().one()
1094 };
1095 return double_rns_repr::<Self, _, _>(P, C, result);
1096 }
1097
1098 #[instrument(skip_all)]
1099 fn max_rns_base(&self) -> zn_rns::Zn<Zn, BigIntRing> {
1100 let log2_q = self.log2_q_min..self.log2_q_max;
1101 let number_ring = self.number_ring();
1102 let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
1103 let max_bits_per_modulus = 57;
1104 let mut rns_base = sample_primes(log2_q.start, log2_q.end, max_bits_per_modulus, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
1105 rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
1106 return zn_rns::Zn::new(rns_base.into_iter().map(|p| Zn::new(int_cast(p, ZZ, ZZbig) as u64)).collect(), ZZbig);
1107 }
1108
1109 #[instrument(skip_all)]
1110 fn create_ciphertext_ring(&self, rns_base: zn_rns::Zn<Zn, BigIntRing>) -> CiphertextRing<Self> {
1111 return ManagedDoubleRNSRingBase::new_with(
1112 self.number_ring(),
1113 rns_base,
1114 self.ciphertext_allocator.clone()
1115 );
1116 }
1117
1118 #[instrument(skip_all)]
1119 fn encode_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>) -> El<CiphertextRing<Self>> {
1120 let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
1121 let result = C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
1122 return C.get_ring().to_doublerns(&result).map(|x| C.get_ring().from_double_rns_repr(C.get_ring().unmanaged_ring().clone_el(x))).unwrap_or(C.zero());
1123 }
1124}
1125
1126#[derive(Clone, Debug)]
1127pub struct CompositeBGV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator> {
1128 pub log2_q_min: usize,
1129 pub log2_q_max: usize,
1130 pub n1: usize,
1131 pub n2: usize,
1132 pub ciphertext_allocator: A
1133}
1134
1135impl<A: Allocator + Clone + Send + Sync> Display for CompositeBGV<A> {
1136
1137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1138 write!(f, "BGV(n = {} * {}, log2(q) in {}..{})", self.n1, self.n2, self.log2_q_min, self.log2_q_max)
1139 }
1140}
1141
1142impl<A: Allocator + Clone + Send + Sync> BGVCiphertextParams for CompositeBGV<A> {
1143
1144 type CiphertextRing = ManagedDoubleRNSRingBase<CompositeCyclotomicNumberRing, A>;
1145
1146 #[instrument(skip_all)]
1147 fn enc_sym_zero<R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
1148 let t = C.base_ring().coerce(&ZZ, *P.base_ring().modulus());
1149 let (a, b) = Self::rlwe_sample(C, rng, sk);
1150 let result = Ciphertext {
1151 c0: C.inclusion().mul_ref_snd_map(b, &t),
1152 c1: C.inclusion().mul_ref_snd_map(a, &t),
1153 implicit_scale: P.base_ring().one()
1154 };
1155 return double_rns_repr::<Self, _, _>(P, C, result);
1156 }
1157
1158 fn number_ring(&self) -> CompositeCyclotomicNumberRing {
1159 CompositeCyclotomicNumberRing::new(self.n1, self.n2)
1160 }
1161
1162 fn max_rns_base(&self) -> zn_rns::Zn<Zn, BigIntRing> {
1163 let log2_q = self.log2_q_min..self.log2_q_max;
1164 let number_ring = self.number_ring();
1165 let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
1166 let max_bits_per_modulus = 57;
1167 let mut rns_base = sample_primes(log2_q.start, log2_q.end, max_bits_per_modulus, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
1168 rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
1169 return zn_rns::Zn::new(rns_base.into_iter().map(|p| Zn::new(int_cast(p, ZZ, ZZbig) as u64)).collect(), ZZbig);
1170 }
1171
1172 #[instrument(skip_all)]
1173 fn create_ciphertext_ring(&self, rns_base: zn_rns::Zn<Zn, BigIntRing>) -> CiphertextRing<Self> {
1174 return ManagedDoubleRNSRingBase::new_with(
1175 self.number_ring(),
1176 rns_base,
1177 self.ciphertext_allocator.clone()
1178 );
1179 }
1180
1181 #[instrument(skip_all)]
1182 fn encode_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>) -> El<CiphertextRing<Self>> {
1183 let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
1184 let result = C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
1185 return C.get_ring().to_doublerns(&result).map(|x| C.get_ring().from_double_rns_repr(C.get_ring().unmanaged_ring().clone_el(x))).unwrap_or(C.zero());
1186 }
1187}
1188
1189fn assert_rns_factor_drop_correct<Params>(Cnew: &CiphertextRing<Params>, Cold: &CiphertextRing<Params>, drop_moduli: &RNSFactorIndexList)
1190 where Params: ?Sized + BGVCiphertextParams
1191{
1192 assert_eq!(Cold.base_ring().len(), Cnew.base_ring().len() + drop_moduli.len(), "incorrect RNS factors dropped");
1193 let mut i_new = 0;
1194 for i_old in 0..Cold.base_ring().len() {
1195 if drop_moduli.contains(i_old) {
1196 continue;
1197 }
1198 assert!(Cold.base_ring().at(i_old).get_ring() == Cnew.base_ring().at(i_new).get_ring(), "incorrect RNS factors dropped");
1199 i_new += 1;
1200 }
1201}
1202
1203pub fn small_basis_repr<Params, NumberRing, A>(_P: &PlaintextRing<Params>, C: &CiphertextRing<Params>, ct: Ciphertext<Params>) -> Ciphertext<Params>
1204 where Params: BGVCiphertextParams<CiphertextRing = ManagedDoubleRNSRingBase<NumberRing, A>>,
1205 NumberRing: HECyclotomicNumberRing,
1206 A: Allocator + Clone
1207{
1208 return Ciphertext {
1209 c0: C.get_ring().from_small_basis_repr(C.get_ring().to_small_basis(&ct.c0).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())),
1210 c1: C.get_ring().from_small_basis_repr(C.get_ring().to_small_basis(&ct.c1).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())),
1211 implicit_scale: ct.implicit_scale
1212 };
1213}
1214
1215pub fn double_rns_repr<Params, NumberRing, A>(_P: &PlaintextRing<Params>, C: &CiphertextRing<Params>, ct: Ciphertext<Params>) -> Ciphertext<Params>
1216 where Params: BGVCiphertextParams<CiphertextRing = ManagedDoubleRNSRingBase<NumberRing, A>>,
1217 NumberRing: HECyclotomicNumberRing,
1218 A: Allocator + Clone
1219{
1220 return Ciphertext {
1221 c0: C.get_ring().from_double_rns_repr(C.get_ring().to_doublerns(&ct.c0).map(|x| C.get_ring().unmanaged_ring().get_ring().clone_el(x)).unwrap_or_else(|| C.get_ring().unmanaged_ring().get_ring().zero())),
1222 c1: C.get_ring().from_double_rns_repr(C.get_ring().to_doublerns(&ct.c1).map(|x| C.get_ring().unmanaged_ring().get_ring().clone_el(x)).unwrap_or_else(|| C.get_ring().unmanaged_ring().get_ring().zero())),
1223 implicit_scale: ct.implicit_scale
1224 };
1225}
1226
1227#[derive(Clone, Debug)]
1228pub struct SingleRNSCompositeBGV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator, C: HERingConvolution<Zn> = DefaultConvolution> {
1229 pub log2_q_min: usize,
1230 pub log2_q_max: usize,
1231 pub n1: usize,
1232 pub n2: usize,
1233 pub ciphertext_allocator: A,
1234 pub convolution: PhantomData<C>
1235}
1236
1237impl<A: Allocator + Clone + Send + Sync, C: HERingConvolution<Zn>> BGVCiphertextParams for SingleRNSCompositeBGV<A, C> {
1238
1239 type CiphertextRing = SingleRNSRingBase<CompositeCyclotomicNumberRing, A, C>;
1240
1241 fn number_ring(&self) -> CompositeCyclotomicNumberRing {
1242 CompositeCyclotomicNumberRing::new(self.n1, self.n2)
1243 }
1244
1245 fn max_rns_base(&self) -> zn_rns::Zn<Zn, BigIntRing> {
1246 let log2_q = self.log2_q_min..self.log2_q_max;
1247 let number_ring = self.number_ring();
1248 let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
1249 let max_bits_per_modulus = 57;
1250 let mut rns_base = sample_primes(log2_q.start, log2_q.end, max_bits_per_modulus, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
1251 rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
1252 return zn_rns::Zn::new(rns_base.into_iter().map(|p| Zn::new(int_cast(p, ZZ, ZZbig) as u64)).collect(), ZZbig);
1253 }
1254
1255 #[instrument(skip_all)]
1256 fn create_ciphertext_ring(&self, rns_base: zn_rns::Zn<Zn, BigIntRing>) -> CiphertextRing<Self> {
1257 let max_log2_n = 1 + ZZ.abs_log2_ceil(&((self.n1 * self.n2) as i64)).unwrap();
1258 let convolutions = rns_base.as_iter().map(|Zp| C::new(*Zp, max_log2_n)).map(Arc::new).collect::<Vec<_>>();
1259 return SingleRNSRingBase::new_with(
1260 self.number_ring(),
1261 rns_base,
1262 self.ciphertext_allocator.clone(),
1263 convolutions
1264 );
1265 }
1266}
1267
1268#[cfg(test)]
1269use tracing_subscriber::prelude::*;
1270#[cfg(test)]
1271use feanor_mempool::dynsize::DynLayoutMempool;
1272#[cfg(test)]
1273use feanor_mempool::AllocArc;
1274#[cfg(test)]
1275use feanor_math::assert_el_eq;
1276#[cfg(test)]
1277use std::ptr::Alignment;
1278#[cfg(test)]
1279use std::fmt::Debug;
1280#[cfg(test)]
1281use crate::log_time;
1282#[cfg(test)]
1283use rand::rngs::StdRng;
1284
1285#[test]
1286fn test_pow2_bgv_enc_dec() {
1287 let mut rng = thread_rng();
1288
1289 let params = Pow2BGV {
1290 log2_q_min: 500,
1291 log2_q_max: 520,
1292 log2_N: 7,
1293 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1294 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1295 };
1296 let t = 257;
1297
1298 let P = params.create_plaintext_ring(t);
1299 let C = params.create_initial_ciphertext_ring();
1300 let sk = Pow2BGV::gen_sk(&C, &mut rng, Some(16));
1301
1302 let input = P.int_hom().map(2);
1303 let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &sk);
1304 let output = Pow2BGV::dec(&P, &C, Pow2BGV::clone_ct(&P, &C, &ctxt), &sk);
1305 assert_el_eq!(&P, input, output);
1306}
1307
1308#[test]
1309fn test_pow2_bgv_gen_sk() {
1310 let mut rng = StdRng::from_seed([0; 32]);
1311
1312 let params = Pow2BGV {
1313 log2_q_min: 500,
1314 log2_q_max: 520,
1315 log2_N: 7,
1316 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1317 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1318 };
1319
1320 let C = params.create_initial_ciphertext_ring();
1321
1322 let sk = Pow2BGV::gen_sk(&C, &mut rng, Some(0));
1323 assert_el_eq!(&C, C.zero(), &sk);
1324
1325 let sk = Pow2BGV::gen_sk(&C, &mut rng, Some(1));
1326 assert!(C.wrt_canonical_basis(&sk).iter().filter(|c| C.base_ring().is_one(&c) || C.base_ring().is_neg_one(&c)).count() == 1);
1327
1328 let sk = Pow2BGV::gen_sk(&C, &mut rng, None);
1329 assert!(C.wrt_canonical_basis(&sk).iter().filter(|c| C.base_ring().is_one(&c)).count() > 32);
1330
1331}
1332
1333#[test]
1334fn test_pow2_bgv_mul() {
1335 let mut rng = thread_rng();
1336
1337 let params = Pow2BGV {
1338 log2_q_min: 500,
1339 log2_q_max: 520,
1340 log2_N: 7,
1341 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1342 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1343 };
1344 let t = 257;
1345
1346 let P = params.create_plaintext_ring(t);
1347 let C = params.create_initial_ciphertext_ring();
1348 let sk = Pow2BGV::gen_sk(&C, &mut rng, None);
1349 let rk = Pow2BGV::gen_rk(&P, &C, &mut rng, &sk, &KeySwitchKeyParams::default(3, 0, C.base_ring().len()));
1350
1351 let input = P.int_hom().map(2);
1352 let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &sk);
1353 let result_ctxt = Pow2BGV::hom_mul(&P, &C, &C, Pow2BGV::clone_ct(&P, &C, &ctxt), ctxt, &rk);
1354 let result = Pow2BGV::dec(&P, &C, result_ctxt, &sk);
1355 assert_el_eq!(&P, P.int_hom().map(4), result);
1356}
1357
1358#[test]
1359fn test_pow2_bgv_hybrid_key_switch() {
1360 let mut rng = thread_rng();
1361
1362 let params = Pow2BGV {
1363 log2_q_min: 300,
1364 log2_q_max: 320,
1365 log2_N: 7,
1366 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1367 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1368 };
1369 let t = 257;
1370 let digits = 3;
1371
1372 let P = params.create_plaintext_ring(t);
1373 let C_special = params.create_initial_ciphertext_ring();
1374 assert_eq!(6, C_special.base_ring().len());
1375 let special_modulus_factors = RNSFactorIndexList::from(vec![5], C_special.base_ring().len());
1376 let C = Pow2BGV::mod_switch_down_C(&C_special, &special_modulus_factors);
1377 let sk = Pow2BGV::gen_sk(&C_special, &mut rng, None);
1378 let sk_new = Pow2BGV::gen_sk(&C_special, &mut rng, None);
1379 let switch_key = Pow2BGV::gen_switch_key(&P, &C_special, &mut rng, &sk, &sk_new, &KeySwitchKeyParams::default(digits, special_modulus_factors.len(), C_special.base_ring().len()));
1380
1381 let input = P.int_hom().map(2);
1382 let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &Pow2BGV::mod_switch_down_sk(&C, &C_special, &special_modulus_factors, &sk));
1383 let result_ctxt = Pow2BGV::key_switch(&P, &C, &C_special, ctxt, &switch_key);
1384 let result = Pow2BGV::dec(&P, &C, result_ctxt, &Pow2BGV::mod_switch_down_sk(&C, &C_special, &special_modulus_factors, &sk_new));
1385 assert_el_eq!(&P, P.int_hom().map(2), result);
1386
1387 let rk = Pow2BGV::gen_rk(&P, &C_special, &mut rng, &sk, &KeySwitchKeyParams::default(digits, special_modulus_factors.len(), C_special.base_ring().len()));
1388 let sk = Pow2BGV::mod_switch_down_sk(&C, &C_special, &special_modulus_factors, &sk);
1389 let input = P.int_hom().map(2);
1390 let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &sk);
1391 let result_ctxt = Pow2BGV::hom_square(&P, &C, &C_special, ctxt, &rk);
1392 let result = Pow2BGV::dec(&P, &C, result_ctxt, &sk);
1393 assert_el_eq!(&P, P.int_hom().map(4), result);
1394}
1395
1396#[test]
1397fn test_pow2_bgv_modulus_switch() {
1398 let mut rng = thread_rng();
1399
1400 let params = Pow2BGV {
1401 log2_q_min: 500,
1402 log2_q_max: 520,
1403 log2_N: 7,
1404 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1405 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1406 };
1407 let t = 257;
1408
1409 let P = params.create_plaintext_ring(t);
1410 let C0 = params.create_initial_ciphertext_ring();
1411 assert_eq!(9, C0.base_ring().len());
1412
1413 let sk = Pow2BGV::gen_sk(&C0, &mut rng, None);
1414
1415 let input = P.int_hom().map(2);
1416 let ctxt = Pow2BGV::enc_sym(&P, &C0, &mut rng, &input, &sk);
1417
1418 for i in [0, 1, 8] {
1419 let to_drop = RNSFactorIndexList::from(vec![i], C0.base_ring().len());
1420 let C1 = Pow2BGV::mod_switch_down_C(&C0, &to_drop);
1421 let result_ctxt = Pow2BGV::mod_switch_down_ct(&P, &C1, &C0, &to_drop, Pow2BGV::clone_ct(&P, &C0, &ctxt));
1422 let result = Pow2BGV::dec(&P, &C1, result_ctxt, &Pow2BGV::mod_switch_down_sk(&C1, &C0, &to_drop, &sk));
1423 assert_el_eq!(&P, P.int_hom().map(2), result);
1424 }
1425}
1426
1427#[test]
1428fn test_pow2_change_plaintext_modulus() {
1429 let mut rng = thread_rng();
1430
1431 let params = Pow2BGV {
1432 log2_q_min: 500,
1433 log2_q_max: 520,
1434 log2_N: 7,
1435 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1436 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1437 };
1438
1439 let P0 = params.create_plaintext_ring(17 * 17);
1440 let P1 = params.create_plaintext_ring(17);
1441 let C = params.create_initial_ciphertext_ring();
1442
1443 let sk = Pow2BGV::gen_sk(&C, &mut rng, None);
1444
1445 let input = P0.int_hom().map(2 * 17);
1446 let ctxt = Pow2BGV::enc_sym(&P0, &C, &mut rng, &input, &sk);
1447 let result_ctxt = Pow2BGV::change_plaintext_modulus(&P1, &P0, &C, ctxt);
1448 let result = Pow2BGV::dec(&P1, &C, result_ctxt, &sk);
1449 assert_el_eq!(&P1, P1.int_hom().map(2), result);
1450}
1451
1452#[test]
1453fn test_pow2_modulus_switch_hom_add() {
1454 let mut rng = thread_rng();
1455
1456 let params = Pow2BGV {
1457 log2_q_min: 500,
1458 log2_q_max: 520,
1459 log2_N: 7,
1460 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1461 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1462 };
1463 let t = 257;
1464
1465 let P = params.create_plaintext_ring(t);
1466 let C0 = params.create_initial_ciphertext_ring();
1467 assert_eq!(9, C0.base_ring().len());
1468
1469 let sk = Pow2BGV::gen_sk(&C0, &mut rng, None);
1470
1471 let input = P.int_hom().map(2);
1472 let ctxt = Pow2BGV::enc_sym(&P, &C0, &mut rng, &input, &sk);
1473
1474 for i in [0, 1, 8] {
1475 let to_drop = RNSFactorIndexList::from(vec![i], C0.base_ring().len());
1476 let C1 = Pow2BGV::mod_switch_down_C(&C0, &to_drop);
1477 let ctxt_modswitch = Pow2BGV::mod_switch_down_ct(&P, &C1, &C0, &to_drop, Pow2BGV::clone_ct(&P, &C0, &ctxt));
1478 let sk_modswitch = Pow2BGV::mod_switch_down_sk(&C1, &C0, &to_drop, &sk);
1479 let ctxt_other = Pow2BGV::enc_sym(&P, &C1, &mut rng, &P.int_hom().map(30), &sk_modswitch);
1480
1481 let ctxt_result = Pow2BGV::hom_add(&P, &C1, ctxt_modswitch, ctxt_other);
1482
1483 let result = Pow2BGV::dec(&P, &C1, ctxt_result, &sk_modswitch);
1484 assert_el_eq!(&P, P.int_hom().map(32), result);
1485 }
1486}
1487
1488#[test]
1489fn test_pow2_bgv_modulus_switch_rk() {
1490 let mut rng = thread_rng();
1491
1492 let params = Pow2BGV {
1493 log2_q_min: 500,
1494 log2_q_max: 520,
1495 log2_N: 7,
1496 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1497 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1498 };
1499 let t = 257;
1500 let digits = 3;
1501
1502 let P = params.create_plaintext_ring(t);
1503 let C0 = params.create_initial_ciphertext_ring();
1504 assert_eq!(9, C0.base_ring().len());
1505
1506 let sk = Pow2BGV::gen_sk(&C0, &mut rng, None);
1507 let rk = Pow2BGV::gen_rk(&P, &C0, &mut rng, &sk, &KeySwitchKeyParams::default(digits, 0, C0.base_ring().len()));
1508
1509 let input = P.int_hom().map(2);
1510 let ctxt = Pow2BGV::enc_sym(&P, &C0, &mut rng, &input, &sk);
1511
1512 for i in [0, 1, 8] {
1513 let to_drop = RNSFactorIndexList::from(vec![i], C0.base_ring().len());
1514 let C1 = Pow2BGV::mod_switch_down_C(&C0, &to_drop);
1515 let new_sk = Pow2BGV::mod_switch_down_sk(&C1, &C0, &to_drop, &sk);
1516 let new_rk = Pow2BGV::mod_switch_down_rk(&C1, &C0, &to_drop, &rk);
1517 let ctxt2 = Pow2BGV::enc_sym(&P, &C1, &mut rng, &P.int_hom().map(3), &new_sk);
1518 let result_ctxt = Pow2BGV::hom_mul(
1519 &P,
1520 &C1,
1521 &C1,
1522 Pow2BGV::mod_switch_down_ct(&P, &C1, &C0, &to_drop, Pow2BGV::clone_ct(&P, &C0, &ctxt)),
1523 ctxt2,
1524 &new_rk
1525 );
1526 let result = Pow2BGV::dec(&P, &C1, result_ctxt, &new_sk);
1527 assert_el_eq!(&P, P.int_hom().map(6), result);
1528 }
1529}
1530
1531#[test]
1532#[should_panic(expected = "special modulus")]
1533fn test_pow2_bgv_drop_special_modulus() {
1534 let mut rng = thread_rng();
1535
1536 let params = Pow2BGV {
1537 log2_q_min: 500,
1538 log2_q_max: 520,
1539 log2_N: 7,
1540 ciphertext_allocator: DefaultCiphertextAllocator::default(),
1541 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1542 };
1543 let t = 257;
1544
1545 let P = params.create_plaintext_ring(t);
1546 let C0 = params.create_initial_ciphertext_ring();
1547 assert_eq!(9, C0.base_ring().len());
1548
1549 let sk = Pow2BGV::gen_sk(&C0, &mut rng, None);
1550 let rk = Pow2BGV::gen_rk(&P, &C0, &mut rng, &sk, &KeySwitchKeyParams::default(3, 2, C0.base_ring().len()));
1551
1552 let main_drop = RNSFactorIndexList::from(vec![7, 8], C0.base_ring().len());
1553 let C = Pow2BGV::mod_switch_down_C(&C0, &main_drop);
1554 let sk = Pow2BGV::mod_switch_down_sk(&C, &C0, &main_drop, &sk);
1555 let input = P.int_hom().map(2);
1556 let ctxt = Pow2BGV::enc_sym(&P, &C, &mut rng, &input, &sk);
1557
1558 let drop1 = RNSFactorIndexList::from(vec![7], C0.base_ring().len());
1559 let C1 = Pow2BGV::mod_switch_down_C(&C0, &drop1);
1560 let rk1 = Pow2BGV::mod_switch_down_rk(&C1, &C0, &drop1, &rk);
1561 assert_el_eq!(&P, P.int_hom().map(4), Pow2BGV::dec(&P, &C, Pow2BGV::hom_square(&P, &C, &C1, Pow2BGV::clone_ct(&P, &C, &ctxt), &rk1), &sk));
1562}
1563
1564#[test]
1565#[ignore]
1566fn measure_time_pow2_bgv() {
1567 let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1568 tracing_subscriber::registry().with(chrome_layer).init();
1569
1570 let mut rng = thread_rng();
1571
1572 let params = Pow2BGV {
1573 log2_q_min: 790,
1574 log2_q_max: 800,
1575 log2_N: 15,
1576 ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1577 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1578 };
1579 let t = 257;
1580 let digits = 3;
1581
1582 let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1583 params.create_plaintext_ring(t)
1584 );
1585 let C = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1586 params.create_initial_ciphertext_ring()
1587 );
1588
1589 let sk = log_time::<_, _, true, _>("GenSK", |[]|
1590 Pow2BGV::gen_sk(&C, &mut rng, None)
1591 );
1592
1593 let m = P.int_hom().map(2);
1594 let ct = log_time::<_, _, true, _>("EncSym", |[]|
1595 Pow2BGV::enc_sym(&P, &C, &mut rng, &m, &sk)
1596 );
1597
1598 let res = log_time::<_, _, true, _>("HomAddPlain", |[]|
1599 Pow2BGV::hom_add_plain(&P, &C, &m, Pow2BGV::clone_ct(&P, &C, &ct))
1600 );
1601 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BGV::dec(&P, &C, res, &sk));
1602
1603 let res = log_time::<_, _, true, _>("HomMulPlain", |[]|
1604 Pow2BGV::hom_mul_plain(&P, &C, &m, Pow2BGV::clone_ct(&P, &C, &ct))
1605 );
1606 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BGV::dec(&P, &C, res, &sk));
1607
1608 let rk = log_time::<_, _, true, _>("GenRK", |[]|
1609 Pow2BGV::gen_rk(&P, &C, &mut rng, &sk, &KeySwitchKeyParams::default(digits, 0, C.base_ring().len()))
1610 );
1611 let ct2 = Pow2BGV::enc_sym(&P, &C, &mut rng, &m, &sk);
1612 let res = log_time::<_, _, true, _>("HomMul", |[]|
1613 Pow2BGV::hom_mul(&P, &C, &C, ct, ct2, &rk)
1614 );
1615 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BGV::dec(&P, &C, Pow2BGV::clone_ct(&P, &C, &res), &sk));
1616
1617 let to_drop = RNSFactorIndexList::from(vec![0], C.base_ring().len());
1618 let C_new = Pow2BGV::mod_switch_down_C(&C, &to_drop);
1619 let sk_new = Pow2BGV::mod_switch_down_sk(&C_new, &C, &to_drop, &sk);
1620 let res_new = log_time::<_, _, true, _>("ModSwitch", |[]|
1621 Pow2BGV::mod_switch_down_ct(&P, &C_new, &C, &to_drop, res)
1622 );
1623 assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BGV::dec(&P, &C_new, res_new, &sk_new));
1624}
1625
1626#[test]
1627#[ignore]
1628fn measure_time_double_rns_composite_bgv() {
1629 let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1630 tracing_subscriber::registry().with(chrome_layer).init();
1631
1632 let mut rng = thread_rng();
1633
1634 let params = CompositeBGV {
1635 log2_q_min: 1090,
1636 log2_q_max: 1100,
1637 n1: 127,
1638 n2: 337,
1639 ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1640 };
1641 let t = 4;
1642 let digits = 3;
1643
1644 let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1645 params.create_plaintext_ring(t)
1646 );
1647 let C = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1648 params.create_initial_ciphertext_ring()
1649 );
1650
1651 let sk = log_time::<_, _, true, _>("GenSK", |[]|
1652 CompositeBGV::gen_sk(&C, &mut rng, None)
1653 );
1654
1655 let m = P.int_hom().map(3);
1656 let ct = log_time::<_, _, true, _>("EncSym", |[]|
1657 CompositeBGV::enc_sym(&P, &C, &mut rng, &m, &sk)
1658 );
1659 assert_el_eq!(&P, &P.int_hom().map(3), &CompositeBGV::dec(&P, &C, CompositeBGV::clone_ct(&P, &C, &ct), &sk));
1660
1661 let res = log_time::<_, _, true, _>("HomAddPlain", |[]|
1662 CompositeBGV::hom_add_plain(&P, &C, &m, CompositeBGV::clone_ct(&P, &C, &ct))
1663 );
1664 assert_el_eq!(&P, &P.int_hom().map(2), &CompositeBGV::dec(&P, &C, res, &sk));
1665
1666 let res = log_time::<_, _, true, _>("HomMulPlain", |[]|
1667 CompositeBGV::hom_mul_plain(&P, &C, &m, CompositeBGV::clone_ct(&P, &C, &ct))
1668 );
1669 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBGV::dec(&P, &C, res, &sk));
1670
1671 let rk = log_time::<_, _, true, _>("GenRK", |[]|
1672 CompositeBGV::gen_rk(&P, &C, &mut rng, &sk, &KeySwitchKeyParams::default(digits, 0, C.base_ring().len()))
1673 );
1674 let ct2 = CompositeBGV::enc_sym(&P, &C, &mut rng, &m, &sk);
1675 let res = log_time::<_, _, true, _>("HomMul", |[]|
1676 CompositeBGV::hom_mul(&P, &C, &C, ct, ct2, &rk)
1677 );
1678 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBGV::dec(&P, &C, CompositeBGV::clone_ct(&P, &C, &res), &sk));
1679
1680 let to_drop = RNSFactorIndexList::from(vec![0], C.base_ring().len());
1681 let C_new = CompositeBGV::mod_switch_down_C(&C, &to_drop);
1682 let sk_new = CompositeBGV::mod_switch_down_sk(&C_new, &C, &to_drop, &sk);
1683 let res_new = log_time::<_, _, true, _>("ModSwitch", |[]|
1684 CompositeBGV::mod_switch_down_ct(&P, &C_new, &C, &to_drop, res)
1685 );
1686 assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBGV::dec(&P, &C_new, res_new, &sk_new));
1687}
1688
1689#[test]
1690#[ignore]
1691fn measure_time_single_rns_composite_bgv() {
1692 let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1693 tracing_subscriber::registry().with(chrome_layer).init();
1694
1695 let mut rng = thread_rng();
1696
1697 let params = SingleRNSCompositeBGV {
1698 log2_q_min: 1090,
1699 log2_q_max: 1100,
1700 n1: 127,
1701 n2: 337,
1702 ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1703 convolution: PhantomData::<DefaultConvolution>
1704 };
1705 let t = 4;
1706 let digits = 3;
1707
1708 let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1709 params.create_plaintext_ring(t)
1710 );
1711 let C = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1712 params.create_initial_ciphertext_ring()
1713 );
1714
1715 let sk = log_time::<_, _, true, _>("GenSK", |[]|
1716 SingleRNSCompositeBGV::gen_sk(&C, &mut rng, None)
1717 );
1718
1719 let m = P.int_hom().map(3);
1720 let ct = log_time::<_, _, true, _>("EncSym", |[]|
1721 SingleRNSCompositeBGV::enc_sym(&P, &C, &mut rng, &m, &sk)
1722 );
1723 assert_el_eq!(&P, &P.int_hom().map(3), &SingleRNSCompositeBGV::dec(&P, &C, SingleRNSCompositeBGV::clone_ct(&P, &C, &ct), &sk));
1724
1725 let res = log_time::<_, _, true, _>("HomAddPlain", |[]|
1726 SingleRNSCompositeBGV::hom_add_plain(&P, &C, &m, SingleRNSCompositeBGV::clone_ct(&P, &C, &ct))
1727 );
1728 assert_el_eq!(&P, &P.int_hom().map(2), &SingleRNSCompositeBGV::dec(&P, &C, res, &sk));
1729
1730 let res = log_time::<_, _, true, _>("HomMulPlain", |[]|
1731 SingleRNSCompositeBGV::hom_mul_plain(&P, &C, &m, SingleRNSCompositeBGV::clone_ct(&P, &C, &ct))
1732 );
1733 assert_el_eq!(&P, &P.int_hom().map(1), &SingleRNSCompositeBGV::dec(&P, &C, res, &sk));
1734
1735 let rk = log_time::<_, _, true, _>("GenRK", |[]|
1736 SingleRNSCompositeBGV::gen_rk(&P, &C, &mut rng, &sk, &KeySwitchKeyParams::default(digits, 0, C.base_ring().len()))
1737 );
1738 let ct2 = SingleRNSCompositeBGV::enc_sym(&P, &C, &mut rng, &m, &sk);
1739 let res = log_time::<_, _, true, _>("HomMul", |[]|
1740 SingleRNSCompositeBGV::hom_mul(&P, &C, &C, ct, ct2, &rk)
1741 );
1742 assert_el_eq!(&P, &P.int_hom().map(1), &SingleRNSCompositeBGV::dec(&P, &C, SingleRNSCompositeBGV::clone_ct(&P, &C, &res), &sk));
1743
1744 let to_drop = RNSFactorIndexList::from(vec![0], C.base_ring().len());
1745 let C_new = SingleRNSCompositeBGV::mod_switch_down_C(&C, &to_drop);
1746 let sk_new = SingleRNSCompositeBGV::mod_switch_down_sk(&C_new, &C, &to_drop, &sk);
1747 let res_new = log_time::<_, _, true, _>("ModSwitch", |[]|
1748 SingleRNSCompositeBGV::mod_switch_down_ct(&P, &C_new, &C, &to_drop, res)
1749 );
1750 assert_el_eq!(&P, &P.int_hom().map(1), &SingleRNSCompositeBGV::dec(&P, &C_new, res_new, &sk_new));
1751}