1
2use feanor_math::algorithms::int_factor::is_prime_power;
3use feanor_math::homomorphism::*;
4use feanor_math::integer::{int_cast, IntegerRingStore};
5use feanor_math::ring::*;
6use feanor_math::rings::zn::ZnRingStore;
7
8use crate::cyclotomic::CyclotomicRingStore;
9use crate::lintransform::matmul::MatmulTransform;
10use crate::digitextract::*;
11use crate::lintransform::pow2;
12use crate::lintransform::composite;
13
14use super::*;
15
16#[derive(Clone, Debug)]
17pub struct ThinBootstrapParams<Params: BFVCiphertextParams> {
18 pub scheme_params: Params,
19 pub v: usize,
20 pub t: i64
21}
22
23pub struct ThinBootstrapData<Params: BFVCiphertextParams> {
30 digit_extract: DigitExtract,
31 slots_to_coeffs_thin: PlaintextCircuit<<PlaintextRing<Params> as RingStore>::Type>,
32 coeffs_to_slots_thin: PlaintextCircuit<<PlaintextRing<Params> as RingStore>::Type>,
33 plaintext_ring_hierarchy: Vec<PlaintextRing<Params>>
34}
35
36impl<Params: BFVCiphertextParams> ThinBootstrapParams<Params>
37 where NumberRing<Params>: Clone
38{
39 pub fn build_pow2<const LOG: bool>(&self) -> ThinBootstrapData<Params> {
40 let log2_n = ZZ.abs_log2_ceil(&(self.scheme_params.number_ring().n() as i64)).unwrap();
41 assert_eq!(self.scheme_params.number_ring().n(), 1 << log2_n);
42
43 let (p, r) = is_prime_power(&ZZ, &self.t).unwrap();
44 let v = self.v;
45 let e = r + v;
46 if LOG {
47 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()));
48 println!("Choosing e = r + v = {} + {}", r, v);
49 }
50
51 let plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, e));
52 let original_plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, r));
53
54 let digit_extract = DigitExtract::new_default(p, e, r);
55
56 let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(plaintext_ring.n() as u64), p);
57 let H = HypercubeIsomorphism::new::<LOG>(&plaintext_ring, hypercube);
58 let original_H = H.change_modulus(&original_plaintext_ring);
59 let slots_to_coeffs = log_time::<_, _, LOG, _>("Creating Slots-to-Coeffs transform", |[]| MatmulTransform::to_circuit_many(pow2::slots_to_coeffs_thin(&original_H), &original_H));
60 let coeffs_to_slots = log_time::<_, _, LOG, _>("Creating Coeffs-to-Slots transform", |[]| pow2::coeffs_to_slots_thin(&H));
61 let plaintext_ring_hierarchy = ((r + 1)..=e).map(|k| self.scheme_params.create_plaintext_ring(ZZ.pow(p, k))).collect();
62
63 return ThinBootstrapData {
64 digit_extract,
65 slots_to_coeffs_thin: slots_to_coeffs,
66 coeffs_to_slots_thin: coeffs_to_slots,
67 plaintext_ring_hierarchy: plaintext_ring_hierarchy
68 };
69 }
70
71 pub fn build_odd<const LOG: bool>(&self) -> ThinBootstrapData<Params> {
72 assert!(self.scheme_params.number_ring().n() % 2 != 0);
73
74 let (p, r) = is_prime_power(&ZZ, &self.t).unwrap();
75 let v = self.v;
76 let e = r + v;
77 if LOG {
78 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());
79 println!("Choosing e = r + v = {} + {}", r, v);
80 }
81
82 let plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, e));
83 let original_plaintext_ring = self.scheme_params.create_plaintext_ring(ZZ.pow(p, r));
84
85 let digit_extract = if p == 2 && e <= 23 {
86 DigitExtract::new_precomputed_p_is_2(p, e, r)
87 } else {
88 DigitExtract::new_default(p, e, r)
89 };
90
91 let hypercube = HypercubeStructure::halevi_shoup_hypercube(CyclotomicGaloisGroup::new(plaintext_ring.n() as u64), p);
92 let H = HypercubeIsomorphism::new::<LOG>(&plaintext_ring, hypercube);
93 let original_H = H.change_modulus(&original_plaintext_ring);
94 let slots_to_coeffs = log_time::<_, _, LOG, _>("Creating Slots-to-Coeffs transform", |[]| MatmulTransform::to_circuit_many(composite::slots_to_powcoeffs_thin(&original_H), &original_H));
95 let coeffs_to_slots = log_time::<_, _, LOG, _>("Creating Coeffs-to-Slots transform", |[]| MatmulTransform::to_circuit_many(composite::powcoeffs_to_slots_thin(&H), &H));
96 let plaintext_ring_hierarchy = ((r + 1)..=e).map(|k| self.scheme_params.create_plaintext_ring(ZZ.pow(p, k))).collect();
97
98 return ThinBootstrapData {
99 digit_extract,
100 slots_to_coeffs_thin: slots_to_coeffs,
101 coeffs_to_slots_thin: coeffs_to_slots,
102 plaintext_ring_hierarchy: plaintext_ring_hierarchy
103 };
104 }
105}
106
107impl<Params: BFVCiphertextParams> ThinBootstrapData<Params> {
108
109 fn r(&self) -> usize {
110 self.digit_extract.e() - self.digit_extract.v()
111 }
112
113 fn e(&self) -> usize {
114 self.digit_extract.e()
115 }
116
117 fn v(&self) -> usize {
118 self.digit_extract.v()
119 }
120
121 fn p(&self) -> i64 {
122 self.digit_extract.p()
123 }
124
125 pub fn required_galois_keys(&self, P: &PlaintextRing<Params>) -> Vec<CyclotomicGaloisGroupEl> {
126 let mut result = Vec::new();
127 result.extend(self.slots_to_coeffs_thin.required_galois_keys(&P.galois_group()).into_iter());
128 result.extend(self.coeffs_to_slots_thin.required_galois_keys(&P.galois_group()).into_iter());
129 result.sort_by_key(|g| P.galois_group().representative(*g));
130 result.dedup_by(|g, s| P.galois_group().eq_el(*g, *s));
131 return result;
132 }
133
134 #[instrument(skip_all)]
135 pub fn bootstrap_thin<'a, const LOG: bool>(
136 &self,
137 C: &CiphertextRing<Params>,
138 Cmul: &CiphertextRing<Params>,
139 P_base: &PlaintextRing<Params>,
140 ct: Ciphertext<Params>,
141 rk: &RelinKey<'a, Params>,
142 gk: &[(CyclotomicGaloisGroupEl, KeySwitchKey<'a, Params>)],
143 debug_sk: Option<&SecretKey<Params>>
144 ) -> Ciphertext<Params>
145 where Params: 'a
146 {
147 assert!(LOG || debug_sk.is_none());
148 assert_eq!(ZZ.pow(self.p(), self.r()), *P_base.base_ring().modulus());
149 if LOG {
150 println!("Starting Bootstrapping")
151 }
152 if let Some(sk) = debug_sk {
153 Params::dec_println_slots(P_base, C, &ct, sk);
154 }
155
156 let P_main = self.plaintext_ring_hierarchy.last().unwrap();
157 debug_assert_eq!(ZZ.pow(self.p(), self.e()), *P_main.base_ring().modulus());
158
159 let values_in_coefficients = log_time::<_, _, LOG, _>("1. Computing Slots-to-Coeffs transform", |[key_switches]| {
160 let result = self.slots_to_coeffs_thin.evaluate_bfv::<Params>(P_base, C, None, std::slice::from_ref(&ct), None, gk, key_switches);
161 assert_eq!(1, result.len());
162 return result.into_iter().next().unwrap();
163 });
164 if let Some(sk) = debug_sk {
165 Params::dec_println(P_base, C, &values_in_coefficients, sk);
166 }
167
168 let noisy_decryption = log_time::<_, _, LOG, _>("2. Computing noisy decryption c0 + c1 * s", |[]| {
169 let (c0, c1) = Params::mod_switch_to_plaintext(P_main, C, values_in_coefficients);
170 let enc_sk = Params::enc_sk(P_main, C);
171 return Params::hom_add_plain(P_main, C, &c0, Params::hom_mul_plain(P_main, C, &c1, enc_sk));
172 });
173 if let Some(sk) = debug_sk {
174 Params::dec_println(P_main, C, &noisy_decryption, sk);
175 }
176
177 let noisy_decryption_in_slots = log_time::<_, _, LOG, _>("3. Computing Coeffs-to-Slots transform", |[key_switches]| {
178 let result = self.coeffs_to_slots_thin.evaluate_bfv::<Params>(P_main, C, None, std::slice::from_ref(&noisy_decryption), None, gk, key_switches);
179 assert_eq!(1, result.len());
180 return result.into_iter().next().unwrap();
181 });
182 if let Some(sk) = debug_sk {
183 Params::dec_println_slots(P_main, C, &noisy_decryption_in_slots, sk);
184 }
185
186 if LOG {
187 println!("4. Performing digit extraction");
188 }
189 let rounding_divisor_half = P_main.base_ring().coerce(&ZZbig, ZZbig.rounded_div(ZZbig.pow(int_cast(self.p(), ZZbig, ZZ), self.v()), &ZZbig.int_hom().map(2)));
190 let digit_extraction_input = Params::hom_add_plain(P_main, C, &P_main.inclusion().map(rounding_divisor_half), noisy_decryption_in_slots);
191 let result = self.digit_extract.evaluate_bfv::<Params>(P_base, &self.plaintext_ring_hierarchy, C, Cmul, digit_extraction_input, rk).0;
192
193 return result;
194 }
195}
196
197impl DigitExtract {
198
199 pub fn evaluate_bfv<'a, Params: BFVCiphertextParams>(&self,
206 P_base: &PlaintextRing<Params>,
207 P: &[PlaintextRing<Params>],
208 C: &CiphertextRing<Params>,
209 Cmul: &CiphertextRing<Params>,
210 input: Ciphertext<Params>,
211 rk: &RelinKey<'a, Params>
212 ) -> (Ciphertext<Params>, Ciphertext<Params>) {
213 let (p, actual_r) = is_prime_power(StaticRing::<i64>::RING, P_base.base_ring().modulus()).unwrap();
214 assert_eq!(self.p(), p);
215 assert!(actual_r >= self.r());
216 for i in 0..(self.e() - self.r()) {
217 assert_eq!(ZZ.pow(self.p(), actual_r + i + 1), *P[i].base_ring().modulus());
218 }
219 let get_P = |exp: usize| if exp == self.r() {
220 P_base
221 } else {
222 &P[exp - self.r() - 1]
223 };
224 let result = self.evaluate_generic(
225 input,
226 |exp, params, circuit| circuit.evaluate_bfv::<Params>(
227 get_P(exp),
228 C,
229 Some(Cmul),
230 params,
231 Some(rk),
232 &[],
233 &mut 0
234 ),
235 |_, _, x| x
236 );
237 return result;
238 }
239}
240
241#[test]
242fn test_pow2_bfv_thin_bootstrapping_17() {
243 let mut rng = thread_rng();
244
245 let params = Pow2BFV {
247 log2_q_min: 790,
248 log2_q_max: 800,
249 log2_N: 7,
250 ciphertext_allocator: DefaultCiphertextAllocator::default(),
251 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
252 };
253 let t = 17;
254 let digits = 3;
255 let bootstrap_params = ThinBootstrapParams {
256 scheme_params: params.clone(),
257 v: 2,
258 t: t
259 };
260 let bootstrapper = bootstrap_params.build_pow2::<true>();
261
262 let P = params.create_plaintext_ring(t);
263 let (C, Cmul) = params.create_ciphertext_rings();
264
265 let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
266 let gk = bootstrapper.required_galois_keys(&P).into_iter().map(|g| (g, Pow2BFV::gen_gk(&C, &mut rng, &sk, g, digits))).collect::<Vec<_>>();
267 let rk = Pow2BFV::gen_rk(&C, &mut rng, &sk, digits);
268
269 let m = P.int_hom().map(2);
270 let ct = Pow2BFV::enc_sym(&P, &C, &mut rng, &m, &sk);
271 let res_ct = bootstrapper.bootstrap_thin::<true>(
272 &C,
273 &Cmul,
274 &P,
275 ct,
276 &rk,
277 &gk,
278 None
279 );
280
281 assert_el_eq!(P, P.int_hom().map(2), Pow2BFV::dec(&P, &C, res_ct, &sk));
282}
283
284#[test]
285fn test_pow2_bfv_thin_bootstrapping_23() {
286 let mut rng = thread_rng();
287
288 let params = Pow2BFV {
290 log2_q_min: 790,
291 log2_q_max: 800,
292 log2_N: 7,
293 ciphertext_allocator: DefaultCiphertextAllocator::default(),
294 negacyclic_ntt: PhantomData::<DefaultNegacyclicNTT>
295 };
296 let t = 23;
297 let digits = 3;
298 let bootstrap_params = ThinBootstrapParams {
299 scheme_params: params.clone(),
300 v: 2,
301 t: t
302 };
303 let bootstrapper = bootstrap_params.build_pow2::<true>();
304
305 let P = params.create_plaintext_ring(t);
306 let (C, Cmul) = params.create_ciphertext_rings();
307
308 let sk = Pow2BFV::gen_sk(&C, &mut rng, None);
309 let gk = bootstrapper.required_galois_keys(&P).into_iter().map(|g| (g, Pow2BFV::gen_gk(&C, &mut rng, &sk, g, digits))).collect::<Vec<_>>();
310 let rk = Pow2BFV::gen_rk(&C, &mut rng, &sk, digits);
311
312 let m = P.int_hom().map(2);
313 let ct = Pow2BFV::enc_sym(&P, &C, &mut rng, &m, &sk);
314 let res_ct = bootstrapper.bootstrap_thin::<true>(
315 &C,
316 &Cmul,
317 &P,
318 ct,
319 &rk,
320 &gk,
321 None
322 );
323
324 assert_el_eq!(P, P.int_hom().map(2), Pow2BFV::dec(&P, &C, res_ct, &sk));
325}
326
327#[test]
328fn test_composite_bfv_thin_bootstrapping_2() {
329 let mut rng = thread_rng();
334
335 let params = CompositeBFV {
336 log2_q_min: 660,
337 log2_q_max: 700,
338 n1: 31,
339 n2: 11,
340 ciphertext_allocator: DefaultCiphertextAllocator::default()
341 };
342 let t = 8;
343 let digits = 3;
344 let bootstrap_params = ThinBootstrapParams {
345 scheme_params: params.clone(),
346 v: 9,
347 t: t
348 };
349 let bootstrapper = bootstrap_params.build_odd::<true>();
350
351 let P = params.create_plaintext_ring(t);
352 let (C, Cmul) = params.create_ciphertext_rings();
353
354 let sk = CompositeBFV::gen_sk(&C, &mut rng, None);
355 let gk = bootstrapper.required_galois_keys(&P).into_iter().map(|g| (g, CompositeBFV::gen_gk(&C, &mut rng, &sk, g, digits))).collect::<Vec<_>>();
356 let rk = CompositeBFV::gen_rk(&C, &mut rng, &sk, digits);
357
358 let m = P.int_hom().map(2);
359 let ct = CompositeBFV::enc_sym(&P, &C, &mut rng, &m, &sk);
360 let res_ct = bootstrapper.bootstrap_thin::<true>(
361 &C,
362 &Cmul,
363 &P,
364 ct,
365 &rk,
366 &gk,
367 None
368 );
369
370 assert_el_eq!(P, P.int_hom().map(2), CompositeBFV::dec(&P, &C, res_ct, &sk));
371}