he_ring/bgv/
mod.rs

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
51///
52/// A key-switching key for BGV. This includes Relinearization and Galois keys.
53/// Note that this implementation does not include an automatic management of
54/// the ciphertext modulus chain, it is up to the user to keep track of the RNS
55/// base used for each ciphertext.
56/// 
57pub 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    ///
67    /// Returns the parameters corresponding to this key-switching key
68    /// 
69    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    ///
77    /// Returns the constant component of the key-switching key, i.e. `k0` from
78    /// the tuple `k0, k1` that satisfies `k0[i] + k1[i] * s_new = g[i] * s_old`
79    /// 
80    pub fn k0<'b>(&'b self) -> &'b GadgetProductRhsOperand<Params::CiphertextRing> {
81        &self.k0
82    }
83
84    ///
85    /// Returns the linear component of the key-switching key, i.e. `k1` from
86    /// the tuple `k0, k1` that satisfies `k0[i] + k1[i] * s_new = g[i] * s_old`
87    /// 
88    pub fn k1<'b>(&'b self) -> &'b GadgetProductRhsOperand<Params::CiphertextRing> {
89        &self.k1
90    }
91}
92
93/// 
94/// Parameters for a key-switching key.
95/// 
96/// More concretely, (hybrid) key-switching is parameterized by two parameters: 
97///  - the set of digits used for the gadget decomposition
98///  - the special modulus
99/// 
100/// For an explanation, see the doc of the corresponding members. 
101/// 
102/// # Example
103/// 
104/// A standard choice is to use a small value for `special_modulus_factor_count`
105/// (e.g. 1 or 2), and then set `digit_count = rns_base_len / special_modulus_factor_count`. 
106/// ```
107/// # use he_ring::bgv::*;
108/// # use he_ring::gadget_product::digits::*;
109/// let rns_base_len = 10; // length of the RNS base of the ciphertext ring
110/// let special_modulus_factor_count = 2;
111/// let digit_count = rns_base_len / special_modulus_factor_count;
112/// let params = KeySwitchKeyParams::default(digit_count, special_modulus_factor_count, rns_base_len);
113/// ```
114/// 
115#[derive(Clone)]
116pub struct KeySwitchKeyParams {
117    /// the special modulus is the product of the last `special_modulus_factor_count` rns factors
118    /// of the ciphertext ring. Key-switching can only be performed on ciphertexts over an rns base
119    /// that does not include the special modulus (i.e. must possibly be modulus-switched down before
120    /// performing key-switching, possibly introducing noise). On the other hand, a high special modulus
121    /// reduces the (additive) noise growth caused by key-switching. Note that the special modulus
122    /// can be 1 (if `special_modulus_factor_count = 0`), in which case only digit-based key-switching
123    /// will be performed.
124    pub special_modulus_factor_count: usize,
125    /// The groups of RNS factors that are used as digits during the gadget decomposition (see also [`RNSGadgetVectorDigitIndices`]).
126    /// The (additive) noise growth during key-switching depends on the largest digit (i.e. the maximal size
127    /// of the product of rns factors belonging to a single digit), however a larger number of digits
128    /// will make key-switching keys larger, and key-switching more expensive. Note that noise growth
129    /// becomes minimal if the largest digit is smaller or equal the special modulus.
130    /// 
131    /// The special modulus RNS factors should not be included in this list.
132    pub digits_without_special: Box<RNSGadgetVectorDigitIndices>
133}
134
135impl KeySwitchKeyParams {
136
137    ///
138    /// Creates new [`KeySwitchKeyParams`], making a balanced selection of digits. This should
139    /// be a reasonable choice in basically all situations.
140    /// 
141    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    /// 
149    /// Returns the length of the RNS base that key-switching keys with these parameters
150    /// are defined over.
151    /// 
152    pub fn expected_rns_base_len(&self) -> usize {
153        self.digits_without_special.rns_base_len() + self.special_modulus_factor_count
154    }
155}
156
157///
158/// Contains the trait [`noise_estimator::BGVNoiseEstimator`] for objects that provide
159/// estimates of the noise level of ciphertexts after BGV homomorphic operations.
160/// Currently, the only provided implementation is the somewhat imprecise and not rigorously
161/// justified [`noise_estimator::NaiveBGVNoiseEstimator`], which is based on simple asymptotic
162/// formulas.
163/// 
164pub mod noise_estimator;
165///
166/// Contains the trait [`modswitch::BGVModswitchStrategy`] and the implementation
167/// [`modswitch::DefaultModswitchStrategy`] for automatic modulus management in BGV.
168/// 
169pub mod modswitch;
170///
171/// Contains the implementation of BGV thin bootstrapping.
172/// 
173pub mod bootstrap;
174
175const ZZbig: BigIntRing = BigIntRing::RING;
176const ZZ: StaticRing<i64> = StaticRing::<i64>::RING;
177
178///
179/// A BGV ciphertext w.r.t. some [`BGVCiphertextParams`]. Note that this implementation
180/// does not include an automatic management of the ciphertext modulus chain,
181/// it is up to the user to keep track of the RNS base used for each ciphertext.
182/// 
183pub struct Ciphertext<Params: ?Sized + BGVCiphertextParams> {
184    /// the ciphertext represents the value `implicit_scale^-1 lift(c0 + c1 s) mod t`, 
185    /// i.e. `implicit_scale` stores the factor in `Z/tZ` that is introduced by modulus-switching;
186    /// Hence, `implicit_scale` is set to `1` when encrypting a value, and only changes when
187    /// doing modulus-switching.
188    pub implicit_scale: El<Zn>,
189    pub c0: El<CiphertextRing<Params>>,
190    pub c1: El<CiphertextRing<Params>>
191}
192
193///
194/// Computes small `a, b` such that `a/b = implicit_scale_bound` modulo `t`.
195/// 
196pub 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
207///
208/// Trait for types that represent an instantiation of BGV.
209/// 
210/// The design is very similar to [`super::bfv::BFVCiphertextParams`], for details
211/// have a look at that. In particular, the plaintext modulus is not a part
212/// of the [`super::bfv::BFVCiphertextParams`], but the (initial) ciphertext modulus size
213/// is. Note however that BGV requires many ciphertext rings, with progressively
214/// smaller ciphertext moduli. You can either manage these manually, or have a look
215/// on [`super::modswitch::BGVModswitchStrategy`], which is built on top of this
216/// trait and (partially at least) manages ciphertext moduli automatically.
217/// In particular, this is different to how other libraries handle BGV ciphertexts, for
218/// example HElib by default manages the moduli of all BGV ciphertexts.
219/// 
220/// For a few more details on how this works, see [`crate::examples::bgv_basics`].
221/// 
222pub trait BGVCiphertextParams {
223    
224    ///
225    /// Type of the ciphertext ring that is used when creating a ciphertext
226    /// ring with these parameters.
227    /// 
228    type CiphertextRing: BGFVCiphertextRing + CyclotomicRing + FiniteRing;
229
230    ///
231    /// Creates the maximal/initial RNS base, which we use to construct
232    /// the ciphertext ring that is used for relinearization and Galois keys,
233    /// and fresh encryptions.
234    /// 
235    /// For more details on the modulus chain, see [`crate::examples::bgv_basics`].
236    /// 
237    fn max_rns_base(&self) -> zn_rns::Zn<Zn, BigIntRing>;
238
239    ///
240    /// Creates the ciphertext ring corresponding to the given RNS base.
241    /// 
242    /// In many cases, you might already have access to a ciphertext ring
243    /// with larger RNS base, in these cases it is more efficient to use
244    /// [`BGVCiphertextParams::mod_switch_down_C()`].
245    /// 
246    fn create_ciphertext_ring(&self, rns_base: zn_rns::Zn<Zn, BigIntRing>) -> CiphertextRing<Self>;
247
248    ///
249    /// Creates the maximal/initial ciphertext ring, which is used for relinearization
250    /// and Galois keys, and fresh encryptions. This should use the maximal RNS base.
251    /// 
252    /// For more details on the modulus chain, see [`crate::examples::bgv_basics`].
253    /// 
254    fn create_initial_ciphertext_ring(&self) -> CiphertextRing<Self> {
255        self.create_ciphertext_ring(self.max_rns_base())
256    }
257
258    ///
259    /// The number ring `R` from which we derive the ciphertext rings `R/qR` and the
260    /// plaintext ring `R/tR`.
261    /// 
262    fn number_ring(&self) -> NumberRing<Self>;
263
264    ///
265    /// Creates a plaintext ring `R/tR` for the given plaintext modulus `t`.
266    /// 
267    #[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    ///
273    /// Generates a secret key, which is either a sparse ternary element of the
274    /// ciphertext ring (with hamming weight `hwt`), or a uniform ternary element
275    /// of the ciphertext ring (if `hwt == None`).
276    /// 
277    #[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    ///
298    /// Creates an RLWE sample `(a, -as + e)`, where `s = sk` is the secret key and `a, e`
299    /// are sampled using randomness from `rng`. Currently, the standard deviation of the
300    /// error is fixed to `3.2`.
301    /// 
302    #[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    ///
312    /// Creates a fresh encryption of zero, i.e. a ciphertext `(c0, c1) = (-as + te, a)`
313    /// where `s = sk` is the given secret key. `a` and `e` are sampled using the randomness
314    /// of `rng`. Currently, the standard deviation of the error is fixed to `3.2`.
315    /// 
316    #[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    ///
328    /// Creates a "transparent" encryption of zero, i.e. a ciphertext that represents zero,
329    /// but does not actually hide the value - everyone can see that it is zero, without the
330    /// secret key.
331    /// 
332    /// Mathematically, this is just the ciphertext `(c0, c1) = (0, 0)`.
333    /// 
334    #[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    ///
344    /// Decrypts the given ciphertext and prints it to stdout. Designed for debugging.
345    /// 
346    #[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    ///
355    /// Decrypts the given ciphertext and prints the values of its slots to stdout. 
356    /// Designed for debugging.
357    /// 
358    #[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    ///
376    /// Returns an encryption of the sum of the encrypted input and the given plaintext,
377    /// which has already been lifted/encoded into the ciphertext ring.
378    /// 
379    /// When the plaintext is given as an element of `P`, use [`BGVCiphertextParams::hom_add_plain()`]
380    /// instead. However, internally, the plaintext will be lifted into the ciphertext ring during
381    /// the addition, and if this is performed in advance (via [`BGVCiphertextParams::encode_plain()`]),
382    /// addition will be faster.
383    /// 
384    #[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    ///
398    /// Returns an encryption of the sum of the encrypted input and the given plaintext.
399    /// 
400    #[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    ///
406    /// Returns a fresh encryption of the given element, i.e. a ciphertext `(c0, c1) = (-as + te + m, a)`
407    /// where `s = sk` is the given secret key. `a` and `e` are sampled using the randomness of `rng`. 
408    /// Currently, the standard deviation of the error is fixed to `3.2`.
409    /// 
410    #[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    ///
416    /// Decrypts the given ciphertext using the given secret key.
417    /// 
418    #[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    ///
429    /// Returns an encryption of the product of the encrypted input and the given plaintext,
430    /// which has already been lifted/encoded into the ciphertext ring.
431    /// 
432    /// When the plaintext is given as an element of `P`, use [`BGVCiphertextParams::hom_mul_plain()`]
433    /// instead. However, internally, the plaintext will be lifted into the ciphertext ring during
434    /// the multiplication, and if this is performed in advance (via [`BGVCiphertextParams::encode_plain()`]),
435    /// multiplication will be faster.
436    /// 
437    #[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    ///
450    /// Computes the smallest lift of the plaintext ring element to the ciphertext
451    /// ring. The result can be used in [`BGVCiphertextParams::hom_add_plain_encoded()`]
452    /// or [`BGVCiphertextParams::hom_mul_plain_encoded()`] to compute plaintext-ciphertext
453    /// addition resp. multiplication faster.
454    /// 
455    #[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    ///
462    /// Returns an encryption of the product of the encrypted input and the given plaintext.
463    /// 
464    #[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    ///
470    /// Returns an encryption of the product of the encrypted input and the given plaintext.
471    /// 
472    #[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        // we could try to do tricks involving `implicit_scale` here
476        //  - if `m mod t` is a unit, we could just multiply `m^-1` to implicit scale;
477        //    however, this makes handling the non-unit case ugly
478        //  - otherwise, we could also use this opportunity to multiply `implicit_scale^-1`
479        //    to the ciphertext as well, and reset the implicit scale to 1; however, this
480        //    might not be helpful in all circumstances
481        // In the end, I think there is no default behavior for this that makes sense
482        // in most situations and is not to unintuitive. Hence, we leave any `implicit_scale`
483        // tricks to the modswitching strategy, which has higher-level information and might
484        // be able to do something with that
485        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    ///
492    /// Converts a ciphertext into a ciphertext with `implicit_scale = 1`, but slightly
493    /// larger noise. Mainly used for internal purposes.
494    /// 
495    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    ///
502    /// Copies a ciphertext.
503    /// 
504    #[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    ///
515    /// Returns the value
516    /// ```text
517    ///   log2( q / | c0 + c1 s |_inf )
518    /// ```
519    /// which roughly corresponds to the "noise budget" of the ciphertext, in bits.
520    /// 
521    #[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    ///
535    /// Generates a key-switch key, which can be used (by [`BGVCiphertextParams::key_switch()`]) to
536    /// convert a ciphertext w.r.t. `old_sk` into a ciphertext w.r.t. `new_sk`.
537    /// 
538    /// The special modulus used for the key-switching key consists of the last 
539    /// [`KeySwitchKeyParams::special_modulus_factor_count`] rns factors of `C`.
540    /// 
541    #[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 = &params.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    ///
579    /// Converts a ciphertext w.r.t. a secret key `old_sk` to a ciphertext w.r.t. a
580    /// secret key `new_sk`, where `switch_key` is a key-switching key for `old_sk` and
581    /// `new_sk` (which can be generated using [`BGVCiphertextParams::gen_switch_key()`]).
582    /// 
583    /// `C_special` must be the ciphertext ring w.r.t. which the key-switching key is defined.
584    /// In other words, this is the ciphertext ring, with additional [`KeySwitchKey::special_modulus_factor_count`]
585    /// rns factors corresponding to the special modulus. If the special modulus is 1, this should be equal to `C`.
586    /// 
587    #[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            // we cheat regarding the implicit scale; since the scaling up and down later exactly
611            // cancel out any changes to the implicit scale, we just temporarily set it to 1 and later
612            // overwrite it with the original implicit scale
613            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    ///
626    /// Generates a relinearization key, necessary to compute homomorphic multiplications.
627    /// 
628    /// The special modulus used for the relinearization key consists of the last 
629    /// [`KeySwitchKeyParams::special_modulus_factor_count`] rns factors of `C`.
630    /// 
631    #[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    ///
639    /// Computes an encryption of the product of two encrypted inputs.
640    /// 
641    /// Since HE-Ring does not (at least not implicitly) perform automatic modulus management,
642    /// it is necessary to modulus-switch between calls to `hom_mul()` in order to prevent
643    /// `hom_mul()` from causing exponential noise growth. For more info on modulus-switching
644    /// and the modulus chain, see [`crate::examples::bgv_basics`].
645    /// 
646    /// `C_special` must be the ciphertext ring w.r.t. which the relinearization key is defined.
647    /// In other words, this is the ciphertext ring, with additional [`KeySwitchKey::special_modulus_factor_count`]
648    /// rns factors corresponding to the special modulus. If the special modulus is 1, this should be equal to `C`.
649    /// 
650    #[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    ///
671    /// Computes an encryption of the square of an encrypted input.
672    /// 
673    /// Since HE-Ring does not (at least not implicitly) perform automatic modulus management,
674    /// it is necessary to modulus-switch between calls to `hom_square()` in order to prevent
675    /// `hom_square()` from causing exponential noise growth. For more info on modulus-switching
676    /// and the modulus chain, see [`crate::examples::bgv_basics`].
677    ///  
678    /// `C_special` must be the ciphertext ring w.r.t. which the relinearization key is defined.
679    /// In other words, this is the ciphertext ring, with additional [`KeySwitchKey::special_modulus_factor_count`]
680    /// rns factors corresponding to the special modulus. If the special modulus is 1, this should be equal to `C`.
681    /// 
682    #[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    ///
702    /// Computes an encryption of the sum of two encrypted inputs.
703    /// 
704    #[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    ///
735    /// Computes an encryption of the difference of two encrypted inputs.
736    /// 
737    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    ///
742    /// Computes an encryption of `sigma(x)`, where `x` is the message encrypted by the given ciphertext
743    /// and `sigma` is the given Galois automorphism.
744    ///  
745    /// `C_special` must be the ciphertext ring w.r.t. which the Galois key is defined.
746    /// In other words, this is the ciphertext ring, with additional [`KeySwitchKey::special_modulus_factor_count`]
747    /// rns factors corresponding to the special modulus. If the special modulus is 1, this should be equal to `C`.
748    /// 
749    #[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    ///
761    /// Homomorphically applies multiple Galois automorphisms at once.
762    /// Functionally, this is equivalent to calling [`BGVCiphertextParams::hom_galois()`]
763    /// multiple times, but can be faster.
764    ///  
765    /// `C_special` must be the ciphertext ring w.r.t. which all the Galois key are defined.
766    /// In other words, this is the ciphertext ring, with additional [`KeySwitchKey::special_modulus_factor_count`]
767    /// rns factors corresponding to the special modulus. If the special modulus is 1, this should be equal to `C`.
768    /// 
769    #[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            // we cheat regarding the implicit scale; since the scaling up and down later exactly
809            // cancel out any changes to the implicit scale, we just temporarily set it to 1 and later
810            // overwrite it with the original implicit scale
811            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    ///
824    /// Given `R/qR` this creates the ciphertext ring `R/q'R`, where the RNS base for `q'`
825    /// is derived from the RNS base of `q` by removing the RNS factors whose indices are mentioned
826    /// in `drop_moduli`.
827    /// 
828    /// Note that for the implementation in HE-Ring at least, the underlying rings will share
829    /// most of their data, which means that this function is actually very cheap, in particular
830    /// much cheaper than creating a new ciphertext ring (e.g. using [`BGVCiphertextParams::create_ciphertext_ring()`]).
831    /// 
832    #[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    ///
838    /// Modulus-switches a secret key in a way compatible with modulus-switching ciphertexts.
839    /// 
840    /// In more detail, given `R/q'R` and `R/qR` where the RNS base for `q'`is derived from the RNS
841    /// base of `q` by removing the RNS factors whose indices are mentioned in `drop_moduli`, this
842    /// computes the secret key `sk mod q'`. Note that, if `ct` is an encryption w.r.t. `sk` over `R/qR`
843    /// and is modulus-switched to `ct'` over the ring `R/q'R`, then `sk mod q'` can be used to decrypt
844    /// `ct'`.
845    /// 
846    #[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    ///
857    /// Modulus-switches a relinearization key in a way compatible with modulus-switching ciphertexts.
858    /// 
859    /// This is equivalent to creating a new relinearization key (using [`BGVCiphertextParams::gen_rk()`])
860    /// over `Cnew` for the secret key `mod_switch_down_sk(Cnew, Cold, drop_moduli, sk)`, but does not require
861    /// access to `sk`.
862    /// 
863    #[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    ///
885    /// Modulus-switches a Galois key in a way compatible with modulus-switching ciphertexts.
886    /// 
887    /// This is equivalent to creating a new Galois key (using [`BGVCiphertextParams::gen_gk()`])
888    /// over `Cnew` for the secret key `mod_switch_down_sk(Cnew, Cold, drop_moduli, sk)`, but does not require
889    /// access to `sk`.
890    /// 
891    #[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    ///
897    /// Internal function to compute how the implicit scale of a ciphertext changes
898    /// once we modulus-switch it.
899    /// 
900    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    ///
911    /// Modulus-switches a ciphertext.
912    /// 
913    /// More concretely, we require that `Cold` is the ring `R/qR` and `Cnew` is the ring `R/q'R`,
914    /// where the RNS base for `q'`is derived from the RNS base of `q` by removing the RNS factors
915    /// whose indices are mentioned in `drop_moduli`. Given a ciphertext `ct` over `R/qR`, this function
916    /// then computes a ciphertext encrypting the same message over `R/q'R` (w.r.t. the secret key
917    /// `sk mod q'`, which can be accessed via [`BGVCiphertextParams::mod_switch_down_sk()`]).
918    /// 
919    #[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                // this logic is slightly complicated, since we want to avoid using `perform_rns_op()`;
936                // in particular, we only need to convert a part of `x` into coefficient/small-basis representation,
937                // while just using `perform_rns_op()` would convert all of `x`.
938                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                // this is the "correction", subtracting it will make `x` divisible by the moduli to drop
941                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                // now subtract `delta` and scale by the moduli to drop - since `x - delta` is divisible by those,
945                // this is actually a rescaling and not only a division in `Z/qZ`
946                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    ///
966    /// Generates a Galois key, usable for homomorphically applying Galois automorphisms.
967    /// 
968    /// The special modulus used for the Galois key consists of the last 
969    /// [`KeySwitchKeyParams::special_modulus_factor_count`] rns factors of `C`.
970    /// 
971    #[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    ///
979    /// Converts an encrypted value `m` w.r.t. a plaintext modulus `t` to an encryption of `t' m / t` w.r.t.
980    /// a plaintext modulus `t'`. This requires that `t' m / t` is an integral ring element (i.e. `t` divides
981    /// `t' m`), otherwise this function will cause immediate noise overflow.
982    /// 
983    #[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    ///
1002    /// Creates an encryption of the secret key.
1003    /// 
1004    /// Note that this does not require access to the secret key.
1005    /// 
1006    #[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    ///
1016    /// Modulus-switches from `R/qR` to `R/t'R`, where the latter one is given as a plaintext ring `target`.
1017    /// In particular, this is necessary during bootstrapping.
1018    /// 
1019    /// As opposed to BFV however, the modulus `t'` of `target` must be coprime with the
1020    /// current plaintext modulus `t`.
1021    /// 
1022    #[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}