Skip to main content

tfhe/strings/server_key/
mod.rs

1mod comp;
2mod no_patterns;
3mod pattern;
4mod trim;
5
6pub use trim::split_ascii_whitespace;
7
8use crate::integer::bigint::static_unsigned::StaticUnsignedBigInt;
9use crate::integer::prelude::*;
10use crate::integer::{BooleanBlock, RadixCiphertext, ServerKey as IntegerServerKey};
11use crate::strings::ciphertext::{num_ascii_blocks, FheAsciiChar, FheString};
12use crate::strings::N;
13use rayon::prelude::*;
14use std::borrow::Borrow;
15use std::cmp::Ordering;
16
17pub struct ServerKey<T>
18where
19    T: Borrow<IntegerServerKey> + Sync,
20{
21    inner: T,
22}
23
24pub type ServerKeyRef<'a> = ServerKey<&'a IntegerServerKey>;
25
26impl<T> ServerKey<T>
27where
28    T: Borrow<IntegerServerKey> + Sync,
29{
30    pub fn inner(&self) -> &IntegerServerKey {
31        self.inner.borrow()
32    }
33
34    pub fn new(inner: T) -> Self {
35        Self { inner }
36    }
37}
38
39// With no padding, the length is just the vector's length (clear result). With padding it requires
40// homomorphically counting the non zero elements (encrypted result).
41pub enum FheStringLen {
42    NoPadding(usize),
43    Padding(RadixCiphertext),
44}
45
46pub enum FheStringIsEmpty {
47    NoPadding(bool),
48    Padding(BooleanBlock),
49}
50
51// A few helper functions for the implementations
52impl<T: Borrow<IntegerServerKey> + Sync> ServerKey<T> {
53    pub(super) fn num_ascii_blocks(&self) -> usize {
54        let sk = self.inner();
55
56        assert_eq!(sk.message_modulus().0, sk.carry_modulus().0);
57
58        num_ascii_blocks(sk.message_modulus())
59    }
60
61    pub fn trivial_encrypt_ascii(&self, str: &str, padding: Option<u32>) -> FheString {
62        let sk = self.inner.borrow();
63
64        super::ciphertext::trivial_encrypt_ascii(
65            &sk.key,
66            &crate::shortint::ServerKey::create_trivial,
67            str,
68            padding,
69        )
70    }
71
72    // If an iterator is longer than the other, the "excess" characters are ignored. This function
73    // performs the equality check by transforming the `str` and `pat` chars into two UInts
74    fn asciis_eq<'a, I, U>(&self, str: I, pat: U) -> BooleanBlock
75    where
76        I: DoubleEndedIterator<Item = &'a FheAsciiChar>,
77        U: DoubleEndedIterator<Item = &'a FheAsciiChar>,
78    {
79        let sk = self.inner();
80
81        let blocks_str = str
82            .into_iter()
83            .rev()
84            .flat_map(|c| c.ciphertext().blocks().to_owned())
85            .collect();
86
87        let blocks_pat = pat
88            .into_iter()
89            .rev()
90            .flat_map(|c| c.ciphertext().blocks().to_owned())
91            .collect();
92
93        let mut uint_str = RadixCiphertext::from_blocks(blocks_str);
94        let mut uint_pat = RadixCiphertext::from_blocks(blocks_pat);
95
96        self.trim_ciphertexts_lsb(&mut uint_str, &mut uint_pat);
97
98        sk.eq_parallelized(&uint_str, &uint_pat)
99    }
100
101    fn clear_asciis_eq<'a, I>(&self, str: I, pat: &str) -> BooleanBlock
102    where
103        I: DoubleEndedIterator<Item = &'a FheAsciiChar>,
104    {
105        let sk = self.inner();
106
107        let num_blocks = self.num_ascii_blocks();
108
109        let blocks_str: Vec<_> = str
110            .into_iter()
111            .rev()
112            .flat_map(|c| c.ciphertext().blocks().to_owned())
113            .collect();
114        let mut clear_pat = pat;
115
116        let str_block_len = blocks_str.len();
117        let pat_block_len = clear_pat.len() * num_blocks;
118
119        let mut uint_str = RadixCiphertext::from_blocks(blocks_str);
120
121        // Trim the str or pat such that the exceeding bytes are removed
122        match str_block_len.cmp(&pat_block_len) {
123            Ordering::Less => {
124                // `str_block_len` is always a multiple of num_blocks as each char is num_blocks
125                // blocks
126                clear_pat = &clear_pat[..str_block_len / num_blocks];
127            }
128            Ordering::Greater => {
129                let diff = str_block_len - pat_block_len;
130                sk.trim_radix_blocks_lsb_assign(&mut uint_str, diff);
131            }
132            Ordering::Equal => (),
133        }
134
135        let clear_pat_uint = self.pad_cipher_and_cleartext_lsb(&mut uint_str, clear_pat);
136
137        sk.scalar_eq_parallelized(&uint_str, clear_pat_uint)
138    }
139
140    fn asciis_eq_ignore_pat_pad<'a, I>(&self, str_pat: I) -> BooleanBlock
141    where
142        I: ParallelIterator<Item = (&'a FheAsciiChar, &'a FheAsciiChar)>,
143    {
144        let sk = self.inner();
145
146        let mut result = sk.create_trivial_boolean_block(true);
147
148        let eq_or_null_pat: Vec<_> = str_pat
149            .map(|(str_char, pat_char)| {
150                let (are_eq, pat_is_null) = rayon::join(
151                    || sk.eq_parallelized(str_char.ciphertext(), pat_char.ciphertext()),
152                    || sk.scalar_eq_parallelized(pat_char.ciphertext(), 0u8),
153                );
154
155                // If `pat_char` is null then `are_eq` is set to true. Hence if ALL `pat_char`s are
156                // null, the result is always true, which is correct since the pattern is empty
157                sk.boolean_bitor(&are_eq, &pat_is_null)
158            })
159            .collect();
160
161        for eq_or_null in eq_or_null_pat {
162            // Will be false if `str_char` != `pat_char` and `pat_char` isn't null
163            sk.boolean_bitand_assign(&mut result, &eq_or_null);
164        }
165
166        result
167    }
168
169    fn pad_cipher_and_cleartext_lsb(
170        &self,
171        lhs: &mut RadixCiphertext,
172        rhs: &str,
173    ) -> StaticUnsignedBigInt<{ N * 8 / 64 }> {
174        let sk = self.inner();
175
176        let num_blocks = self.num_ascii_blocks();
177
178        let mut rhs_bytes = rhs.as_bytes().to_vec();
179
180        // Resize rhs with nulls at the end such that it matches the N const u8 length (for the
181        // StaticUnsignedBigInt)
182        rhs_bytes.resize(N, 0);
183
184        let mut rhs_clear_uint = StaticUnsignedBigInt::<{ N * 8 / 64 }>::from(0u8);
185        rhs_clear_uint.copy_from_be_byte_slice(&rhs_bytes);
186
187        // Also fill the lhs with null blocks at the end
188        if lhs.blocks().len() < N * num_blocks {
189            let diff = N * num_blocks - lhs.blocks().len();
190            sk.extend_radix_with_trivial_zero_blocks_lsb_assign(lhs, diff);
191        }
192
193        rhs_clear_uint
194    }
195
196    fn pad_ciphertexts_lsb(&self, lhs: &mut RadixCiphertext, rhs: &mut RadixCiphertext) {
197        let sk = self.inner();
198
199        let lhs_blocks = lhs.blocks().len();
200        let rhs_blocks = rhs.blocks().len();
201
202        match lhs_blocks.cmp(&rhs_blocks) {
203            Ordering::Less => {
204                let diff = rhs_blocks - lhs_blocks;
205                sk.extend_radix_with_trivial_zero_blocks_lsb_assign(lhs, diff);
206            }
207            Ordering::Greater => {
208                let diff = lhs_blocks - rhs_blocks;
209                sk.extend_radix_with_trivial_zero_blocks_lsb_assign(rhs, diff);
210            }
211            Ordering::Equal => (),
212        }
213    }
214
215    fn pad_or_trim_ciphertext(&self, cipher: &mut RadixCiphertext, len: usize) {
216        let sk = self.inner();
217
218        let cipher_len = cipher.blocks().len();
219
220        match cipher_len.cmp(&len) {
221            Ordering::Less => {
222                let diff = len - cipher_len;
223                sk.extend_radix_with_trivial_zero_blocks_msb_assign(cipher, diff);
224            }
225            Ordering::Greater => {
226                let diff = cipher_len - len;
227                sk.trim_radix_blocks_msb_assign(cipher, diff);
228            }
229            Ordering::Equal => (),
230        }
231    }
232
233    fn trim_ciphertexts_lsb(&self, lhs: &mut RadixCiphertext, rhs: &mut RadixCiphertext) {
234        let sk = self.inner();
235
236        let lhs_blocks = lhs.blocks().len();
237        let rhs_blocks = rhs.blocks().len();
238
239        match lhs_blocks.cmp(&rhs_blocks) {
240            Ordering::Less => {
241                let diff = rhs_blocks - lhs_blocks;
242                sk.trim_radix_blocks_lsb_assign(rhs, diff);
243            }
244            Ordering::Greater => {
245                let diff = lhs_blocks - rhs_blocks;
246                sk.trim_radix_blocks_lsb_assign(lhs, diff);
247            }
248            Ordering::Equal => (),
249        }
250    }
251
252    fn conditional_string(
253        &self,
254        condition: &BooleanBlock,
255        true_ct: &FheString,
256        false_ct: &FheString,
257    ) -> FheString {
258        let sk = self.inner();
259
260        let mut true_ct = true_ct.clone();
261        let mut false_ct = false_ct.clone();
262
263        self.pad_strings(&mut true_ct, &mut false_ct);
264
265        let true_is_padded = true_ct.is_padded();
266        let false_is_padded = false_ct.is_padded();
267
268        let true_ct_uint = true_ct.into_uint();
269        let false_ct_uint = false_ct.into_uint();
270
271        let result_uint = sk.if_then_else_parallelized(condition, &true_ct_uint, &false_ct_uint);
272
273        let mut result = FheString::from_uint(result_uint, false);
274
275        match (true_is_padded, false_is_padded) {
276            (true, true) => {
277                result.set_is_padded(true);
278            }
279            (true, false) | (false, true) => {
280                // We don't know  if the result is padded or not.
281                // We ensure that it is padded by adding a single null.
282                result.append_null(self);
283            }
284            (false, false) => {}
285        }
286
287        result
288    }
289
290    fn pad_strings(&self, rhs: &mut FheString, lhs: &mut FheString) {
291        loop {
292            match rhs.len().cmp(&lhs.len()) {
293                Ordering::Less => rhs.append_null(self),
294                Ordering::Equal => break,
295                Ordering::Greater => lhs.append_null(self),
296            }
297        }
298    }
299
300    fn left_shift_chars(&self, str: &FheString, shift: &RadixCiphertext) -> FheString {
301        let sk = self.inner();
302
303        let uint = str.to_uint();
304        let mut shift_bits = sk.scalar_left_shift_parallelized(shift, 3);
305
306        // `shift_bits` needs to have the same block len as `uint` for the tfhe-rs shift to work
307        self.pad_or_trim_ciphertext(&mut shift_bits, uint.blocks().len());
308
309        let len = uint.blocks.len();
310
311        let shifted = if len == 0 {
312            uint
313        } else {
314            sk.left_shift_parallelized(&uint, &shift_bits)
315        };
316
317        // If the shifting amount is >= than the str length we get zero i.e. all chars are out of
318        // range (instead of wrapping, which is the behavior of Rust and tfhe-rs)
319        let bit_len = (str.len() * 8) as u32;
320        let shift_ge_than_str = sk.scalar_ge_parallelized(&shift_bits, bit_len);
321
322        let result = sk.if_then_else_parallelized(
323            &shift_ge_than_str,
324            &sk.create_trivial_zero_radix(len),
325            &shifted,
326        );
327
328        FheString::from_uint(result, false)
329    }
330
331    fn right_shift_chars(&self, str: &FheString, shift: &RadixCiphertext) -> FheString {
332        let sk = self.inner();
333
334        let uint = str.to_uint();
335        let mut shift_bits = sk.scalar_left_shift_parallelized(shift, 3);
336
337        // `shift_bits` needs to have the same block len as `uint` for the tfhe-rs shift to work
338        self.pad_or_trim_ciphertext(&mut shift_bits, uint.blocks().len());
339
340        let len = uint.blocks().len();
341
342        let shifted = if len == 0 {
343            uint
344        } else {
345            sk.right_shift_parallelized(&uint, &shift_bits)
346        };
347
348        // If the shifting amount is >= than the str length we get zero i.e. all chars are out of
349        // range (instead of wrapping, which is the behavior of Rust and tfhe-rs)
350        let bit_len = (str.len() * 8) as u32;
351        let shift_ge_than_str = sk.scalar_ge_parallelized(&shift_bits, bit_len);
352
353        let result = sk.if_then_else_parallelized(
354            &shift_ge_than_str,
355            &sk.create_trivial_zero_radix(len),
356            &shifted,
357        );
358
359        FheString::from_uint(result, false)
360    }
361}
362
363pub trait FheStringIterator<T: Borrow<IntegerServerKey> + Sync> {
364    fn next(&mut self, sk: &ServerKey<T>) -> (FheString, BooleanBlock);
365}