arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/poulpy-schemes/src/tfhe/blind_rotation/lut.rs
poulpy-schemes/src/tfhe/blind_rotation/lut.rsRaw
  1use poulpy_hal::{
  2    api::{
  3        ScratchOwnedAlloc, ScratchOwnedBorrow, TakeSlice, VecZnxCopy, VecZnxNormalizeInplace, VecZnxNormalizeTmpBytes,
  4        VecZnxRotateInplace, VecZnxRotateInplaceTmpBytes, VecZnxSwitchRing,
  5    },
  6    layouts::{Backend, Module, Scratch, ScratchOwned, VecZnx, ZnxInfos, ZnxViewMut},
  7    reference::{vec_znx::vec_znx_rotate_inplace, znx::ZnxRef},
  8};
  9
 10#[derive(Debug, Clone, Copy)]
 11pub enum LookUpTableRotationDirection {
 12    Left,
 13    Right,
 14}
 15
 16pub struct LookUpTable {
 17    pub(crate) data: Vec<VecZnx<Vec<u8>>>,
 18    pub(crate) rot_dir: LookUpTableRotationDirection,
 19    pub(crate) base2k: usize,
 20    pub(crate) k: usize,
 21    pub(crate) drift: usize,
 22}
 23
 24impl LookUpTable {
 25    pub fn alloc<B: Backend>(module: &Module<B>, base2k: usize, k: usize, extension_factor: usize) -> Self {
 26        #[cfg(debug_assertions)]
 27        {
 28            assert!(
 29                extension_factor & (extension_factor - 1) == 0,
 30                "extension_factor must be a power of two but is: {extension_factor}"
 31            );
 32        }
 33        let size: usize = k.div_ceil(base2k);
 34        let mut data: Vec<VecZnx<Vec<u8>>> = Vec::with_capacity(extension_factor);
 35        (0..extension_factor).for_each(|_| {
 36            data.push(VecZnx::alloc(module.n(), 1, size));
 37        });
 38        Self {
 39            data,
 40            base2k,
 41            k,
 42            drift: 0,
 43            rot_dir: LookUpTableRotationDirection::Left,
 44        }
 45    }
 46
 47    pub fn log_extension_factor(&self) -> usize {
 48        (usize::BITS - (self.extension_factor() - 1).leading_zeros()) as _
 49    }
 50
 51    pub fn extension_factor(&self) -> usize {
 52        self.data.len()
 53    }
 54
 55    pub fn domain_size(&self) -> usize {
 56        self.data.len() * self.data[0].n()
 57    }
 58
 59    pub fn rotation_direction(&self) -> LookUpTableRotationDirection {
 60        self.rot_dir
 61    }
 62
 63    // By default X^{-dec(lwe)} is computed during the blind rotation.
 64    // Setting [reverse_rotation] to true will reverse the sign of
 65    // rotation of the LUT by instead evaluating X^{dec(lwe)} during
 66    // the blind rotation.
 67    pub fn set_rotation_direction(&mut self, rot_dir: LookUpTableRotationDirection) {
 68        self.rot_dir = rot_dir
 69    }
 70
 71    pub fn set<B: Backend>(&mut self, module: &Module<B>, f: &[i64], k: usize)
 72    where
 73        Module<B>: VecZnxRotateInplace<B>
 74            + VecZnxNormalizeInplace<B>
 75            + VecZnxNormalizeTmpBytes
 76            + VecZnxSwitchRing
 77            + VecZnxCopy
 78            + VecZnxRotateInplaceTmpBytes,
 79        ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
 80        Scratch<B>: TakeSlice,
 81    {
 82        assert!(f.len() <= module.n());
 83
 84        let base2k: usize = self.base2k;
 85
 86        let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(module.vec_znx_normalize_tmp_bytes() | (self.domain_size() << 3));
 87
 88        // Get the number minimum limb to store the message modulus
 89        let limbs: usize = k.div_ceil(base2k);
 90
 91        #[cfg(debug_assertions)]
 92        {
 93            assert!(f.len() <= module.n());
 94            assert!(
 95                (max_bit_size(f) + (k % base2k) as u32) < i64::BITS,
 96                "overflow: max(|f|) << (k%base2k) > i64::BITS"
 97            );
 98            assert!(limbs <= self.data[0].size());
 99        }
100
101        // Scaling factor
102        let mut scale = 1;
103        if !k.is_multiple_of(base2k) {
104            scale <<= base2k - (k % base2k);
105        }
106
107        // #elements in lookup table
108        let f_len: usize = f.len();
109
110        // If LUT size > TakeScalarZnx
111        let domain_size: usize = self.domain_size();
112
113        let size: usize = self.k.div_ceil(self.base2k);
114
115        // Equivalent to AUTO([f(0), -f(n-1), -f(n-2), ..., -f(1)], -1)
116        let mut lut_full: VecZnx<Vec<u8>> = VecZnx::alloc(domain_size, 1, size);
117
118        let lut_at: &mut [i64] = lut_full.at_mut(0, limbs - 1);
119
120        let step: usize = domain_size.div_round(f_len);
121
122        f.iter().enumerate().for_each(|(i, fi)| {
123            let start: usize = i * step;
124            let end: usize = start + step;
125            lut_at[start..end].fill(fi * scale);
126        });
127
128        let drift: usize = step >> 1;
129
130        // Rotates half the step to the left
131
132        if self.extension_factor() > 1 {
133            let (tmp, _) = scratch.borrow().take_slice(lut_full.n());
134
135            for i in 0..self.extension_factor() {
136                module.vec_znx_switch_ring(&mut self.data[i], 0, &lut_full, 0);
137                if i < self.extension_factor() {
138                    vec_znx_rotate_inplace::<_, ZnxRef>(-1, &mut lut_full, 0, tmp);
139                }
140            }
141        } else {
142            module.vec_znx_copy(&mut self.data[0], 0, &lut_full, 0);
143        }
144
145        for a in self.data.iter_mut() {
146            module.vec_znx_normalize_inplace(self.base2k, a, 0, scratch.borrow());
147        }
148
149        self.rotate(module, -(drift as i64));
150
151        self.drift = drift
152    }
153
154    #[allow(dead_code)]
155    pub(crate) fn rotate<B: Backend>(&mut self, module: &Module<B>, k: i64)
156    where
157        Module<B>: VecZnxRotateInplace<B> + VecZnxRotateInplaceTmpBytes,
158        ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
159    {
160        let extension_factor: usize = self.extension_factor();
161        let two_n: usize = 2 * self.data[0].n();
162        let two_n_ext: usize = two_n * extension_factor;
163
164        let mut scratch: ScratchOwned<_> = ScratchOwned::alloc(module.vec_znx_rotate_inplace_tmp_bytes());
165
166        let k_pos: usize = ((k + two_n_ext as i64) % two_n_ext as i64) as usize;
167
168        let k_hi: usize = k_pos / extension_factor;
169        let k_lo: usize = k_pos % extension_factor;
170
171        (0..extension_factor - k_lo).for_each(|i| {
172            module.vec_znx_rotate_inplace(k_hi as i64, &mut self.data[i], 0, scratch.borrow());
173        });
174
175        (extension_factor - k_lo..extension_factor).for_each(|i| {
176            module.vec_znx_rotate_inplace(k_hi as i64 + 1, &mut self.data[i], 0, scratch.borrow());
177        });
178
179        self.data.rotate_right(k_lo);
180    }
181}
182
183pub(crate) trait DivRound {
184    fn div_round(self, rhs: Self) -> Self;
185}
186
187impl DivRound for usize {
188    #[inline]
189    fn div_round(self, rhs: Self) -> Self {
190        (self + rhs / 2) / rhs
191    }
192}
193
194#[allow(dead_code)]
195fn max_bit_size(vec: &[i64]) -> u32 {
196    vec.iter()
197        .map(|&v| {
198            if v == 0 {
199                0
200            } else {
201                v.unsigned_abs().ilog2() + 1
202            }
203        })
204        .max()
205        .unwrap_or(0)
206}