he_ring/bfv/
mod.rs

1#![allow(non_snake_case)]
2#![allow(non_upper_case_globals)]
3
4use std::alloc::Allocator;
5use std::alloc::Global;
6use std::marker::PhantomData;
7use std::sync::Arc;
8use std::ops::Range;
9use std::fmt::Display;
10
11use feanor_math::algorithms::eea::signed_lcm;
12use feanor_math::algorithms::int_factor::is_prime_power;
13use feanor_math::primitive_int::StaticRingBase;
14use feanor_math::ring::*;
15use feanor_math::rings::finite::FiniteRing;
16use feanor_math::rings::zn::*;
17use feanor_math::rings::zn::zn_64::*;
18use feanor_math::primitive_int::StaticRing;
19use feanor_math::integer::*;
20use feanor_math::homomorphism::Homomorphism;
21use feanor_math::rings::extension::FreeAlgebraStore;
22use feanor_math::seq::*;
23use feanor_math::ordered::OrderedRingStore;
24use feanor_math::rings::finite::FiniteRingStore;
25use tracing::instrument;
26
27use crate::ciphertext_ring::perform_rns_op;
28use crate::ciphertext_ring::perform_rns_op_to_plaintext_ring;
29use crate::ciphertext_ring::BGFVCiphertextRing;
30use crate::circuit::evaluator::DefaultCircuitEvaluator;
31use crate::circuit::Coefficient;
32use crate::circuit::PlaintextCircuit;
33use crate::cyclotomic::*;
34use crate::gadget_product::GadgetProductLhsOperand;
35use crate::gadget_product::GadgetProductRhsOperand;
36use crate::ntt::{HERingNegacyclicNTT, HERingConvolution};
37use crate::ciphertext_ring::double_rns_managed::*;
38use crate::number_ring::hypercube::isomorphism::*;
39use crate::number_ring::hypercube::structure::HypercubeStructure;
40use crate::number_ring::{largest_prime_leq_congruent_to_one, sample_primes, extend_sampled_primes, HECyclotomicNumberRing, HENumberRing};
41use crate::number_ring::quotient::*;
42use crate::number_ring::pow2_cyclotomic::*;
43use crate::number_ring::odd_cyclotomic::*;
44use crate::ciphertext_ring::single_rns_ring::SingleRNSRingBase;
45use crate::rnsconv::bfv_rescale::{AlmostExactRescaling, AlmostExactRescalingConvert};
46use crate::rnsconv::shared_lift::AlmostExactSharedBaseConversion;
47use crate::DefaultCiphertextAllocator;
48use crate::*;
49
50use rand::{Rng, CryptoRng};
51use rand_distr::StandardNormal;
52
53///
54/// Contains the implementation of bootstrapping for BFV.
55/// 
56pub mod bootstrap;
57
58pub type NumberRing<Params: BFVCiphertextParams> = <Params::CiphertextRing as BGFVCiphertextRing>::NumberRing;
59pub type PlaintextRing<Params: BFVCiphertextParams> = NumberRingQuotient<NumberRing<Params>, Zn, Global>;
60pub type SecretKey<Params: BFVCiphertextParams> = El<CiphertextRing<Params>>;
61pub type KeySwitchKey<'a, Params: BFVCiphertextParams> = (GadgetProductOperand<'a, Params>, GadgetProductOperand<'a, Params>);
62pub type RelinKey<'a, Params: BFVCiphertextParams> = KeySwitchKey<'a, Params>;
63pub type CiphertextRing<Params: BFVCiphertextParams> = RingValue<Params::CiphertextRing>;
64pub type Ciphertext<Params: BFVCiphertextParams> = (El<CiphertextRing<Params>>, El<CiphertextRing<Params>>);
65pub type GadgetProductOperand<'a, Params: BFVCiphertextParams> = GadgetProductRhsOperand<Params::CiphertextRing>;
66
67const ZZbig: BigIntRing = BigIntRing::RING;
68const ZZ: StaticRing<i64> = StaticRing::<i64>::RING;
69
70///
71/// Trait for types that represent an instantiation of BFV.
72/// 
73/// For example, the implementation [`Pow2BFV`] stores
74///  - the binary logarithm `log(N)` of the ring degree `N`
75///  - the length of the ciphertext modulus `q`
76///  - the type of ciphertext ring to be used - in this case [`ManagedDoubleRNSRing`] with 
77///    [`Pow2CyclotomicNumberRing`]
78/// 
79/// In particular, we consider the parameters for the ciphertext ring to be part
80/// of the instantiation of BFV, but not other information (like plaintext modulus).
81/// The reason is that some applications (most notably bootstrapping)
82/// consider the "same" BFV instantiation with different plaintext moduli - 
83/// since a BFV encryption of an element of `R/tR` is always also a valid BFV
84/// encryption of the derived element in `R/t'R`, for every `t'` with `t | t'`. 
85/// Similarly, it might make sense to consider situations with multiple secret
86/// keys or ciphertexts that are generated according to different parameters.
87/// 
88/// Most functionality of BFV is provided using default functions, but can be
89/// overloaded in case a specific ciphertext ring type allows for a more efficient
90/// implementation.
91/// 
92/// ## Combining different ring implementations, or why all BFV functions are associated
93/// 
94/// I'm not yet completely sure what is the best way to handle this.
95/// 
96/// Currently, each implementor of [`BFVCiphertextParams`] fixes the type of the
97/// rings to be used, and defines all BFV functions (in terms of these ring
98/// types) as associated functions. The advantage is obviously that certain
99/// [`BFVCiphertextParams`] can overwrite the default implementations, and perform
100/// BFV operations in a way that uses the rings in the most efficient way.
101/// 
102/// The disadvantage is also clear: We cannot (easily) mix rings or BFV
103/// objects created w.r.t. different [`BFVCiphertextParams`] implementations. For example,
104/// if we want to use a ciphertext w.r.t. (say) [`CompositeBFV`] in a function
105/// of [`CompositeSingleRNSBFV`], an explicit conversion is necessary. In this
106/// case, this could for example be achieved by using the isomorphism between
107/// these rings, as given by [`feanor_math::ring::RingStore::can_iso()`];
108///  
109pub trait BFVCiphertextParams {
110    
111    ///
112    /// Implementation of the ciphertext ring to use.
113    /// 
114    type CiphertextRing: BGFVCiphertextRing + CyclotomicRing + FiniteRing;
115
116    ///
117    /// Returns the allowed lengths of the ciphertext modulus.
118    /// 
119    fn ciphertext_modulus_bits(&self) -> Range<usize>;
120
121    ///
122    /// The number ring `R` we work in, i.e. the ciphertext ring is `R/qR` and
123    /// the plaintext ring is `R/tR`.
124    /// 
125    fn number_ring(&self) -> NumberRing<Self>;
126
127    ///
128    /// Creates the ciphertext ring `R/qR` and the extended-modulus ciphertext ring
129    /// `R/qq'R` that is necessary for homomorphic multiplication.
130    /// 
131    fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>);
132
133    ///
134    /// Creates the plaintext ring `R/tR` for the given plaintext modulus `t`.
135    /// 
136    #[instrument(skip_all)]
137    fn create_plaintext_ring(&self, modulus: i64) -> PlaintextRing<Self> {
138        NumberRingQuotientBase::new(self.number_ring(), Zn::new(modulus as u64))
139    }
140
141    ///
142    /// Generates a secret key, using the randomness of the given rng.
143    /// 
144    /// If `hwt` is set, the secret will be a random ring element with
145    /// exactly `hwt` entries (w.r.t. coefficient basis) in `{-1, 1}`, 
146    /// and the others as `0`. If `hwt` is not set, the secret will be
147    /// a ring element whose coefficient basis coefficients are drawn
148    /// uniformly at random from `{-1, 0, 1}`.
149    /// 
150    /// If you need another kind of secret, consider creating the ring
151    /// element yourself using `C.from_canonical_basis()`.
152    /// 
153    #[instrument(skip_all)]
154    fn gen_sk<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, hwt: Option<usize>) -> SecretKey<Self> {
155        assert!(hwt.is_none() || hwt.unwrap() * 3 <= C.rank() * 2, "it does not make sense to take more than 2/3 of secret key entries in {{-1, 1}}");
156        if let Some(hwt) = hwt {
157            let mut result_data = (0..C.rank()).map(|_| 0).collect::<Vec<_>>();
158            for _ in 0..hwt {
159                let mut i = rng.next_u32() as usize % C.rank();
160                while result_data[i] != 0 {
161                    i = rng.next_u32() as usize % C.rank();
162                }
163                result_data[i] = (rng.next_u32() % 2) as i32 * 2 - 1;
164            }
165            let result = C.from_canonical_basis(result_data.into_iter().map(|c| C.base_ring().int_hom().map(c)));
166            return result;
167        } else {
168            let result = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.next_u32() % 3) as i32 - 1)));
169            return result;
170        }
171    }
172    
173    ///
174    /// Generates a new encryption of zero using the secret key and the randomness of the given rng.
175    /// 
176    #[instrument(skip_all)]
177    fn enc_sym_zero<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
178        let a = C.random_element(|| rng.next_u64());
179        let mut b = C.negate(C.mul_ref(&a, &sk));
180        let e = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.sample::<f64, _>(StandardNormal) * 3.2).round() as i32)));
181        C.add_assign(&mut b, e);
182        return (b, a);
183    }
184    
185    ///
186    /// Creates a transparent encryption of zero, i.e. a noiseless encryption that does not hide
187    /// the encrypted value - everyone can read it, even without access to the secret key.
188    /// 
189    /// Often used to initialize an accumulator (or similar) during algorithms. 
190    /// 
191    #[instrument(skip_all)]
192    fn transparent_zero(C: &CiphertextRing<Self>) -> Ciphertext<Self> {
193        (C.zero(), C.zero())
194    }
195
196    ///
197    /// Encrypts the given value, using the randomness of the given rng.
198    /// 
199    #[instrument(skip_all)]
200    fn enc_sym<R: Rng + CryptoRng>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, rng: R, m: &El<PlaintextRing<Self>>, sk: &SecretKey<Self>) -> Ciphertext<Self> {
201        Self::hom_add_plain(P, C, m, Self::enc_sym_zero(C, rng, sk))
202    }
203
204    ///
205    /// Creates an encryption of the secret key - this is always easily possible in BFV.
206    /// 
207    #[instrument(skip_all)]
208    fn enc_sk(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>) -> Ciphertext<Self> {
209        let Delta = ZZbig.rounded_div(
210            ZZbig.clone_el(C.base_ring().modulus()), 
211            &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
212        );
213        (C.zero(), C.inclusion().map(C.base_ring().coerce(&ZZbig, Delta)))
214    }
215    
216    ///
217    /// Given `q/t m + e`, removes the noise term `e`, thus returns `q/t m`.
218    /// Used during [`BFVCiphertextParams::dec()`] and [`BFVCiphertextParams::noise_budget()`].
219    /// 
220    #[instrument(skip_all)]
221    fn remove_noise(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, c: &El<CiphertextRing<Self>>) -> El<PlaintextRing<Self>> {
222        let coefficients = C.wrt_canonical_basis(c);
223        let Delta = ZZbig.rounded_div(
224            ZZbig.clone_el(C.base_ring().modulus()), 
225            &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
226        );
227        let modulo = P.base_ring().can_hom(&ZZbig).unwrap();
228        return P.from_canonical_basis((0..coefficients.len()).map(|i| modulo.map(ZZbig.rounded_div(C.base_ring().smallest_lift(coefficients.at(i)), &Delta))));
229    }
230    
231    ///
232    /// Decrypts a given ciphertext.
233    /// 
234    /// This function does perform any semantic checks. In particular, it is up to the
235    /// caller to ensure that the ciphertext is defined over the given ring, and is a valid
236    /// BFV encryption w.r.t. the given plaintext modulus.
237    /// 
238    #[instrument(skip_all)]
239    fn dec(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>, sk: &SecretKey<Self>) -> El<PlaintextRing<Self>> {
240        let (c0, c1) = ct;
241        let noisy_m = C.add(c0, C.mul_ref_snd(c1, sk));
242        return Self::remove_noise(P, C, &noisy_m);
243    }
244    
245    ///
246    /// Decrypts a given ciphertext and prints its value to stdout.
247    /// Designed for debugging purposes.
248    /// 
249    #[instrument(skip_all)]
250    fn dec_println(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) {
251        let m = Self::dec(P, C, Self::clone_ct(C, ct), sk);
252        println!("ciphertext (noise budget: {}):", Self::noise_budget(P, C, ct, sk));
253        P.println(&m);
254        println!();
255    }
256    
257    ///
258    /// Decrypts a given ciphertext and prints the values in its slots to stdout.
259    /// Designed for debugging purposes.
260    /// 
261    #[instrument(skip_all)]
262    fn dec_println_slots(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) {
263        let (p, _e) = is_prime_power(ZZ, P.base_ring().modulus()).unwrap();
264        let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(P.n() as u64), p);
265        let H = HypercubeIsomorphism::new::<false>(P, hypercube);
266        let m = Self::dec(P, C, Self::clone_ct(C, ct), sk);
267        println!("ciphertext (noise budget: {}):", Self::noise_budget(P, C, ct, sk));
268        for a in H.get_slot_values(&m) {
269            H.slot_ring().println(&a);
270        }
271        println!();
272    }
273    
274    ///
275    /// Computes an encryption of the sum of two encrypted messages.
276    /// 
277    /// This function does perform any semantic checks. In particular, it is up to the
278    /// caller to ensure that the ciphertexts are defined over the given ring, and are
279    /// BFV encryptions w.r.t. compatible plaintext moduli.
280    /// 
281    #[instrument(skip_all)]
282    fn hom_add(C: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: &Ciphertext<Self>) -> Ciphertext<Self> {
283        let (lhs0, lhs1) = lhs;
284        let (rhs0, rhs1) = rhs;
285        return (C.add_ref(&lhs0, &rhs0), C.add_ref(&lhs1, &rhs1));
286    }
287    
288    ///
289    /// Computes an encryption of the difference of two encrypted messages.
290    /// 
291    /// This function does perform any semantic checks. In particular, it is up to the
292    /// caller to ensure that the ciphertexts are defined over the given ring, and are
293    /// BFV encryptions w.r.t. compatible plaintext moduli.
294    /// 
295    #[instrument(skip_all)]
296    fn hom_sub(C: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: &Ciphertext<Self>) -> Ciphertext<Self> {
297        let (lhs0, lhs1) = lhs;
298        let (rhs0, rhs1) = rhs;
299        return (C.sub_ref(&lhs0, rhs0), C.sub_ref(&lhs1, rhs1));
300    }
301    
302    ///
303    /// Copies a ciphertext.
304    /// 
305    #[instrument(skip_all)]
306    fn clone_ct(C: &CiphertextRing<Self>, ct: &Ciphertext<Self>) -> Ciphertext<Self> {
307        (C.clone_el(&ct.0), C.clone_el(&ct.1))
308    }
309    
310    ///
311    /// Computes an encryption of the sum of an encrypted message and a plaintext.
312    /// 
313    /// This function does perform any semantic checks. In particular, it is up to the
314    /// caller to ensure that the ciphertext is defined over the given ring, and is
315    /// BFV encryption w.r.t. the given plaintext modulus.
316    /// 
317    #[instrument(skip_all)]
318    fn hom_add_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
319        let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
320        let mut m = C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
321        let Delta = C.base_ring().coerce(&ZZbig, ZZbig.rounded_div(
322            ZZbig.clone_el(C.base_ring().modulus()), 
323            &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
324        ));
325        C.inclusion().mul_assign_ref_map(&mut m, &Delta);
326        return (C.add(ct.0, m), ct.1);
327    }
328    
329    ///
330    /// Computes an encryption of the product of an encrypted message and a plaintext.
331    /// 
332    /// This function does perform any semantic checks. In particular, it is up to the
333    /// caller to ensure that the ciphertext is defined over the given ring, and is
334    /// BFV encryption w.r.t. the given plaintext modulus.
335    /// 
336    #[instrument(skip_all)]
337    fn hom_mul_plain(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: &El<PlaintextRing<Self>>, ct: Ciphertext<Self>) -> Ciphertext<Self> {
338        let ZZ_to_Zq = C.base_ring().can_hom(P.base_ring().integer_ring()).unwrap();
339        let m = C.from_canonical_basis(P.wrt_canonical_basis(m).iter().map(|c| ZZ_to_Zq.map(P.base_ring().smallest_lift(c))));
340        let (c0, c1) = ct;
341        return (C.mul_ref_snd(c0, &m), C.mul(c1, m));
342    }
343    
344    ///
345    /// Computes an encryption of the product of an encrypted message and an integer plaintext.
346    /// 
347    /// This function does perform any semantic checks. In particular, it is up to the
348    /// caller to ensure that the ciphertext is defined over the given ring, and is
349    /// BFV encryption w.r.t. the given plaintext modulus.
350    /// 
351    #[instrument(skip_all)]
352    fn hom_mul_plain_i64(_P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, m: i64, ct: Ciphertext<Self>) -> Ciphertext<Self> {
353        (C.int_hom().mul_map(ct.0, m as i32), C.int_hom().mul_map(ct.1, m as i32))
354    }
355    
356    ///
357    /// Computes the "noise budget" of a given ciphertext.
358    /// 
359    /// Concretely, the noise budget is `log(q/(t|e|))`, where `t` is the plaintext modulus
360    /// and `|e|` is the `l_inf`-norm of the noise term. This will decrease during homomorphic
361    /// operations, and if it reaches zero, decryption may yield incorrect results.
362    /// 
363    #[instrument(skip_all)]
364    fn noise_budget(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: &Ciphertext<Self>, sk: &SecretKey<Self>) -> usize {
365        let (c0, c1) = Self::clone_ct(C, ct);
366        let noisy_m = C.add(c0, C.mul_ref_snd(c1, sk));
367        let coefficients = C.wrt_canonical_basis(&noisy_m);
368        let Delta = ZZbig.rounded_div(
369            ZZbig.clone_el(C.base_ring().modulus()), 
370            &int_cast(*P.base_ring().modulus() as i32, &ZZbig, &StaticRing::<i32>::RING)
371        );
372        let log2_size_of_noise = <_ as Iterator>::max((0..coefficients.len()).map(|i| {
373            let c = C.base_ring().smallest_lift(coefficients.at(i));
374            let size = ZZbig.abs_log2_ceil(&ZZbig.sub_ref_fst(&c, ZZbig.mul_ref_snd(ZZbig.rounded_div(ZZbig.clone_el(&c), &Delta), &Delta)));
375            return size.unwrap_or(0);
376        })).unwrap();
377        return ZZbig.abs_log2_ceil(C.base_ring().modulus()).unwrap().saturating_sub(log2_size_of_noise + P.base_ring().integer_ring().abs_log2_ceil(P.base_ring().modulus()).unwrap() + 1);
378    }
379    
380    ///
381    /// Generates a relinearization key, necessary to compute homomorphic multiplications.
382    /// 
383    /// The parameter `digits` refers to the number of "digits" to use for the gadget product
384    /// during relinearization. More concretely, when performing relinearization, the ciphertext
385    /// will be decomposed into multiple small parts, which are then multiplied with the components
386    /// of the relinearization key. Thus, a larger value for `digits` will result in lower (additive)
387    /// noise growth during relinearization, at the cost of higher performance.
388    /// 
389    #[instrument(skip_all)]
390    fn gen_rk<'a, R: Rng + CryptoRng>(C: &'a CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>, digits: usize) -> RelinKey<'a, Self>
391        where Self: 'a
392    {
393        Self::gen_switch_key(C, rng, &C.pow(C.clone_el(sk), 2), sk, digits)
394    }
395    
396    ///
397    /// Computes an encryption of the product of two encrypted messages.
398    /// 
399    /// This function does perform any semantic checks. In particular, it is up to the
400    /// caller to ensure that the ciphertexts are defined over the given ring, and are
401    /// BFV encryptions w.r.t. the given plaintext modulus.
402    /// 
403    #[instrument(skip_all)]
404    fn hom_mul<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>, lhs: Ciphertext<Self>, rhs: Ciphertext<Self>, rk: &RelinKey<'a, Self>) -> Ciphertext<Self>
405        where Self: 'a
406    {
407        let (c00, c01) = lhs;
408        let (c10, c11) = rhs;
409
410        let lift_to_Cmul = Self::create_lift_to_Cmul(C, Cmul);
411        let lift = |c| perform_rns_op(Cmul.get_ring(), C.get_ring(), &c, &lift_to_Cmul);
412        let c00_lifted = lift(c00);
413        let c01_lifted = lift(c01);
414        let c10_lifted = lift(c10);
415        let c11_lifted = lift(c11);
416
417        let [lifted0, lifted1, lifted2] = Cmul.get_ring().two_by_two_convolution([&c00_lifted, &c01_lifted], [&c10_lifted, &c11_lifted]);
418
419        let scale_down_to_C = Self::create_scale_down_to_C(P, C, Cmul);
420        let scale_down = |c: El<CiphertextRing<Self>>| perform_rns_op(C.get_ring(), Cmul.get_ring(), &c, &scale_down_to_C);
421        let res0 = scale_down(lifted0);
422        let res1 = scale_down(lifted1);
423        let res2 = scale_down(lifted2);
424
425        let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &res2, rk.0.gadget_vector_digits());
426        let (s0, s1) = rk;
427        return (C.add_ref(&res0, &op.gadget_product(s0, C.get_ring())), C.add_ref(&res1, &op.gadget_product(s1, C.get_ring())));
428        
429    }
430    
431    ///
432    /// Computes an encryption of the square of an encrypted messages.
433    /// 
434    /// This function does perform any semantic checks. In particular, it is up to the
435    /// caller to ensure that the ciphertexts are defined over the given ring, and are
436    /// BFV encryptions w.r.t. the given plaintext modulus.
437    /// 
438    #[instrument(skip_all)]
439    fn hom_square<'a>(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>, val: Ciphertext<Self>, rk: &RelinKey<'a, Self>) -> Ciphertext<Self>
440        where Self: 'a
441    {
442        let (c0, c1) = val;
443
444        let lift_to_Cmul = Self::create_lift_to_Cmul(C, Cmul);
445        let lift = |c| perform_rns_op(Cmul.get_ring(), C.get_ring(), &c, &lift_to_Cmul);
446        let c0_lifted = lift(c0);
447        let c1_lifted = lift(c1);
448
449        let [lifted0, lifted1, lifted2] = Cmul.get_ring().two_by_two_convolution([&c0_lifted, &c1_lifted], [&c0_lifted, &c1_lifted]);
450
451        let scale_down_to_C = Self::create_scale_down_to_C(P, C, Cmul);
452        let scale_down = |c: El<CiphertextRing<Self>>| perform_rns_op(C.get_ring(), Cmul.get_ring(), &c, &scale_down_to_C);
453        let res0 = scale_down(lifted0);
454        let res1 = scale_down(lifted1);
455        let res2 = scale_down(lifted2);
456
457        let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &res2, rk.0.gadget_vector_digits());
458        let (s0, s1) = rk;
459        return (C.add_ref(&res0, &op.gadget_product(s0, C.get_ring())), C.add_ref(&res1, &op.gadget_product(s1, C.get_ring())));
460        
461    }
462    
463    ///
464    /// Generates a key-switch key. 
465    /// 
466    /// In particular, this is used to generate relinearization keys (via [`BFVCiphertextParams::gen_rk()`])
467    /// or Galois keys (via [`BFVCiphertextParams::gen_gk()`]).
468    /// 
469    /// The parameter `digits` refers to the number of "digits" to use for the gadget product
470    /// during key-switching. More concretely, when performing key-switching, the ciphertext
471    /// will be decomposed into multiple small parts, which are then multiplied with the components
472    /// of the key-switching key. Thus, a larger value for `digits` will result in lower (additive)
473    /// noise growth during key-switching, at the cost of higher performance.
474    /// 
475    #[instrument(skip_all)]
476    fn gen_switch_key<'a, R: Rng + CryptoRng>(C: &'a CiphertextRing<Self>, mut rng: R, old_sk: &SecretKey<Self>, new_sk: &SecretKey<Self>, digits: usize) -> KeySwitchKey<'a, Self>
477        where Self: 'a
478    {
479        let mut res0 = GadgetProductRhsOperand::new(C.get_ring(), digits);
480        let mut res1 = GadgetProductRhsOperand::new(C.get_ring(), digits);
481        for digit_i in 0..digits {
482            let (c0, c1) = Self::enc_sym_zero(C, &mut rng, new_sk);
483            let digit_range = res0.gadget_vector_digits().at(digit_i).clone();
484            let factor = C.base_ring().get_ring().from_congruence((0..C.base_ring().len()).map(|i2| {
485                let Fp = C.base_ring().at(i2);
486                if digit_range.contains(&i2) { Fp.one() } else { Fp.zero() } 
487            }));
488            let mut payload = C.clone_el(&old_sk);
489            C.inclusion().mul_assign_ref_map(&mut payload, &factor);
490            C.add_assign_ref(&mut payload, &c0);
491            res0.set_rns_factor(C.get_ring(), digit_i, payload);
492            res1.set_rns_factor(C.get_ring(), digit_i, c1);
493        }
494        return (res0, res1);
495    }
496    
497    ///
498    /// Using a key-switch key, computes an encryption encrypting the same message as the given ciphertext
499    /// under a different secret key.
500    /// 
501    #[instrument(skip_all)]
502    fn key_switch<'a>(C: &CiphertextRing<Self>, ct: Ciphertext<Self>, switch_key: &KeySwitchKey<'a, Self>) -> Ciphertext<Self>
503        where Self: 'a
504    {
505        let (c0, c1) = ct;
506        let (s0, s1) = switch_key;
507        let op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &c1, switch_key.0.gadget_vector_digits());
508        return (
509            C.add_ref_snd(c0, &op.gadget_product(s0, C.get_ring())),
510            op.gadget_product(s1, C.get_ring())
511        );
512    }
513    
514    ///
515    /// Modulus-switches from `R/qR` to `R/tR`, where the latter one is given as a plaintext ring.
516    /// In particular, this is necessary during bootstrapping.
517    /// 
518    #[instrument(skip_all)]
519    fn mod_switch_to_plaintext(target: &PlaintextRing<Self>, C: &CiphertextRing<Self>, ct: Ciphertext<Self>) -> (El<PlaintextRing<Self>>, El<PlaintextRing<Self>>) {
520        let mod_switch = AlmostExactRescaling::new_with(
521            C.base_ring().as_iter().map(|Zp| *Zp).collect(),
522            vec![*target.base_ring()],
523            (0..C.base_ring().len()).collect(),
524            Global
525        );
526        let (c0, c1) = ct;
527        return (
528            perform_rns_op_to_plaintext_ring(target, C.get_ring(), &c0, &mod_switch),
529            perform_rns_op_to_plaintext_ring(target, C.get_ring(), &c1, &mod_switch)
530        );
531    }
532    
533    ///
534    /// Generates a Galois key, usable for homomorphically applying Galois automorphisms.
535    /// 
536    /// The parameter `digits` refers to the number of "digits" to use for the gadget product
537    /// during key-switching. More concretely, when performing key-switching, the ciphertext
538    /// will be decomposed into multiple small parts, which are then multiplied with the components
539    /// of the key-switching key. Thus, a larger value for `digits` will result in lower (additive)
540    /// noise growth during key-switching, at the cost of higher performance.
541    /// 
542    #[instrument(skip_all)]
543    fn gen_gk<'a, R: Rng + CryptoRng>(C: &'a CiphertextRing<Self>, rng: R, sk: &SecretKey<Self>, g: CyclotomicGaloisGroupEl, digits: usize) -> KeySwitchKey<'a, Self>
544        where Self: 'a
545    {
546        Self::gen_switch_key(C, rng, &C.get_ring().apply_galois_action(sk, g), sk, digits)
547    }
548    
549    ///
550    /// Computes an encryption of `sigma(x)`, where `x` is the message encrypted by the given ciphertext
551    /// and `sigma` is the given Galois automorphism.
552    /// 
553    #[instrument(skip_all)]
554    fn hom_galois<'a>(C: &CiphertextRing<Self>, ct: Ciphertext<Self>, g: CyclotomicGaloisGroupEl, gk: &KeySwitchKey<'a, Self>) -> Ciphertext<Self>
555        where Self: 'a
556    {
557        Self::key_switch(C, (
558            C.get_ring().apply_galois_action(&ct.0, g),
559            C.get_ring().apply_galois_action(&ct.1, g)
560        ), gk)
561    }
562    
563    ///
564    /// Homomorphically applies multiple Galois automorphisms at once.
565    /// Functionally, this is equivalent to calling [`BFVCiphertextParams::hom_galois()`]
566    /// multiple times, but can be faster.
567    /// 
568    #[instrument(skip_all)]
569    fn hom_galois_many<'a, 'b, V>(C: &CiphertextRing<Self>, ct: Ciphertext<Self>, gs: &[CyclotomicGaloisGroupEl], gks: V) -> Vec<Ciphertext<Self>>
570        where V: VectorFn<&'b KeySwitchKey<'a, Self>>,
571            KeySwitchKey<'a, Self>: 'b,
572            'a: 'b,
573            Self: 'a
574    {
575        let digits = gks.at(0).0.gadget_vector_digits();
576        let has_same_digits = |gk: &GadgetProductRhsOperand<_>| gk.gadget_vector_digits().len() == digits.len() && gk.gadget_vector_digits().iter().zip(digits.iter()).all(|(l, r)| l == r);
577        assert!(gks.iter().all(|gk| has_same_digits(&gk.0) && has_same_digits(&gk.1)));
578        let (c0, c1) = ct;
579        let c1_op = GadgetProductLhsOperand::from_element_with(C.get_ring(), &c1, digits);
580        let c1_op_gs = c1_op.apply_galois_action_many(C.get_ring(), gs);
581        let c0_gs = C.get_ring().apply_galois_action_many(&c0, gs).into_iter();
582        assert_eq!(gks.len(), c1_op_gs.len());
583        assert_eq!(gks.len(), c0_gs.len());
584        return c0_gs.zip(c1_op_gs.iter()).enumerate().map(|(i, (c0_g, c1_g))| {
585            let (s0, s1) = gks.at(i);
586            let r0 = c1_g.gadget_product(s0, C.get_ring());
587            let r1 = c1_g.gadget_product(s1, C.get_ring());
588            return (C.add_ref(&r0, &c0_g), r1);
589        }).collect();
590    }
591
592    fn create_lift_to_Cmul(C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>) -> AlmostExactSharedBaseConversion {
593        AlmostExactSharedBaseConversion::new_with(
594            C.base_ring().as_iter().map(|R| Zn::new(*R.modulus() as u64)).collect::<Vec<_>>(), 
595            Vec::new(),
596            Cmul.base_ring().as_iter().skip(C.base_ring().len()).map(|R| Zn::new(*R.modulus() as u64)).collect::<Vec<_>>(),
597            Global
598        )
599    }
600
601    fn create_scale_down_to_C(P: &PlaintextRing<Self>, C: &CiphertextRing<Self>, Cmul: &CiphertextRing<Self>) -> AlmostExactRescalingConvert {
602        AlmostExactRescalingConvert::new_with(
603            Cmul.base_ring().as_iter().map(|R| Zn::new(*R.modulus() as u64)).collect::<Vec<_>>(), 
604            vec![ Zn::new(*P.base_ring().modulus() as u64) ], 
605            (0..C.base_ring().len()).collect(),
606            Global
607        )
608    }
609}
610
611impl<NumberRing> PlaintextCircuit<NumberRingQuotientBase<NumberRing, Zn>>
612    where NumberRing: HENumberRing
613{
614    #[instrument(skip_all)]
615    pub fn evaluate_bfv<Params>(&self, 
616        P: &PlaintextRing<Params>, 
617        C: &CiphertextRing<Params>, 
618        Cmul: Option<&CiphertextRing<Params>>,
619        inputs: &[Ciphertext<Params>], 
620        rk: Option<&RelinKey<Params>>, 
621        gks: &[(CyclotomicGaloisGroupEl, KeySwitchKey<Params>)], 
622        _key_switches: &mut usize
623    ) -> Vec<Ciphertext<Params>> 
624        where Params: BFVCiphertextParams,
625            Params::CiphertextRing: BGFVCiphertextRing<NumberRing = NumberRing>
626    {
627        assert!(!self.has_multiplication_gates() || Cmul.is_some());
628        assert_eq!(Cmul.is_some(), rk.is_some());
629        let galois_group = C.galois_group();
630        return self.evaluate_generic(
631            inputs,
632            DefaultCircuitEvaluator::new(
633                |lhs, rhs| Params::hom_mul(P, C, Cmul.unwrap(), lhs, rhs, rk.unwrap()),
634                |x| match x {
635                    Coefficient::Zero => Params::transparent_zero(C),
636                    x => Params::hom_add_plain(P, C, &x.clone(P).to_ring_el(P), Params::transparent_zero(C))
637                },
638                |dst, x, ct| Params::hom_add(C, dst, &Params::hom_mul_plain(P, C, &x.clone(P).to_ring_el(P), Params::clone_ct(C, ct))),
639            ).with_square(
640                |x| Params::hom_square(P, C, Cmul.unwrap(), x, rk.unwrap()),
641            ).with_gal(
642                |x, gs| if gs.len() == 1 {
643                    vec![Params::hom_galois(C, x, gs[0], &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, gs[0])).next().unwrap().1)]
644                } else {
645                    Params::hom_galois_many(C, x, gs, gs.as_fn().map_fn(|expected_g| &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, *expected_g)).next().unwrap().1))
646                }
647            )
648        );
649    }
650}
651
652impl PlaintextCircuit<StaticRingBase<i64>> {
653
654    #[instrument(skip_all)]
655    pub fn evaluate_bfv<Params>(&self, 
656        P: &PlaintextRing<Params>, 
657        C: &CiphertextRing<Params>, 
658        Cmul: Option<&CiphertextRing<Params>>,
659        inputs: &[Ciphertext<Params>], 
660        rk: Option<&RelinKey<Params>>, 
661        gks: &[(CyclotomicGaloisGroupEl, KeySwitchKey<Params>)], 
662        _key_switches: &mut usize
663    ) -> Vec<Ciphertext<Params>> 
664        where Params: BFVCiphertextParams
665    {
666        assert!(!self.has_multiplication_gates() || Cmul.is_some());
667        assert_eq!(Cmul.is_some(), rk.is_some());
668        const ZZ: StaticRing<i64> = StaticRing::<i64>::RING;
669        let galois_group = C.galois_group();
670        return self.evaluate_generic(
671            inputs,
672            DefaultCircuitEvaluator::new(
673                |lhs, rhs| Params::hom_mul(P, C, Cmul.unwrap(), lhs, rhs, rk.unwrap()),
674                |x| match x {
675                    Coefficient::Zero => Params::transparent_zero(C),
676                    x => Params::hom_add_plain(P, C, &P.int_hom().map(x.clone(ZZ).to_ring_el(ZZ) as i32), Params::transparent_zero(C))
677                },
678                |dst, x, ct| Params::hom_add(C, dst, &Params::hom_mul_plain_i64(P, C, x.to_ring_el(ZZ), Params::clone_ct(C, ct))),
679            ).with_square(
680                |x| Params::hom_square(P, C, Cmul.unwrap(), x, rk.unwrap()),
681            ).with_gal(
682                |x, gs| if gs.len() == 1 {
683                    vec![Params::hom_galois(C, x, gs[0], &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, gs[0])).next().unwrap().1)]
684                } else {
685                    Params::hom_galois_many(C, x, gs, gs.as_fn().map_fn(|expected_g| &gks.iter().filter(|(g, _)| galois_group.eq_el(*g, *expected_g)).next().unwrap().1))
686                }
687            )
688        );
689    }
690}
691
692///
693/// Instantiation of BFV in power-of-two cyclotomic rings `Z[X]/(X^N + 1)` for `N`
694/// a power of two.
695/// 
696/// For these rings, using a `DoubleRNSRing` as ciphertext ring is always the best
697/// (i.e. fastest) solution.
698/// 
699#[derive(Debug)]
700pub struct Pow2BFV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator, C: Send + Sync + HERingNegacyclicNTT<Zn> = DefaultNegacyclicNTT> {
701    pub log2_q_min: usize,
702    pub log2_q_max: usize,
703    pub log2_N: usize,
704    pub ciphertext_allocator: A,
705    pub negacyclic_ntt: PhantomData<C>
706}
707
708impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> Display for Pow2BFV<A, C> {
709
710    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
711        write!(f, "BFV(n = 2^{}, log2(q) in {}..{})", self.log2_N + 1, self.log2_q_min, self.log2_q_max)
712    }
713}
714
715impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> Clone for Pow2BFV<A, C> {
716
717    fn clone(&self) -> Self {
718        Self {
719            log2_q_min: self.log2_q_min,
720            log2_q_max: self.log2_q_max,
721            log2_N: self.log2_N,
722            ciphertext_allocator: self.ciphertext_allocator.clone(),
723            negacyclic_ntt: PhantomData
724        }
725    }
726}
727
728impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingNegacyclicNTT<Zn>> BFVCiphertextParams for Pow2BFV<A, C> {
729
730    type CiphertextRing = ManagedDoubleRNSRingBase<Pow2CyclotomicNumberRing<C>, A>;
731
732    fn number_ring(&self) -> Pow2CyclotomicNumberRing<C> {
733        Pow2CyclotomicNumberRing::new_with(2 << self.log2_N)
734    }
735
736    fn ciphertext_modulus_bits(&self) -> Range<usize> {
737        assert!(self.log2_q_min < self.log2_q_max);
738        self.log2_q_min..self.log2_q_max
739    }
740
741    #[instrument(skip_all)]
742    fn enc_sym_zero<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
743        let a = C.random_element(|| rng.next_u64());
744        let mut b = C.negate(C.mul_ref(&a, &sk));
745        let e = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.sample::<f64, _>(StandardNormal) * 3.2).round() as i32)));
746        C.add_assign(&mut b, e);
747        return small_basis_repr::<Self, _, _>(C, (b, a));
748    }
749
750    #[instrument(skip_all)]
751    fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>)  {
752        let log2_q = self.ciphertext_modulus_bits();
753        let number_ring = self.number_ring();
754        let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
755
756        let mut C_rns_base = sample_primes(log2_q.start, log2_q.end, 56, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
757        C_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
758
759        let mut Cmul_rns_base = extend_sampled_primes(&C_rns_base, log2_q.end * 2 + 10, log2_q.end * 2 + 67, 57, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
760        assert!(ZZbig.is_gt(&Cmul_rns_base[Cmul_rns_base.len() - 1], &C_rns_base[C_rns_base.len() - 1]));
761        Cmul_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
762
763        let C_rns_base = zn_rns::Zn::new(C_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>(), ZZbig);
764        let Cmul_rns_base = zn_rns::Zn::new(Cmul_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect(), ZZbig);
765
766        let Cmul = ManagedDoubleRNSRingBase::new_with(
767            number_ring,
768            Cmul_rns_base,
769            self.ciphertext_allocator.clone()
770        );
771
772        let dropped_indices = (0..Cmul.base_ring().len()).filter(|i| C_rns_base.as_iter().all(|Zp| Zp.get_ring() != Cmul.base_ring().at(*i).get_ring())).collect::<Vec<_>>();
773        let C = RingValue::from(Cmul.get_ring().drop_rns_factor(&dropped_indices));
774        debug_assert!(C.base_ring().get_ring() == C_rns_base.get_ring());
775        return (C, Cmul);
776    }
777}
778
779///
780/// Instantiation of BFV over odd, composite cyclotomic rings `Z[X]/(Phi_n(X))`
781/// with `n = n1 n2` and `n2, n2` odd coprime integers. Ciphertexts are represented
782/// in double-RNS form. If single-RNS form is instead requires, use [`CompositeSingleRNSBFV`].
783/// 
784#[derive(Clone, Debug)]
785pub struct CompositeBFV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator> {
786    pub log2_q_min: usize,
787    pub log2_q_max: usize,
788    pub n1: usize,
789    pub n2: usize,
790    pub ciphertext_allocator: A
791}
792
793impl<A: Allocator + Clone + Send + Sync> BFVCiphertextParams for CompositeBFV<A> {
794
795    type CiphertextRing = ManagedDoubleRNSRingBase<CompositeCyclotomicNumberRing, A>;
796
797    fn ciphertext_modulus_bits(&self) -> Range<usize> {
798        self.log2_q_min..self.log2_q_max
799    }
800
801    fn number_ring(&self) -> CompositeCyclotomicNumberRing {
802        CompositeCyclotomicNumberRing::new(self.n1, self.n2)
803    }
804
805    #[instrument(skip_all)]
806    fn enc_sym_zero<R: Rng + CryptoRng>(C: &CiphertextRing<Self>, mut rng: R, sk: &SecretKey<Self>) -> Ciphertext<Self> {
807        let a = C.random_element(|| rng.next_u64());
808        let mut b = C.negate(C.mul_ref(&a, &sk));
809        let e = C.from_canonical_basis((0..C.rank()).map(|_| C.base_ring().int_hom().map((rng.sample::<f64, _>(StandardNormal) * 3.2).round() as i32)));
810        C.add_assign(&mut b, e);
811        return small_basis_repr::<Self, _, _>(C, (b, a));
812    }
813    
814    #[instrument(skip_all)]
815    fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>)  {
816        let log2_q = self.ciphertext_modulus_bits();
817        let number_ring = self.number_ring();
818        let required_root_of_unity = number_ring.mod_p_required_root_of_unity() as i64;
819
820        let mut C_rns_base = sample_primes(log2_q.start, log2_q.end, 56, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
821        C_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
822
823        let mut Cmul_rns_base = extend_sampled_primes(&C_rns_base, log2_q.end * 2, log2_q.end * 2 + 57, 57, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
824        assert!(ZZbig.is_gt(&Cmul_rns_base[Cmul_rns_base.len() - 1], &C_rns_base[C_rns_base.len() - 1]));
825        Cmul_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
826
827        let C_rns_base = zn_rns::Zn::new(C_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>(), ZZbig);
828        let Cmul_rns_base = zn_rns::Zn::new(Cmul_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect(), ZZbig);
829
830        let Cmul = ManagedDoubleRNSRingBase::new_with(
831            number_ring,
832            Cmul_rns_base,
833            self.ciphertext_allocator.clone()
834        );
835
836        let dropped_indices = (0..Cmul.base_ring().len()).filter(|i| C_rns_base.as_iter().all(|Zp| Zp.get_ring() != Cmul.base_ring().at(*i).get_ring())).collect::<Vec<_>>();
837        let C = RingValue::from(Cmul.get_ring().drop_rns_factor(&dropped_indices));
838        debug_assert!(C.base_ring().get_ring() == C_rns_base.get_ring());
839        return (C, Cmul);
840    }
841}
842
843///
844/// Instantiation of BFV over odd, composite cyclotomic rings `Z[X]/(Phi_n(X))`
845/// with `n = n1 n2` and `n2, n2` odd coprime integers. Ciphertexts are represented
846/// in single-RNS form. If double-RNS form is instead requires, use [`CompositeBFV`].
847/// 
848/// This takes a type `C` as last generic argument, which is the type of the convolution
849/// algorithm to use to instantiate the ciphertext ring. This has a major impact on 
850/// performance.
851/// 
852#[derive(Clone, Debug)]
853pub struct CompositeSingleRNSBFV<A: Allocator + Clone + Send + Sync = DefaultCiphertextAllocator, C: Send + Sync + HERingConvolution<Zn> = DefaultConvolution> {
854    pub log2_q_min: usize,
855    pub log2_q_max: usize,
856    pub n1: usize,
857    pub n2: usize,
858    pub ciphertext_allocator: A,
859    pub convolution: PhantomData<C>
860}
861
862impl<A: Allocator + Clone + Send + Sync, C: Send + Sync + HERingConvolution<Zn>> BFVCiphertextParams for CompositeSingleRNSBFV<A, C> {
863
864    type CiphertextRing = SingleRNSRingBase<CompositeCyclotomicNumberRing, A, C>;
865
866    fn ciphertext_modulus_bits(&self) -> Range<usize> {
867        self.log2_q_min..self.log2_q_max
868    }
869
870    fn number_ring(&self) -> CompositeCyclotomicNumberRing {
871        CompositeCyclotomicNumberRing::new(self.n1, self.n2)
872    }
873
874    #[instrument(skip_all)]
875    fn create_ciphertext_rings(&self) -> (CiphertextRing<Self>, CiphertextRing<Self>) {
876        let log2_q = self.ciphertext_modulus_bits();
877        let number_ring = self.number_ring();
878        let required_root_of_unity = signed_lcm(
879            number_ring.mod_p_required_root_of_unity() as i64,
880            1 << ZZ.abs_log2_ceil(&(number_ring.n() as i64 * 2)).unwrap(),
881            ZZ
882        );
883
884        let mut C_rns_base = sample_primes(log2_q.start, log2_q.end, 56, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
885        C_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
886
887        let mut Cmul_rns_base = extend_sampled_primes(&C_rns_base, log2_q.end * 2, log2_q.end * 2 + 57, 57, |bound| largest_prime_leq_congruent_to_one(int_cast(bound, ZZ, ZZbig), required_root_of_unity).map(|p| int_cast(p, ZZbig, ZZ))).unwrap();
888        assert!(ZZbig.is_gt(&Cmul_rns_base[Cmul_rns_base.len() - 1], &C_rns_base[C_rns_base.len() - 1]));
889        Cmul_rns_base.sort_unstable_by(|l, r| ZZbig.cmp(l, r));
890
891        let max_log2_n = 1 + ZZ.abs_log2_ceil(&((self.n1 * self.n2) as i64)).unwrap();
892        let C_rns_base = C_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>();
893        let Cmul_rns_base = Cmul_rns_base.iter().map(|p| Zn::new(int_cast(ZZbig.clone_el(p), ZZ, ZZbig) as u64)).collect::<Vec<_>>();
894
895        let C_convolutions = C_rns_base.iter().map(|Zp| C::new(*Zp, max_log2_n)).map(Arc::new).collect::<Vec<_>>();
896        let Cmul_convolutions = Cmul_rns_base.iter().map(|Zp| match C_rns_base.iter().enumerate().filter(|(_, C_Zp)| C_Zp.get_ring() == Zp.get_ring()).next() {
897            Some((i, _)) => C_convolutions.at(i).clone(),
898            None => Arc::new(C::new(*Zp, max_log2_n))
899        }).collect();
900
901        let C = SingleRNSRingBase::new_with(
902            self.number_ring(),
903            zn_rns::Zn::new(C_rns_base.clone(), ZZbig),
904            self.ciphertext_allocator.clone(),
905            C_convolutions
906        );
907        let Cmul = SingleRNSRingBase::new_with(
908            number_ring,
909            zn_rns::Zn::new(Cmul_rns_base.clone(), ZZbig),
910            self.ciphertext_allocator.clone(),
911            Cmul_convolutions
912        );
913        return (C, Cmul);
914    }
915}
916
917pub fn small_basis_repr<Params, NumberRing, A>(C: &CiphertextRing<Params>, ct: Ciphertext<Params>) -> Ciphertext<Params>
918    where Params: BFVCiphertextParams<CiphertextRing = ManagedDoubleRNSRingBase<NumberRing, A>>,
919        NumberRing: HECyclotomicNumberRing,
920        A: Allocator + Clone
921{
922    return (
923        C.get_ring().from_small_basis_repr(C.get_ring().to_small_basis(&ct.0).map(|x| C.get_ring().unmanaged_ring().get_ring().clone_el_non_fft(x)).unwrap_or_else(|| C.get_ring().unmanaged_ring().get_ring().zero_non_fft())), 
924        C.get_ring().from_small_basis_repr(C.get_ring().to_small_basis(&ct.1).map(|x| C.get_ring().unmanaged_ring().get_ring().clone_el_non_fft(x)).unwrap_or_else(|| C.get_ring().unmanaged_ring().get_ring().zero_non_fft())), 
925    );
926}
927
928#[cfg(test)]
929use tracing_subscriber::prelude::*;
930#[cfg(test)]
931use feanor_mempool::dynsize::DynLayoutMempool;
932#[cfg(test)]
933use feanor_mempool::AllocArc;
934#[cfg(test)]
935use feanor_math::assert_el_eq;
936#[cfg(test)]
937use std::ptr::Alignment;
938#[cfg(test)]
939use std::fmt::Debug;
940#[cfg(test)]
941use crate::log_time;
942#[cfg(test)]
943use rand::thread_rng;
944
945#[test]
946fn test_pow2_bfv_enc_dec() {
947    let mut rng = thread_rng();
948    
949    let params = Pow2BFV {
950        log2_q_min: 500,
951        log2_q_max: 520,
952        log2_N: 7,
953        ciphertext_allocator: DefaultCiphertextAllocator::default(),
954        negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
955    };
956    let t = 257;
957    
958    let P = params.create_plaintext_ring(t);
959    let (C, _Cmul) = params.create_ciphertext_rings();
960
961    let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
962
963    let input = P.int_hom().map(2);
964    let ctxt = Pow2BFV::enc_sym(&P, &C, &mut rng, &input, &sk);
965    let output = Pow2BFV::dec(&P, &C, Pow2BFV::clone_ct(&C, &ctxt), &sk);
966    assert_el_eq!(&P, input, output);
967}
968
969#[test]
970fn test_pow2_bfv_hom_galois() {
971    let mut rng = thread_rng();
972    
973    let params = Pow2BFV {
974        log2_q_min: 500,
975        log2_q_max: 520,
976        log2_N: 7,
977        ciphertext_allocator: DefaultCiphertextAllocator::default(),
978        negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
979    };
980    let t = 3;
981    let digits = 3;
982    
983    let P = params.create_plaintext_ring(t);
984    let (C, _Cmul) = params.create_ciphertext_rings();    
985    let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
986    let gk = Pow2BFV::gen_gk(&C, &mut rng, &sk, P.galois_group().from_representative(3), digits);
987    
988    let input = P.canonical_gen();
989    let ctxt = Pow2BFV::enc_sym(&P, &C, &mut rng, &input, &sk);
990    let result_ctxt = Pow2BFV::hom_galois(&C, ctxt, P.galois_group().from_representative(3), &gk);
991    let result = Pow2BFV::dec(&P, &C, result_ctxt, &sk);
992
993    assert_el_eq!(&P, &P.pow(P.canonical_gen(), 3), &result);
994}
995
996#[test]
997fn test_pow2_bfv_mul() {
998    let mut rng = thread_rng();
999    
1000    let params = Pow2BFV {
1001        log2_q_min: 500,
1002        log2_q_max: 520,
1003        log2_N: 10,
1004        ciphertext_allocator: DefaultCiphertextAllocator::default(),
1005        negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1006    };
1007    let t = 257;
1008    let digits = 3;
1009    
1010    let P = params.create_plaintext_ring(t);
1011    let (C, Cmul) = params.create_ciphertext_rings();
1012    let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
1013    let rk = Pow2BFV::gen_rk(&C, &mut rng, &sk, digits);
1014
1015    let input = P.int_hom().map(2);
1016    let ctxt = Pow2BFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1017    let result_ctxt = Pow2BFV::hom_mul(&P, &C, &Cmul, Pow2BFV::clone_ct(&C, &ctxt), Pow2BFV::clone_ct(&C, &ctxt), &rk);
1018    let result = Pow2BFV::dec(&P, &C, result_ctxt, &sk);
1019
1020    assert_el_eq!(&P, &P.int_hom().map(4), &result);
1021}
1022
1023#[test]
1024fn test_composite_bfv_mul() {
1025    let mut rng = thread_rng();
1026    
1027    let params = CompositeBFV {
1028        log2_q_min: 500,
1029        log2_q_max: 520,
1030        n1: 17,
1031        n2: 97,
1032        ciphertext_allocator: DefaultCiphertextAllocator::default()
1033    };
1034    let t = 8;
1035    let digits = 3;
1036    
1037    let P = params.create_plaintext_ring(t);
1038    let (C, Cmul) = params.create_ciphertext_rings();
1039    let sk = CompositeBFV::gen_sk(&C, &mut rng, None);
1040    let rk = CompositeBFV::gen_rk(&C, &mut rng, &sk, digits);
1041
1042    let input = P.int_hom().map(2);
1043    let ctxt = CompositeBFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1044    let result_ctxt = CompositeBFV::hom_mul(&P, &C, &Cmul, CompositeBFV::clone_ct(&C, &ctxt), CompositeBFV::clone_ct(&C, &ctxt), &rk);
1045    let result = CompositeBFV::dec(&P, &C, result_ctxt, &sk);
1046
1047    assert_el_eq!(&P, &P.int_hom().map(4), &result);
1048}
1049
1050#[test]
1051fn test_composite_bfv_hom_galois() {
1052    let mut rng = thread_rng();
1053    
1054    let params = CompositeSingleRNSBFV {
1055        log2_q_min: 500,
1056        log2_q_max: 520,
1057        n1: 7,
1058        n2: 11,
1059        ciphertext_allocator: DefaultCiphertextAllocator::default(),
1060        convolution: PhantomData::<DefaultConvolution>
1061    };
1062    let t = 3;
1063    let digits = 3;
1064    
1065    let P = params.create_plaintext_ring(t);
1066    let (C, _Cmul) = params.create_ciphertext_rings();    
1067    let sk = CompositeSingleRNSBFV::gen_sk(&C, &mut rng, None);
1068    let gk = CompositeSingleRNSBFV::gen_gk(&C, &mut rng, &sk, P.galois_group().from_representative(3), digits);
1069    
1070    let input = P.canonical_gen();
1071    let ctxt = CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1072    let result_ctxt = CompositeSingleRNSBFV::hom_galois(&C, ctxt, P.galois_group().from_representative(3), &gk);
1073    let result = CompositeSingleRNSBFV::dec(&P, &C, result_ctxt, &sk);
1074
1075    assert_el_eq!(&P, &P.pow(P.canonical_gen(), 3), &result);
1076}
1077
1078#[test]
1079fn test_single_rns_composite_bfv_mul() {
1080    let mut rng = thread_rng();
1081    
1082    let params = CompositeSingleRNSBFV {
1083        log2_q_min: 500,
1084        log2_q_max: 520,
1085        n1: 7,
1086        n2: 11,
1087        ciphertext_allocator: DefaultCiphertextAllocator::default(),
1088        convolution: PhantomData::<DefaultConvolution>
1089    };
1090    let t = 3;
1091    let digits = 3;
1092
1093    let P = params.create_plaintext_ring(t);
1094    let (C, Cmul) = params.create_ciphertext_rings();
1095
1096    let sk = CompositeSingleRNSBFV::gen_sk(&C, &mut rng, None);
1097    let rk = CompositeSingleRNSBFV::gen_rk(&C, &mut rng, &sk, digits);
1098
1099    let input = P.int_hom().map(2);
1100    let ctxt = CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &input, &sk);
1101    let result_ctxt = CompositeSingleRNSBFV::hom_mul(&P, &C, &Cmul, CompositeSingleRNSBFV::clone_ct(&C, &ctxt), CompositeSingleRNSBFV::clone_ct(&C, &ctxt), &rk);
1102    let result = CompositeSingleRNSBFV::dec(&P, &C, result_ctxt, &sk);
1103
1104    assert_el_eq!(&P, &P.int_hom().map(4), &result);
1105}
1106
1107#[test]
1108#[ignore]
1109fn measure_time_pow2_bfv() {
1110    let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1111    tracing_subscriber::registry().with(chrome_layer).init();
1112
1113    let mut rng = thread_rng();
1114    
1115    let params = Pow2BFV {
1116        log2_q_min: 790,
1117        log2_q_max: 800,
1118        log2_N: 15,
1119        ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1120        negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
1121    };
1122    let t = 257;
1123    let digits = 3;
1124    
1125    let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1126        params.create_plaintext_ring(t)
1127    );
1128    let (C, Cmul) = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1129        params.create_ciphertext_rings()
1130    );
1131
1132    let sk = log_time::<_, _, true, _>("GenSK", |[]| 
1133        Pow2BFV::gen_sk(&C, &mut rng, None)
1134    );
1135
1136    let m = P.int_hom().map(2);
1137    let ct = log_time::<_, _, true, _>("EncSym", |[]|
1138        Pow2BFV::enc_sym(&P, &C, &mut rng, &m, &sk)
1139    );
1140
1141    let res = log_time::<_, _, true, _>("HomAddPlain", |[]| 
1142        Pow2BFV::hom_add_plain(&P, &C, &m, Pow2BFV::clone_ct(&C, &ct))
1143    );
1144    assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1145
1146    let res = log_time::<_, _, true, _>("HomAdd", |[]| 
1147        Pow2BFV::hom_add(&C, Pow2BFV::clone_ct(&C, &ct), &ct)
1148    );
1149    assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1150
1151    let res = log_time::<_, _, true, _>("HomMulPlain", |[]| 
1152        Pow2BFV::hom_mul_plain(&P, &C, &m, Pow2BFV::clone_ct(&C, &ct))
1153    );
1154    assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1155
1156    let rk = log_time::<_, _, true, _>("GenRK", |[]| 
1157        Pow2BFV::gen_rk(&C, &mut rng, &sk, digits)
1158    );
1159    let ct2 = Pow2BFV::enc_sym(&P, &C, &mut rng, &m, &sk);
1160    let res = log_time::<_, _, true, _>("HomMul", |[]| 
1161        Pow2BFV::hom_mul(&P, &C, &Cmul, ct, ct2, &rk)
1162    );
1163    assert_el_eq!(&P, &P.int_hom().map(4), &Pow2BFV::dec(&P, &C, res, &sk));
1164}
1165
1166#[test]
1167#[ignore]
1168fn measure_time_double_rns_composite_bfv() {
1169    let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1170    tracing_subscriber::registry().with(chrome_layer).init();
1171
1172    let mut rng = thread_rng();
1173    
1174    let params = CompositeBFV {
1175        log2_q_min: 1090,
1176        log2_q_max: 1100,
1177        n1: 127,
1178        n2: 337,
1179        ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1180    };
1181    let t = 4;
1182    let digits = 3;
1183    
1184    let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1185        params.create_plaintext_ring(t)
1186    );
1187    let (C, Cmul) = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1188        params.create_ciphertext_rings()
1189    );
1190
1191    let sk = log_time::<_, _, true, _>("GenSK", |[]| 
1192        CompositeBFV::gen_sk(&C, &mut rng, None)
1193    );
1194    
1195    let m = P.int_hom().map(3);
1196    let ct = log_time::<_, _, true, _>("EncSym", |[]|
1197        CompositeBFV::enc_sym(&P, &C, &mut rng, &m, &sk)
1198    );
1199    assert_el_eq!(&P, &P.int_hom().map(3), &CompositeBFV::dec(&P, &C, CompositeBFV::clone_ct(&C, &ct), &sk));
1200
1201    let res = log_time::<_, _, true, _>("HomAdd", |[]| 
1202        CompositeBFV::hom_add(&C, CompositeBFV::clone_ct(&C, &ct), &ct)
1203    );
1204    assert_el_eq!(&P, &P.int_hom().map(2), &CompositeBFV::dec(&P, &C, res, &sk));
1205
1206    let res = log_time::<_, _, true, _>("HomAddPlain", |[]| 
1207        CompositeBFV::hom_add_plain(&P, &C, &m, CompositeBFV::clone_ct(&C, &ct))
1208    );
1209    assert_el_eq!(&P, &P.int_hom().map(2), &CompositeBFV::dec(&P, &C, res, &sk));
1210
1211    let res = log_time::<_, _, true, _>("HomMulPlain", |[]| 
1212        CompositeBFV::hom_mul_plain(&P, &C, &m, CompositeBFV::clone_ct(&C, &ct))
1213    );
1214    assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBFV::dec(&P, &C, res, &sk));
1215
1216    let rk = log_time::<_, _, true, _>("GenRK", |[]| 
1217        CompositeBFV::gen_rk(&C, &mut rng, &sk, digits)
1218    );
1219    let ct2 = CompositeBFV::enc_sym(&P, &C, &mut rng, &m, &sk);
1220    let res = log_time::<_, _, true, _>("HomMul", |[]| 
1221        CompositeBFV::hom_mul(&P, &C, &Cmul, ct, ct2, &rk)
1222    );
1223    assert_el_eq!(&P, &P.int_hom().map(1), &CompositeBFV::dec(&P, &C, res, &sk));
1224}
1225
1226#[test]
1227#[ignore]
1228fn measure_time_single_rns_composite_bfv() {
1229    let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
1230    tracing_subscriber::registry().with(chrome_layer).init();
1231
1232    let mut rng = thread_rng();
1233    
1234    let params = CompositeSingleRNSBFV {
1235        log2_q_min: 1090,
1236        log2_q_max: 1100,
1237        n1: 127,
1238        n2: 337,
1239        ciphertext_allocator: AllocArc(Arc::new(DynLayoutMempool::<Global>::new(Alignment::of::<u64>()))),
1240        convolution: PhantomData::<DefaultConvolution>
1241    };
1242    let t = 4;
1243    let digits = 3;
1244    
1245    let P = log_time::<_, _, true, _>("CreatePtxtRing", |[]|
1246        params.create_plaintext_ring(t)
1247    );
1248    let (C, Cmul) = log_time::<_, _, true, _>("CreateCtxtRing", |[]|
1249        params.create_ciphertext_rings()
1250    );
1251
1252    let sk = log_time::<_, _, true, _>("GenSK", |[]| 
1253        CompositeSingleRNSBFV::gen_sk(&C, &mut rng, None)
1254    );
1255
1256    let m = P.int_hom().map(3);
1257    let ct = log_time::<_, _, true, _>("EncSym", |[]|
1258        CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &m, &sk)
1259    );
1260
1261    let res = log_time::<_, _, true, _>("HomAddPlain", |[]| 
1262        CompositeSingleRNSBFV::hom_add_plain(&P, &C, &m, CompositeSingleRNSBFV::clone_ct(&C, &ct))
1263    );
1264    assert_el_eq!(&P, &P.int_hom().map(2), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1265
1266    let res = log_time::<_, _, true, _>("HomAdd", |[]| 
1267        CompositeSingleRNSBFV::hom_add(&C, CompositeSingleRNSBFV::clone_ct(&C, &ct), &ct)
1268    );
1269    assert_el_eq!(&P, &P.int_hom().map(2), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1270
1271    let res = log_time::<_, _, true, _>("HomMulPlain", |[]| 
1272        CompositeSingleRNSBFV::hom_mul_plain(&P, &C, &m, CompositeSingleRNSBFV::clone_ct(&C, &ct))
1273    );
1274    assert_el_eq!(&P, &P.int_hom().map(1), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1275
1276    let rk = log_time::<_, _, true, _>("GenRK", |[]| 
1277        CompositeSingleRNSBFV::gen_rk(&C, &mut rng, &sk, digits)
1278    );
1279    let ct2 = CompositeSingleRNSBFV::enc_sym(&P, &C, &mut rng, &m, &sk);
1280    let res = log_time::<_, _, true, _>("HomMul", |[]| 
1281        CompositeSingleRNSBFV::hom_mul(&P, &C, &Cmul, ct, ct2, &rk)
1282    );
1283    assert_el_eq!(&P, &P.int_hom().map(1), &CompositeSingleRNSBFV::dec(&P, &C, res, &sk));
1284}