poulpy/poulpy-schemes/src/tfhe/blind_rotation/lut.rs
poulpy-schemes/src/tfhe/blind_rotation/lut.rsRaw
1use poulpy_core::layouts::{Base2K, Degree, TorusPrecision};
2use poulpy_hal::{
3 api::{
4 ScratchOwnedAlloc, ScratchOwnedBorrow, TakeSlice, VecZnxCopy, VecZnxNormalizeInplace, VecZnxNormalizeTmpBytes,
5 VecZnxRotateInplace, VecZnxRotateInplaceTmpBytes, VecZnxSwitchRing,
6 },
7 layouts::{Backend, Module, Scratch, ScratchOwned, VecZnx, ZnxInfos, ZnxViewMut},
8 reference::{vec_znx::vec_znx_rotate_inplace, znx::ZnxRef},
9};
10
11#[derive(Debug, Clone, Copy)]
12pub enum LookUpTableRotationDirection {
13 Left,
14 Right,
15}
16
17pub struct LookUpTableLayout {
18 pub n: Degree,
19 pub extension_factor: usize,
20 pub k: TorusPrecision,
21 pub base2k: Base2K,
22}
23
24pub trait LookupTableInfos {
25 fn n(&self) -> Degree;
26 fn extension_factor(&self) -> usize;
27 fn k(&self) -> TorusPrecision;
28 fn base2k(&self) -> Base2K;
29 fn size(&self) -> usize;
30}
31
32impl LookupTableInfos for LookUpTableLayout {
33 fn base2k(&self) -> Base2K {
34 self.base2k
35 }
36
37 fn extension_factor(&self) -> usize {
38 self.extension_factor
39 }
40
41 fn k(&self) -> TorusPrecision {
42 self.k
43 }
44
45 fn size(&self) -> usize {
46 self.k().as_usize().div_ceil(self.base2k().as_usize())
47 }
48
49 fn n(&self) -> Degree {
50 self.n
51 }
52}
53
54pub struct LookupTable {
55 pub(crate) data: Vec<VecZnx<Vec<u8>>>,
56 pub(crate) rot_dir: LookUpTableRotationDirection,
57 pub(crate) base2k: Base2K,
58 pub(crate) k: TorusPrecision,
59 pub(crate) drift: usize,
60}
61
62impl LookupTableInfos for LookupTable {
63 fn base2k(&self) -> Base2K {
64 self.base2k
65 }
66
67 fn extension_factor(&self) -> usize {
68 self.data.len()
69 }
70
71 fn k(&self) -> TorusPrecision {
72 self.k
73 }
74
75 fn n(&self) -> Degree {
76 self.data[0].n().into()
77 }
78
79 fn size(&self) -> usize {
80 self.data[0].size()
81 }
82}
83
84pub trait LookupTableFactory {
85 fn lookup_table_set(&self, res: &mut LookupTable, f: &[i64], k: usize);
86 fn lookup_table_rotate(&self, k: i64, res: &mut LookupTable);
87}
88
89impl LookupTable {
90 pub fn alloc<A>(infos: &A) -> Self
91 where
92 A: LookupTableInfos,
93 {
94 #[cfg(debug_assertions)]
95 {
96 assert!(
97 infos.extension_factor() & (infos.extension_factor() - 1) == 0,
98 "extension_factor must be a power of two but is: {}",
99 infos.extension_factor()
100 );
101 }
102 Self {
103 data: (0..infos.extension_factor())
104 .map(|_| VecZnx::alloc(infos.n().into(), 1, infos.size()))
105 .collect(),
106 base2k: infos.base2k(),
107 k: infos.k(),
108 drift: 0,
109 rot_dir: LookUpTableRotationDirection::Left,
110 }
111 }
112
113 pub fn log_extension_factor(&self) -> usize {
114 (usize::BITS - (self.extension_factor() - 1).leading_zeros()) as _
115 }
116
117 pub fn extension_factor(&self) -> usize {
118 self.data.len()
119 }
120
121 pub fn domain_size(&self) -> usize {
122 self.data.len() * self.data[0].n()
123 }
124
125 pub fn rotation_direction(&self) -> LookUpTableRotationDirection {
126 self.rot_dir
127 }
128
129 // By default X^{-dec(lwe)} is computed during the blind rotation.
130 // Setting [reverse_rotation] to true will reverse the sign of
131 // rotation of the LUT by instead evaluating X^{dec(lwe)} during
132 // the blind rotation.
133 pub fn set_rotation_direction(&mut self, rot_dir: LookUpTableRotationDirection) {
134 self.rot_dir = rot_dir
135 }
136
137 pub fn set<M>(&mut self, module: &M, f: &[i64], k: usize)
138 where
139 M: LookupTableFactory,
140 {
141 module.lookup_table_set(self, f, k);
142 }
143
144 pub(crate) fn rotate<M>(&mut self, module: &M, k: i64)
145 where
146 M: LookupTableFactory,
147 {
148 module.lookup_table_rotate(k, self);
149 }
150}
151
152pub(crate) trait DivRound {
153 fn div_round(self, rhs: Self) -> Self;
154}
155
156impl DivRound for usize {
157 #[inline]
158 fn div_round(self, rhs: Self) -> Self {
159 (self + rhs / 2) / rhs
160 }
161}
162
163#[allow(dead_code)]
164fn max_bit_size(vec: &[i64]) -> u32 {
165 vec.iter()
166 .map(|&v| {
167 if v == 0 {
168 0
169 } else {
170 v.unsigned_abs().ilog2() + 1
171 }
172 })
173 .max()
174 .unwrap_or(0)
175}
176
177impl<BE: Backend> LookupTableFactory for Module<BE>
178where
179 Self: VecZnxRotateInplace<BE>
180 + VecZnxNormalizeInplace<BE>
181 + VecZnxNormalizeTmpBytes
182 + VecZnxSwitchRing
183 + VecZnxCopy
184 + VecZnxRotateInplaceTmpBytes
185 + VecZnxRotateInplace<BE>
186 + VecZnxRotateInplaceTmpBytes,
187 ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>,
188 Scratch<BE>: TakeSlice,
189{
190 fn lookup_table_set(&self, res: &mut LookupTable, f: &[i64], k: usize) {
191 assert!(f.len() <= self.n());
192
193 let base2k: usize = res.base2k.into();
194
195 let mut scratch: ScratchOwned<BE> = ScratchOwned::alloc(
196 self.vec_znx_normalize_tmp_bytes()
197 .max(res.domain_size() << 3),
198 );
199
200 // Get the number minimum limb to store the message modulus
201 let limbs: usize = k.div_ceil(base2k);
202
203 #[cfg(debug_assertions)]
204 {
205 assert!(f.len() <= self.n());
206 assert!(
207 (max_bit_size(f) + (k % base2k) as u32) < i64::BITS,
208 "overflow: max(|f|) << (k%base2k) > i64::BITS"
209 );
210 assert!(limbs <= res.data[0].size());
211 }
212
213 // Scaling factor
214 let mut scale = 1;
215 if !k.is_multiple_of(base2k) {
216 scale <<= base2k - (k % base2k);
217 }
218
219 // #elements in lookup table
220 let f_len: usize = f.len();
221
222 // If LUT size > TakeScalarZnx
223 let domain_size: usize = res.domain_size();
224
225 let size: usize = res.k.div_ceil(res.base2k) as usize;
226
227 // Equivalent to AUTO([f(0), -f(n-1), -f(n-2), ..., -f(1)], -1)
228 let mut lut_full: VecZnx<Vec<u8>> = VecZnx::alloc(domain_size, 1, size);
229
230 let lut_at: &mut [i64] = lut_full.at_mut(0, limbs - 1);
231
232 let step: usize = domain_size.div_round(f_len);
233
234 f.iter().enumerate().for_each(|(i, fi)| {
235 let start: usize = i * step;
236 let end: usize = start + step;
237 lut_at[start..end].fill(fi * scale);
238 });
239
240 let drift: usize = step >> 1;
241
242 // Rotates half the step to the left
243
244 if res.extension_factor() > 1 {
245 let (tmp, _) = scratch.borrow().take_slice(lut_full.n());
246
247 for i in 0..res.extension_factor() {
248 self.vec_znx_switch_ring(&mut res.data[i], 0, &lut_full, 0);
249 if i < res.extension_factor() {
250 vec_znx_rotate_inplace::<_, ZnxRef>(-1, &mut lut_full, 0, tmp);
251 }
252 }
253 } else {
254 self.vec_znx_copy(&mut res.data[0], 0, &lut_full, 0);
255 }
256
257 for a in res.data.iter_mut() {
258 self.vec_znx_normalize_inplace(res.base2k.into(), a, 0, scratch.borrow());
259 }
260
261 res.rotate(self, -(drift as i64));
262
263 res.drift = drift
264 }
265
266 fn lookup_table_rotate(&self, k: i64, res: &mut LookupTable) {
267 let extension_factor: usize = res.extension_factor();
268 let two_n: usize = 2 * res.data[0].n();
269 let two_n_ext: usize = two_n * extension_factor;
270
271 let mut scratch: ScratchOwned<_> = ScratchOwned::alloc(self.vec_znx_rotate_inplace_tmp_bytes());
272
273 let k_pos: usize = ((k + two_n_ext as i64) % two_n_ext as i64) as usize;
274
275 let k_hi: usize = k_pos / extension_factor;
276 let k_lo: usize = k_pos % extension_factor;
277
278 (0..extension_factor - k_lo).for_each(|i| {
279 self.vec_znx_rotate_inplace(k_hi as i64, &mut res.data[i], 0, scratch.borrow());
280 });
281
282 (extension_factor - k_lo..extension_factor).for_each(|i| {
283 self.vec_znx_rotate_inplace(k_hi as i64 + 1, &mut res.data[i], 0, scratch.borrow());
284 });
285
286 res.data.rotate_right(k_lo);
287 }
288}