he_ring/bgv/
bootstrap.rs

1use std::fs::File;
2use std::io::{BufReader, BufWriter};
3use std::cmp::max;
4
5use feanor_math::algorithms::int_factor::is_prime_power;
6use feanor_math::ring::*;
7
8use crate::bgv::modswitch::DefaultModswitchStrategy;
9use crate::circuit::serialization::{DeserializeSeedPlaintextCircuit, SerializablePlaintextCircuit};
10use crate::circuit::*;
11use crate::lintransform::matmul::MatmulTransform;
12use crate::log_time;
13use crate::digitextract::DigitExtract;
14
15use crate::lintransform::composite;
16use crate::lintransform::pow2;
17
18use serde::de::DeserializeSeed;
19use serde::Serialize;
20
21use super::modswitch::*;
22use super::*;
23
24#[derive(Clone, Debug)]
25pub struct ThinBootstrapParams<Params: BGVCiphertextParams> {
26    pub scheme_params: Params,
27    pub v: usize,
28    pub t: i64
29}
30
31impl<Params> ThinBootstrapParams<Params>
32    where Params: BGVCiphertextParams,
33        NumberRing<Params>: Clone,
34        <CiphertextRing<Params> as RingStore>::Type: AsBGVPlaintext<Params>
35{
36    fn read_or_create_circuit<F, const LOG: bool>(H: &DefaultHypercube<NumberRing<Params>>, base_name: &str, cache_dir: Option<&str>, create: F) -> PlaintextCircuit<<PlaintextRing<Params> as RingStore>::Type>
37        where F: FnOnce() -> PlaintextCircuit<<PlaintextRing<Params> as RingStore>::Type>
38    {
39        if let Some(cache_dir) = cache_dir {
40            let filename = format!("{}/{}_n{}_p{}_e{}.json", cache_dir, base_name, H.hypercube().n(), H.p(), H.e());
41            if let Ok(file) = File::open(filename.as_str()) {
42                if LOG {
43                    println!("Reading {} from file {}", base_name, filename);
44                }
45                let reader = serde_json::de::IoRead::new(BufReader::new(file));
46                let mut deserializer = serde_json::Deserializer::new(reader);
47                let deserialized = DeserializeSeedPlaintextCircuit::new(H.ring(), H.galois_group()).deserialize(&mut deserializer).unwrap();
48                return deserialized;
49            }
50            let result = log_time::<_, _, LOG, _>(format!("Creating circuit {}", base_name).as_str(), |[]| create());
51            let file = File::create(filename.as_str()).unwrap();
52            let writer = BufWriter::new(file);
53            let mut serializer = serde_json::Serializer::new(writer);
54            SerializablePlaintextCircuit::new(H.ring(), H.galois_group(), &result).serialize(&mut serializer).unwrap();
55            return result;
56        } else {
57            return create();
58        }
59    }
60
61    pub fn build_pow2<M: BGVModswitchStrategy<Params>, const LOG: bool>(&self, C: &CiphertextRing<Params>, modswitch_strategy: M, cache_dir: Option<&str>) -> ThinBootstrapData<Params, M> {
62        let log2_n = ZZ.abs_log2_ceil(&(self.scheme_params.number_ring().n() as i64)).unwrap();
63        assert_eq!(self.scheme_params.number_ring().n(), 1 << log2_n);
64
65        let (p, r) = is_prime_power(&ZZ, &self.t).unwrap();
66        let v = self.v;
67        let e = r + v;
68        if LOG {
69            println!("Setting up bootstrapping for plaintext modulus p^r = {}^{} = {} within the cyclotomic ring Q[X]/(Phi_{})", p, r, self.t, <_ as HECyclotomicNumberRing>::n(&self.scheme_params.number_ring()));
70            println!("Choosing e = r + v = {} + {}", r, v);
71        }
72
73        let plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, e));
74        let original_plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, r));
75
76        let digit_extract = DigitExtract::new_default(p, e, r);
77
78        let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(plaintext_ring.n() as u64), p);
79        let H = if let Some(cache_dir) = cache_dir {
80            HypercubeIsomorphism::new_cache_file::<LOG>(&plaintext_ring, hypercube, cache_dir)
81        } else {
82            HypercubeIsomorphism::new::<LOG>(&plaintext_ring, hypercube)
83        };
84        let original_H = H.change_modulus(&original_plaintext_ring);
85
86        let slots_to_coeffs = Self::read_or_create_circuit::<_, LOG>(&original_H, "slots_to_coeffs", cache_dir, || MatmulTransform::to_circuit_many(pow2::slots_to_coeffs_thin(&original_H), &original_H));
87        let coeffs_to_slots = Self::read_or_create_circuit::<_, LOG>(&H, "coeffs_to_slots", cache_dir, || pow2::coeffs_to_slots_thin(&H));
88        let plaintext_ring_hierarchy = ((r + 1)..=e).map(|k| self.scheme_params.create_plaintext_ring(ZZ.pow(p, k))).collect();
89
90        return ThinBootstrapData {
91            digit_extract,
92            slots_to_coeffs_thin: slots_to_coeffs.change_ring_uniform(|x| x.change_ring(|x| Params::encode_plain(&original_plaintext_ring, C, &x))),
93            coeffs_to_slots_thin: coeffs_to_slots.change_ring_uniform(|x| x.change_ring(|x| Params::encode_plain(&plaintext_ring, C, &x))),
94            plaintext_ring_hierarchy: plaintext_ring_hierarchy,
95            modswitch_strategy: modswitch_strategy,
96            tmp_coprime_modulus_plaintext: self.scheme_params.create_plaintext_ring(ZZ.pow(p, e) + 1)
97        };
98    }
99
100    pub fn build_odd<M: BGVModswitchStrategy<Params>, const LOG: bool>(&self, C: &CiphertextRing<Params>, modswitch_strategy: M, cache_dir: Option<&str>) -> ThinBootstrapData<Params, M> {
101        assert!(self.scheme_params.number_ring().n() % 2 != 0);
102
103        let (p, r) = is_prime_power(&ZZ, &self.t).unwrap();
104        let v = self.v;
105        let e = r + v;
106        if LOG {
107            println!("Setting up bootstrapping for plaintext modulus p^r = {}^{} = {} within the cyclotomic ring Q[X]/(Phi_{})", p, r, self.t, self.scheme_params.number_ring().n());
108            println!("Choosing e = r + v = {} + {}", r, v);
109        }
110
111        let plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, e));
112        let original_plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, r));
113
114        let digit_extract = if p == 2 && e <= 23 {
115            DigitExtract::new_precomputed_p_is_2(p, e, r)
116        } else {
117            DigitExtract::new_default(p, e, r)
118        };
119
120        let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(plaintext_ring.n() as u64), p);
121        let H = if let Some(cache_dir) = cache_dir {
122            HypercubeIsomorphism::new_cache_file::<LOG>(&plaintext_ring, hypercube, cache_dir)
123        } else {
124            HypercubeIsomorphism::new::<LOG>(&plaintext_ring, hypercube)
125        };
126        let original_H = H.change_modulus(&original_plaintext_ring);
127        let slots_to_coeffs =  Self::read_or_create_circuit::<_, LOG>(&original_H, "slots_to_coeffs", cache_dir, ||MatmulTransform::to_circuit_many(composite::slots_to_powcoeffs_thin(&original_H), &original_H));
128        let coeffs_to_slots = Self::read_or_create_circuit::<_, LOG>(&H, "coeffs_to_slots", cache_dir, || MatmulTransform::to_circuit_many(composite::powcoeffs_to_slots_thin(&H), &H));
129        let plaintext_ring_hierarchy = ((r + 1)..=e).map(|k| self.scheme_params.create_plaintext_ring(ZZ.pow(p, k))).collect();
130
131        return ThinBootstrapData {
132            digit_extract,
133            slots_to_coeffs_thin: slots_to_coeffs.change_ring_uniform(|x| x.change_ring(|x| Params::encode_plain(&original_plaintext_ring, C, &x))),
134            coeffs_to_slots_thin: coeffs_to_slots.change_ring_uniform(|x| x.change_ring(|x| Params::encode_plain(&plaintext_ring, C, &x))),
135            plaintext_ring_hierarchy: plaintext_ring_hierarchy,
136            modswitch_strategy: modswitch_strategy,
137            tmp_coprime_modulus_plaintext: self.scheme_params.create_plaintext_ring(ZZ.pow(p, e) + 1)
138        };
139    }
140}
141
142pub struct ThinBootstrapData<Params, Strategy>
143    where Params: BGVCiphertextParams, 
144        Strategy: BGVModswitchStrategy<Params>,
145        <CiphertextRing<Params> as RingStore>::Type: AsBGVPlaintext<Params>
146{
147    modswitch_strategy: Strategy,
148    digit_extract: DigitExtract,
149    slots_to_coeffs_thin: PlaintextCircuit<<CiphertextRing<Params> as RingStore>::Type>,
150    coeffs_to_slots_thin: PlaintextCircuit<<CiphertextRing<Params> as RingStore>::Type>,
151    plaintext_ring_hierarchy: Vec<PlaintextRing<Params>>,
152    tmp_coprime_modulus_plaintext: PlaintextRing<Params>
153}
154
155
156impl<Params, Strategy> ThinBootstrapData<Params, Strategy>
157    where Params: BGVCiphertextParams, 
158        Strategy: BGVModswitchStrategy<Params>,
159        <CiphertextRing<Params> as RingStore>::Type: AsBGVPlaintext<Params>
160{
161    fn r(&self) -> usize {
162        self.digit_extract.e() - self.digit_extract.v()
163    }
164
165    fn e(&self) -> usize {
166        self.digit_extract.e()
167    }
168
169    fn v(&self) -> usize {
170        self.digit_extract.v()
171    }
172
173    fn p(&self) -> i64 {
174        self.digit_extract.p()
175    }
176
177    pub fn largest_plaintext_ring(&self) -> &PlaintextRing<Params> {
178        self.plaintext_ring_hierarchy.last().unwrap()
179    }
180
181    pub fn required_galois_keys(&self, P: &PlaintextRing<Params>) -> Vec<CyclotomicGaloisGroupEl> {
182        let mut result = Vec::new();
183        result.extend(self.slots_to_coeffs_thin.required_galois_keys(&P.galois_group()).into_iter());
184        result.extend(self.coeffs_to_slots_thin.required_galois_keys(&P.galois_group()).into_iter());
185        result.sort_by_key(|g| P.galois_group().representative(*g));
186        result.dedup_by(|g, s| P.galois_group().eq_el(*g, *s));
187        return result;
188    }
189
190    ///
191    /// Performs bootstrapping on thinly packed ciphertexts.
192    /// 
193    /// Parameters are as follows:
194    ///  - `C_master` is the ciphertext ring over the largest RNS base, both relinearization and
195    ///    Galois keys must be defined w.r.t. `C_master`
196    ///  - `P_base` is the current plaintext ring; `ct` must be a valid BGV ciphertext encrypting
197    ///    a message from `P_base`
198    ///  - `ct_dropped_moduli` contains all RNS factor indices of `C_master` that aren't used by `ct`
199    ///    (anymore); More concrete, `ct` lives over the ciphertext ring one obtains by dropping the
200    ///    RNS factors with these indices from the RNS base of `C_master`
201    ///  - `ct` is the ciphertext to bootstrap; It must be thinly packed (i.e. each slot may only
202    ///    contain an element of `Z/(t)`), otherwise this function will cause immediate noise overflow.
203    ///  - `rk` is a relinearization key, to be used for computing products
204    ///  - `gks` is a list of Galois keys, to be used for applying Galois automorphisms. This list
205    ///    must contain a Galois key for each Galois automorphism listed in [`ThinBootstrapData::required_galois_keys()`],
206    ///    but may contain additional Galois keys
207    ///  - `debug_sk` can be a reference to a secret key, which is used to print out decryptions
208    ///    of intermediate results for debugging purposes. May only be set if `LOG == true`.
209    /// 
210    #[instrument(skip_all)]
211    pub fn bootstrap_thin<'a, const LOG: bool>(
212        &self,
213        C_master: &CiphertextRing<Params>, 
214        P_base: &PlaintextRing<Params>,
215        ct_dropped_moduli: &RNSFactorIndexList,
216        ct: Ciphertext<Params>,
217        rk: &RelinKey<'a, Params>,
218        gks: &[(CyclotomicGaloisGroupEl, KeySwitchKey<'a, Params>)],
219        sk_hwt: Option<usize>,
220        debug_sk: Option<&SecretKey<Params>>
221    ) -> ModulusAwareCiphertext<Params, Strategy>
222        where Params: 'a
223    {
224        assert!(LOG || debug_sk.is_none());
225        assert_eq!(ZZ.pow(self.p(), self.r()), *P_base.base_ring().modulus());
226        if LOG {
227            println!("Starting Bootstrapping")
228        }
229
230        let drop_input_rns_factors = {
231            let special_modulus_factor_count = gks[0].1.special_modulus_factor_count;
232            let special_modulus_factors = RNSFactorIndexList::from(((C_master.base_ring().len() - special_modulus_factor_count)..C_master.base_ring().len()).collect(), C_master.base_ring().len());
233            let drop_additional_moduli_count = max(2, C_master.base_ring().len() - ct_dropped_moduli.union(&special_modulus_factors).len()) - 2;
234            let ct_dropped_moduli_without_special = ct_dropped_moduli.subtract(&special_modulus_factors);
235            let gk_digits_after_drop = gks[0].1.k0.gadget_vector_digits().remove_indices(&ct_dropped_moduli_without_special);
236            let drop_additional_moduli = recommended_rns_factors_to_drop(KeySwitchKeyParams { digits_without_special: gk_digits_after_drop, special_modulus_factor_count }, drop_additional_moduli_count);
237            drop_additional_moduli.pullback(&ct_dropped_moduli_without_special).union(&special_modulus_factors)
238        };
239        let C_input = Params::mod_switch_down_C(C_master, &drop_input_rns_factors);
240        let ct_input = Params::mod_switch_down_ct(P_base, &C_input, &Params::mod_switch_down_C(C_master, ct_dropped_moduli), &drop_input_rns_factors.pushforward(&ct_dropped_moduli), ct);
241
242        let sk_input = debug_sk.map(|sk| Params::mod_switch_down_sk(&C_input, &C_master, &drop_input_rns_factors, sk));
243        if let Some(sk) = &sk_input {
244            Params::dec_println_slots(P_base, &C_input, &ct_input, sk, Some("."));
245        }
246
247        let P_main = self.plaintext_ring_hierarchy.last().unwrap();
248        debug_assert_eq!(ZZ.pow(self.p(), self.e()), *P_main.base_ring().modulus());
249
250        let values_in_coefficients = log_time::<_, _, LOG, _>("1. Computing Slots-to-Coeffs transform", |[key_switches]| {
251            let result = DefaultModswitchStrategy::never_modswitch().evaluate_circuit(
252                &self.slots_to_coeffs_thin, 
253                C_master,
254                P_base, 
255                C_master, 
256                &[ModulusAwareCiphertext {
257                    data: ct_input, 
258                    info: (), 
259                    dropped_rns_factor_indices: drop_input_rns_factors.clone()
260                }], 
261                None, 
262                gks,
263                key_switches,
264                debug_sk
265            );
266            assert_eq!(1, result.len());
267            let result = result.into_iter().next().unwrap();
268            debug_assert_eq!(result.dropped_rns_factor_indices, drop_input_rns_factors);
269            return result.data;
270        });
271        if let Some(sk) = &sk_input {
272            Params::dec_println(P_base, &C_input, &values_in_coefficients, sk);
273        }
274
275        let noisy_decryption = log_time::<_, _, LOG, _>("2. Computing noisy decryption c0 + c1 * s", |[]| {
276            // this is slightly more complicated than in BFV, since we cannot mod-switch to a ciphertext modulus that is not coprime to `t = p^r`.
277            // Instead, we first multiply by `p^v`, then mod-switch to `p^e + 1`, and then reduce the shortest lift of the result modulo `p^e`.
278            // This will introduce the overflow modulo `p^e + 1` as error in the lower bits, which we will later remove during digit extraction
279            let values_scaled = Ciphertext {
280                c0: C_input.inclusion().mul_map(values_in_coefficients.c0, C_input.base_ring().coerce(&ZZ, ZZ.pow(self.p(), self.v()))),
281                c1: C_input.inclusion().mul_map(values_in_coefficients.c1, C_input.base_ring().coerce(&ZZ, ZZ.pow(self.p(), self.v()))),
282                implicit_scale: values_in_coefficients.implicit_scale
283            };
284            // change to `p^e + 1`
285            let (c0, c1) = Params::mod_switch_to_plaintext(P_main, &self.tmp_coprime_modulus_plaintext, &C_input, values_scaled);
286            // reduce modulo `p^e`, which will introduce additional error in the lower digits
287            let mod_pe = P_main.base_ring().can_hom(&ZZ).unwrap();
288            let (c0, c1) = (
289                P_main.from_canonical_basis(self.tmp_coprime_modulus_plaintext.wrt_canonical_basis(&c0).iter().map(|x| mod_pe.map(self.tmp_coprime_modulus_plaintext.base_ring().smallest_lift(x)))),
290                P_main.from_canonical_basis(self.tmp_coprime_modulus_plaintext.wrt_canonical_basis(&c1).iter().map(|x| mod_pe.map(self.tmp_coprime_modulus_plaintext.base_ring().smallest_lift(x))))
291            );
292
293            let enc_sk = Params::enc_sk(P_main, C_master);
294            return ModulusAwareCiphertext {
295                data: Params::hom_add_plain(P_main, C_master, &c0, Params::hom_mul_plain(P_main, C_master, &c1, enc_sk)),
296                info: self.modswitch_strategy.info_for_fresh_encryption(P_main, C_master, sk_hwt),
297                dropped_rns_factor_indices: RNSFactorIndexList::empty()
298            };
299        });
300        if let Some(sk) = debug_sk {
301            Params::dec_println(P_main, &C_master, &noisy_decryption.data, sk);
302        }
303
304        let noisy_decryption_in_slots = log_time::<_, _, LOG, _>("3. Computing Coeffs-to-Slots transform", |[key_switches]| {
305            let result = self.modswitch_strategy.evaluate_circuit(
306                &self.coeffs_to_slots_thin, 
307                C_master,
308                P_main, 
309                C_master, 
310                &[noisy_decryption], 
311                None, 
312                gks,
313                key_switches,
314                debug_sk
315            );
316            assert_eq!(1, result.len());
317            return result.into_iter().next().unwrap();
318        });
319        if let Some(sk) = debug_sk {
320            let C_current = Params::mod_switch_down_C(C_master, &noisy_decryption_in_slots.dropped_rns_factor_indices);
321            Params::dec_println_slots(P_main, &C_current, &noisy_decryption_in_slots.data, &Params::mod_switch_down_sk(&C_current, C_master, &noisy_decryption_in_slots.dropped_rns_factor_indices, sk), Some("."));
322        }
323
324        let final_result = log_time::<_, _, LOG, _>("4. Computing digit extraction", |[key_switches]| {
325
326            let C_current = Params::mod_switch_down_C(C_master, &noisy_decryption_in_slots.dropped_rns_factor_indices);
327            let rounding_divisor_half = C_current.base_ring().coerce(&ZZbig, ZZbig.rounded_div(ZZbig.pow(int_cast(self.p(), ZZbig, ZZ), self.v()), &ZZbig.int_hom().map(2)));
328            let digit_extraction_input = ModulusAwareCiphertext {
329                data: Params::hom_add_plain_encoded(P_main, &C_current, &C_current.inclusion().map(rounding_divisor_half), noisy_decryption_in_slots.data),
330                info: noisy_decryption_in_slots.info,
331                dropped_rns_factor_indices: noisy_decryption_in_slots.dropped_rns_factor_indices
332            };
333    
334            if let Some(sk) = debug_sk {
335                self.modswitch_strategy.print_info(P_main, &C_current, &digit_extraction_input);
336                Params::dec_println_slots(P_main, &C_current, &digit_extraction_input.data, &Params::mod_switch_down_sk(&C_current, C_master, &digit_extraction_input.dropped_rns_factor_indices, sk), Some("."));
337            }
338
339            return self.digit_extract.evaluate_bgv::<Params, Strategy, LOG>(
340                &self.modswitch_strategy,
341                P_base,
342                &self.plaintext_ring_hierarchy,
343                C_master,
344                digit_extraction_input,
345                rk,
346                key_switches,
347                debug_sk
348            ).0;
349        });
350        return final_result;
351    }
352}
353
354impl DigitExtract {
355
356    pub fn evaluate_bgv<'a, Params: BGVCiphertextParams, Strategy: BGVModswitchStrategy<Params>, const LOG: bool>(
357        &self,
358        modswitch_strategy: &Strategy, 
359        P_base: &PlaintextRing<Params>, 
360        P: &[PlaintextRing<Params>], 
361        C_master: &CiphertextRing<Params>, 
362        input: ModulusAwareCiphertext<Params, Strategy>, 
363        rk: &RelinKey<'a, Params>,
364        key_switches: &mut usize,
365        debug_sk: Option<&SecretKey<Params>>
366    ) -> (ModulusAwareCiphertext<Params, Strategy>, ModulusAwareCiphertext<Params, Strategy>) {
367        assert!(LOG || debug_sk.is_none());
368
369        let (p, actual_r) = is_prime_power(StaticRing::<i64>::RING, P_base.base_ring().modulus()).unwrap();
370        assert_eq!(self.p(), p);
371        assert!(actual_r >= self.r());
372        for i in 0..(self.e() - self.r()) {
373            assert_eq!(ZZ.pow(self.p(), actual_r + i + 1), *P[i].base_ring().modulus());
374        }
375        let get_P = |exp: usize| if exp == self.r() {
376            P_base
377        } else {
378            &P[exp - self.r() - 1]
379        };
380        return self.evaluate_generic(
381            input,
382            |exp, inputs, circuit| {
383                let digit_extracted = modswitch_strategy.evaluate_circuit(circuit, ZZi64, get_P(exp), C_master, inputs, Some(rk), &[], key_switches, debug_sk);
384                if LOG && /* don't log if the circuit is just adding/cloning elements */ circuit.has_multiplication_gates() {
385                    println!("Digit extraction modulo p^{} done", exp);
386                    if let Some(sk) = debug_sk {
387                        for ct in &digit_extracted {
388                            modswitch_strategy.print_info(get_P(exp), C_master, ct);
389                            let Clocal = Params::mod_switch_down_C(C_master, &ct.dropped_rns_factor_indices);
390                            let sk_local = Params::mod_switch_down_sk(&Clocal, C_master, &ct.dropped_rns_factor_indices, sk);
391                            Params::dec_println_slots(get_P(exp), &Clocal, &ct.data, &sk_local, Some("."));
392                            println!();
393                        }
394                    }
395                }
396                return digit_extracted;
397            },
398            |exp_old, exp_new, input| {
399                let C_current = Params::mod_switch_down_C(C_master, &input.dropped_rns_factor_indices);
400                let result = ModulusAwareCiphertext {
401                    data: Params::change_plaintext_modulus(get_P(exp_new), get_P(exp_old), &C_current, input.data),
402                    dropped_rns_factor_indices: input.dropped_rns_factor_indices.clone(),
403                    info: input.info
404                };
405                return result;
406            }
407        );
408    }
409}
410
411#[cfg(test)]
412use crate::bgv::noise_estimator::NaiveBGVNoiseEstimator;
413
414#[test]
415fn test_pow2_bgv_thin_bootstrapping_17() {
416    let mut rng = StdRng::from_seed([0; 32]);
417    
418    // 8 slots of rank 16
419    let params = Pow2BGV {
420        log2_q_min: 790,
421        log2_q_max: 800,
422        log2_N: 7,
423        ciphertext_allocator: DefaultCiphertextAllocator::default(),
424        negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
425    };
426    let t = 17;
427    let bootstrap_params = ThinBootstrapParams {
428        scheme_params: params.clone(),
429        v: 2,
430        t: t
431    };
432    let P = params.create_plaintext_ring(t);
433    let C_master = params.create_initial_ciphertext_ring();
434    let key_switch_params = KeySwitchKeyParams::default(5, 2, C_master.base_ring().len());
435
436    let bootstrapper = bootstrap_params.build_pow2::<_, true>(&C_master, DefaultModswitchStrategy::<_, _, false>::new(NaiveBGVNoiseEstimator), None);
437    
438    let sk = Pow2BGV::gen_sk(&C_master, &mut rng, None);
439    let gk = bootstrapper.required_galois_keys(&P).into_iter().map(|g| (g, Pow2BGV::gen_gk(bootstrapper.largest_plaintext_ring(), &C_master, &mut rng, &sk, g, &key_switch_params))).collect::<Vec<_>>();
440    let rk = Pow2BGV::gen_rk(bootstrapper.largest_plaintext_ring(), &C_master, &mut rng, &sk, &key_switch_params);
441    
442    let m = P.int_hom().map(2);
443    let ct = Pow2BGV::enc_sym(&P, &C_master, &mut rng, &m, &sk);
444    let ct_result = bootstrapper.bootstrap_thin::<true>(
445        &C_master, 
446        &P, 
447        &RNSFactorIndexList::empty(),
448        ct, 
449        &rk, 
450        &gk,
451        None,
452        Some(&sk)
453    );
454    let C_result = Pow2BGV::mod_switch_down_C(&C_master, &ct_result.dropped_rns_factor_indices);
455    let sk_result = Pow2BGV::mod_switch_down_sk(&C_result, &C_master, &ct_result.dropped_rns_factor_indices, &sk);
456
457    assert_el_eq!(P, P.int_hom().map(2), Pow2BGV::dec(&P, &C_result, ct_result.data, &sk_result));
458}
459
460#[ignore]
461#[test]
462fn test_bootstrap_large() {
463    let (chrome_layer, _guard) = tracing_chrome::ChromeLayerBuilder::new().build();
464    let filtered_chrome_layer = chrome_layer.with_filter(tracing_subscriber::filter::filter_fn(|metadata| !["small_basis_to_mult_basis", "mult_basis_to_small_basis", "small_basis_to_coeff_basis", "coeff_basis_to_small_basis"].contains(&metadata.name())));
465    tracing_subscriber::registry().with(filtered_chrome_layer).init();
466
467    let mut rng = StdRng::from_seed([0; 32]);
468
469    let t = 4;
470    let v = 7;
471    let hwt = 256;
472    let params = CompositeBGV {
473        log2_q_min: 805,
474        log2_q_max: 820,
475        n1: 37,
476        n2: 949,
477        ciphertext_allocator: Global
478    };
479    let bootstrap_params = ThinBootstrapParams {
480        scheme_params: params.clone(),
481        v: v,
482        t: t
483    };
484    let P = params.create_plaintext_ring(t);
485    let C_master = params.create_initial_ciphertext_ring();
486    assert_eq!(15, C_master.base_ring().len());
487    let key_switch_params = KeySwitchKeyParams::default(7, 2, C_master.base_ring().len());
488
489    let bootstrapper = bootstrap_params.build_odd::<_, true>(&C_master, DefaultModswitchStrategy::<_, _, false>::new(NaiveBGVNoiseEstimator), Some("."));
490    
491    let sk = CompositeBGV::gen_sk(&C_master, &mut rng, Some(hwt));
492    let gk = bootstrapper.required_galois_keys(&P).into_iter().map(|g| (g, CompositeBGV::gen_gk(bootstrapper.largest_plaintext_ring(), &C_master, &mut rng, &sk, g, &key_switch_params))).collect::<Vec<_>>();
493    let rk = CompositeBGV::gen_rk(bootstrapper.largest_plaintext_ring(), &C_master, &mut rng, &sk, &key_switch_params);
494    
495    let m = P.int_hom().map(2);
496    let ct = CompositeBGV::enc_sym(&P, &C_master, &mut rng, &m, &sk);
497    let ct_result = bootstrapper.bootstrap_thin::<true>(
498        &C_master, 
499        &P, 
500        &RNSFactorIndexList::empty(),
501        ct, 
502        &rk, 
503        &gk,
504        Some(hwt),
505        None // Some(&sk)
506    );
507    let C_result = CompositeBGV::mod_switch_down_C(&C_master, &ct_result.dropped_rns_factor_indices);
508    let sk_result = CompositeBGV::mod_switch_down_sk(&C_result, &C_master, &ct_result.dropped_rns_factor_indices, &sk);
509    println!("final noise budget: {}", CompositeBGV::noise_budget(&P, &C_result, &ct_result.data, &sk_result));
510    let result = CompositeBGV::dec(&P, &C_result, ct_result.data, &sk_result);
511    assert_el_eq!(P, P.int_hom().map(2), result);
512}