mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-09 14:47:56 -05:00
refactor(strings): move fhe_strings from examples to strings module
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
# FHE Strings
|
||||
|
||||
This example contains the implementation of a str API in FHE, featuring 30 methods. This API allows the user to:
|
||||
* Encrypt the `str` with or without padding nulls (i.e. encrypted `0u8`s at the end of the string), which serve to obfuscate the length but are ignored by algorithms
|
||||
* Encrypt any kind of pattern (`pat`, `from`, `to`, `rhs`) with or without padding nulls
|
||||
* Encrypt the number of repetitions `n`, allowing to provide a clear `max` to restrict the range of the encrypted `n`
|
||||
* Provide a cleartext pattern when algorithms can run faster. Otherwise, it's possible to trivially encrypt the pattern with `FheString::trivial`
|
||||
|
||||
Encrypted strings contain a flag indicating whether they have padding nulls or not. Algorithms are optimized to differentiate between the two kind of strings. For instance, in some cases we can skip entirely the FHE computations if we know the true lengths of the string or pattern.
|
||||
|
||||
Just like the clear str API, any encrypted string returned by a function can be used as input to other functions. For instance when `trim_start` is executed, or a `Split` iterator instance is advanced with `next`, the result will only have nulls at the end. The decryption function `decrypt_ascii` will panic if it encounters with malformed encrypted strings, including padding inconsistencies.
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
let (ck, sk) = gen_keys();
|
||||
let s = "Zama ";
|
||||
let padding = Some(2);
|
||||
|
||||
let enc_s = FheString::new(&ck, &s, padding);
|
||||
let clear_count = UIntArg::Clear(3);
|
||||
|
||||
// All the nulls are shifted to the right end
|
||||
let result_repeat = sk.repeat(&enc_s, &clear_count);
|
||||
let result_trim_end = sk.trim_end(&result_repeat);
|
||||
let result_uppercase = sk.to_uppercase(&result_trim_end);
|
||||
|
||||
let clear = ck.decrypt_ascii(&result_uppercase);
|
||||
|
||||
assert_eq!(clear, "ZAMA ZAMA ZAMA");
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
We have implemented conversions between encrypted strings (`FheString`) and UInts (`RadixCiphertext`). This is useful for:
|
||||
|
||||
- Speeding up comparisons and pattern matching: We perform a _single comparison_ between two numbers. This is more efficient than many u8 comparisons.
|
||||
- Shifting by an encrypted number of characters: By treating the string as a `RadixCiphertext` we can use the tfhe-rs shifting operations, and then convert back to `FheString`.
|
||||
|
||||
Similarly, when a pattern is provided in the clear (`ClearString`) we convert it to `StaticUnsignedBigInt<N>`. This type requires a constant `N` for the u64 length of the clear UInt, and we have set it to 4, allowing for up to 32 characters in `ClearString`.
|
||||
|
||||
`N` can be increased in `main.rs` to enable longer clear patterns, or reduced to improve performance (if we know that we will work with smaller clear patterns).
|
||||
|
||||
## Test Cases
|
||||
|
||||
We have handled corner cases like empty strings and empty patterns (with and without padding), the number of repetitions `n` (clear and encrypted) being zero, etc. A complete list of tests can be found at `assert_functions/test_vectors.rs`.
|
||||
|
||||
## Usage
|
||||
To run all the functions and see the comparison with the clear Rust API you can specify the following arguments:
|
||||
|
||||
```--str <"your str"> --pat <"your pattern"> --to <"argument used in replace"> --rhs <"used in comparisons and concat"> --n <number of repetitions> --max <clear max n>```
|
||||
|
||||
To optionally specify a number of padding nulls for any argument you can also use: ``--str_pad``, ``--pat_pad``, ``--to_pad`` and ``--rhs_pad``.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,471 +0,0 @@
|
||||
use crate::Keys;
|
||||
|
||||
const TEST_CASES_MATCH: [(&str, u32); 15] = [
|
||||
("", 0),
|
||||
("", 1),
|
||||
("", 2),
|
||||
("", 3),
|
||||
("a", 0),
|
||||
("e", 1),
|
||||
("o", 2),
|
||||
(" ", 2),
|
||||
("?", 3),
|
||||
("<3", 3),
|
||||
("foo", 0),
|
||||
("foofoo", 0),
|
||||
("foofoo", 1),
|
||||
(" don't", 12),
|
||||
("What is <3? Baby don't hurt me", 0),
|
||||
];
|
||||
|
||||
const TEST_WHITESPACE: [(&str, u32); 17] = [
|
||||
("", 0),
|
||||
("", 1),
|
||||
("", 2),
|
||||
("", 3),
|
||||
(" ", 0),
|
||||
(" ", 1),
|
||||
("\n", 0),
|
||||
("\n", 1),
|
||||
("\t", 0),
|
||||
("\t", 1),
|
||||
("\r", 0),
|
||||
("\r", 1),
|
||||
("\u{000C}", 0),
|
||||
("\u{000C}", 1),
|
||||
("viv4_crist0_rey!", 1),
|
||||
(
|
||||
" \t\u{000C}\r\n viv4 crist0\t\r\u{000C}\n rey! \t\u{000C}\n \r",
|
||||
0,
|
||||
),
|
||||
(
|
||||
" \t\u{000C}\r\n viv4 crist0\t\r\u{000C}\n rey! \t\u{000C}\n \r",
|
||||
1,
|
||||
),
|
||||
];
|
||||
|
||||
const TEST_CASES_COMP: [(&str, u32); 15] = [
|
||||
("", 0),
|
||||
("", 1),
|
||||
("", 2),
|
||||
("", 3),
|
||||
("a", 0),
|
||||
("a", 1),
|
||||
("a", 10),
|
||||
("foo", 0),
|
||||
("foofoo4", 0),
|
||||
("foofoo4", 1),
|
||||
("foofoo4", 2),
|
||||
("FooFoo4", 0),
|
||||
("FooFoo4", 1),
|
||||
("foofoo44", 0),
|
||||
("foofoo44", 1),
|
||||
];
|
||||
|
||||
const TEST_CASES_SPLIT: [((&str, u32), (&str, u32)); 21] = [
|
||||
// Empty strings and patterns with different paddings to test edge cases
|
||||
(("", 0), ("", 0)),
|
||||
(("", 0), ("", 1)),
|
||||
(("", 0), ("", 2)),
|
||||
(("", 1), ("", 0)),
|
||||
(("", 1), ("", 1)),
|
||||
(("", 1), ("", 2)),
|
||||
(("", 2), ("", 0)),
|
||||
(("", 2), ("", 1)),
|
||||
(("", 2), ("", 2)),
|
||||
// More edge cases involving the empty string and pattern
|
||||
(("", 0), ("a", 0)),
|
||||
(("", 1), ("a", 0)),
|
||||
(("", 2), ("a", 0)),
|
||||
(("Kikwi", 0), ("", 0)),
|
||||
(("Bucha", 0), ("", 1)),
|
||||
(("Yerbal", 0), ("", 2)),
|
||||
(("aaa", 0), ("a", 0)),
|
||||
(("aaa", 0), ("aa", 0)),
|
||||
(("Deep Woods", 0), ("woods", 0)),
|
||||
(("Skyview Temple", 0), ("e", 2)),
|
||||
(("Lake.Floria.", 2), (".", 1)),
|
||||
(("Ghirahim", 2), ("hi", 0)),
|
||||
];
|
||||
|
||||
const TEST_CASES_REPLACE: [((&str, u32), (&str, u32), (&str, u32)); 27] = [
|
||||
// Empty string matches with different padding combinations
|
||||
(("", 0), ("", 0), ("", 0)),
|
||||
(("", 1), ("", 0), ("", 0)),
|
||||
(("", 2), ("", 0), ("", 0)),
|
||||
(("", 0), ("", 1), ("{}", 0)),
|
||||
(("", 1), ("", 1), ("{}", 0)),
|
||||
(("", 2), ("", 1), ("{}", 0)),
|
||||
(("", 0), ("", 2), ("<3", 0)),
|
||||
(("", 1), ("", 2), ("<3", 0)),
|
||||
(("", 2), ("", 2), ("<3", 0)),
|
||||
(("aa", 0), ("", 0), ("|", 0)),
|
||||
(("aa", 0), ("", 1), ("|", 0)),
|
||||
(("aa", 0), ("", 2), ("|", 0)),
|
||||
(("aa", 0), ("", 2), ("|", 1)),
|
||||
(("aa", 1), ("", 2), ("|empty|", 1)),
|
||||
// Non-empty string matches
|
||||
(("a", 0), ("a", 0), ("A", 0)),
|
||||
(("a", 2), ("a", 2), ("A", 1)),
|
||||
(("@1@2", 0), ("@", 2), ("", 0)),
|
||||
(("@1@2", 0), ("@", 2), ("", 2)),
|
||||
(("Bokob", 0), ("Boko", 0), ("Bul", 0)),
|
||||
// Cases where `from` is contained in `to`
|
||||
(("abab", 1), ("b", 0), ("ab", 0)),
|
||||
(("Keese", 1), ("e", 1), ("ee", 0)),
|
||||
(("Keese", 1), ("ee", 0), ("Keese", 1)),
|
||||
// Cases with no match
|
||||
(("Keese", 1), ("Keesee", 0), ("Keese", 0)),
|
||||
(("Keese", 1), ("k", 0), ("K", 0)),
|
||||
(("", 0), ("k", 0), ("K", 0)),
|
||||
(("", 1), ("k", 0), ("K", 0)),
|
||||
(("", 2), ("k", 0), ("K", 0)),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_len(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_empty() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_is_empty(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_encrypt_decrypt(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_contains(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ends_with() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_ends_with(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_starts_with() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_starts_with(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_prefix() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_strip_prefix(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_suffix() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_strip_suffix(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rfind() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_rfind(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find() {
|
||||
let keys = Keys::new();
|
||||
|
||||
// 225 different cases
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_find(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_WHITESPACE {
|
||||
keys.assert_trim(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_start() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_WHITESPACE {
|
||||
keys.assert_trim_start(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_end() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_WHITESPACE {
|
||||
keys.assert_trim_end(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comparisons() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_COMP {
|
||||
for (rhs, rhs_pad) in TEST_CASES_COMP {
|
||||
keys.assert_comp(str, Some(str_pad), rhs, Some(rhs_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_lowercase() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_COMP {
|
||||
keys.assert_to_lowercase(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_uppercase() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_COMP {
|
||||
keys.assert_to_uppercase(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq_ignore_case() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_COMP {
|
||||
for (rhs, rhs_pad) in TEST_CASES_COMP {
|
||||
keys.assert_eq_ignore_case(str, Some(str_pad), rhs, Some(rhs_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_ascii_whitespace() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_WHITESPACE {
|
||||
keys.assert_split_ascii_whitespace(str, Some(str_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsplit_once() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_rsplit_once(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_once() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in TEST_CASES_MATCH {
|
||||
for (pat, pat_pad) in TEST_CASES_MATCH {
|
||||
keys.assert_split_once(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsplit_real() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
keys.assert_rsplit(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_real() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
keys.assert_split(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsplitn() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
for n in 0..=3 {
|
||||
keys.assert_rsplitn(str, Some(str_pad), pat, Some(pat_pad), n, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_splitn() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
for n in 0..=3 {
|
||||
keys.assert_splitn(str, Some(str_pad), pat, Some(pat_pad), n, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_terminator() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
keys.assert_split_terminator(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsplit_terminator() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
keys.assert_rsplit_terminator(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_inclusive() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (pat, pat_pad)) in TEST_CASES_SPLIT {
|
||||
keys.assert_split_inclusive(str, Some(str_pad), pat, Some(pat_pad));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for (str, str_pad) in [
|
||||
("", 0),
|
||||
("", 1),
|
||||
("", 2),
|
||||
("A", 0),
|
||||
("a", 1),
|
||||
("Techno", 0),
|
||||
("Cursed", 2),
|
||||
] {
|
||||
for (rhs, rhs_pad) in [
|
||||
("", 0),
|
||||
("", 1),
|
||||
("", 2),
|
||||
("W", 0),
|
||||
(" ", 1),
|
||||
(" Bokoblins", 0),
|
||||
("blins", 3),
|
||||
] {
|
||||
keys.assert_concat(str, Some(str_pad), rhs, Some(rhs_pad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repeat() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), n) in [
|
||||
(("", 0), 3),
|
||||
(("", 1), 3),
|
||||
(("", 2), 3),
|
||||
(("A", 0), 4),
|
||||
(("a", 1), 4),
|
||||
(("Yiga", 1), 0),
|
||||
(("Yiga", 1), 1),
|
||||
(("Eldin", 2), 2),
|
||||
(("<Gerudo Highlands>", 0), 4),
|
||||
] {
|
||||
keys.assert_repeat(str, Some(str_pad), n, 4);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replacen() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (from, from_pad), (to, to_pad)) in TEST_CASES_REPLACE {
|
||||
for n in 0..=3 {
|
||||
keys.assert_replacen(
|
||||
(str, Some(str_pad)),
|
||||
(from, Some(from_pad)),
|
||||
(to, Some(to_pad)),
|
||||
n,
|
||||
3,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_real() {
|
||||
let keys = Keys::new();
|
||||
|
||||
for ((str, str_pad), (from, from_pad), (to, to_pad)) in TEST_CASES_REPLACE {
|
||||
keys.assert_replace(str, Some(str_pad), from, Some(from_pad), to, Some(to_pad));
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
use crate::client_key::{ClientKey, EncU16};
|
||||
use crate::server_key::ServerKey;
|
||||
use crate::N;
|
||||
use tfhe::integer::{IntegerCiphertext, IntegerRadixCiphertext, RadixCiphertext};
|
||||
|
||||
/// Represents a encrypted ASCII character.
|
||||
#[derive(Clone)]
|
||||
pub struct FheAsciiChar {
|
||||
pub enc_char: RadixCiphertext,
|
||||
}
|
||||
|
||||
/// Represents a encrypted string made up of [`FheAsciiChar`]s.
|
||||
#[derive(Clone)]
|
||||
pub struct FheString {
|
||||
pub enc_string: Vec<FheAsciiChar>,
|
||||
pub padded: bool,
|
||||
}
|
||||
|
||||
// For str functions that require unsigned integers as arguments
|
||||
pub enum UIntArg {
|
||||
Clear(u16),
|
||||
Enc(EncU16),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClearString {
|
||||
str: String,
|
||||
}
|
||||
|
||||
impl ClearString {
|
||||
pub fn new(str: String) -> Self {
|
||||
assert!(str.is_ascii() && !str.contains('\0'));
|
||||
assert!(str.len() <= N * 8);
|
||||
|
||||
ClearString { str }
|
||||
}
|
||||
|
||||
pub fn str(&self) -> &str {
|
||||
&self.str
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum GenericPattern {
|
||||
Clear(ClearString),
|
||||
Enc(FheString),
|
||||
}
|
||||
|
||||
impl FheAsciiChar {
|
||||
pub fn ciphertext(&self) -> &RadixCiphertext {
|
||||
&self.enc_char
|
||||
}
|
||||
|
||||
pub fn ciphertext_mut(&mut self) -> &mut RadixCiphertext {
|
||||
&mut self.enc_char
|
||||
}
|
||||
|
||||
pub fn null(sk: &ServerKey) -> Self {
|
||||
FheAsciiChar {
|
||||
enc_char: sk.key().create_trivial_zero_radix(4),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FheString {
|
||||
/// Constructs a new `FheString` from a plaintext string, a [`ClientKey`] and an optional
|
||||
/// padding length.
|
||||
///
|
||||
/// Utilizes [`ClientKey::encrypt_ascii`] for the encryption.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the provided string is not ASCII.
|
||||
pub fn new(client_key: &ClientKey, str: &str, padding: Option<u32>) -> Self {
|
||||
client_key.encrypt_ascii(str, padding)
|
||||
}
|
||||
|
||||
pub fn trivial(server_key: &ServerKey, str: &str) -> Self {
|
||||
assert!(str.is_ascii() & !str.contains('\0'));
|
||||
|
||||
let enc_string: Vec<_> = str
|
||||
.bytes()
|
||||
.map(|char| FheAsciiChar {
|
||||
enc_char: server_key.key().create_trivial_radix(char, 4),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
enc_string,
|
||||
padded: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chars(&self) -> &[FheAsciiChar] {
|
||||
&self.enc_string
|
||||
}
|
||||
|
||||
pub fn chars_mut(&mut self) -> &mut [FheAsciiChar] {
|
||||
&mut self.enc_string
|
||||
}
|
||||
|
||||
pub fn chars_vec(&mut self) -> &mut Vec<FheAsciiChar> {
|
||||
&mut self.enc_string
|
||||
}
|
||||
|
||||
pub fn is_padded(&self) -> bool {
|
||||
self.padded
|
||||
}
|
||||
|
||||
pub fn set_is_padded(&mut self, to: bool) {
|
||||
self.padded = to;
|
||||
}
|
||||
|
||||
// Converts a `RadixCiphertext` to a `FheString`, building a `FheAsciiChar` for each 4 blocks.
|
||||
// Panics if the uint doesn't have a number of blocks that is multiple of 4.
|
||||
pub fn from_uint(uint: RadixCiphertext, padded: bool) -> FheString {
|
||||
let blocks_len = uint.blocks().len();
|
||||
assert_eq!(blocks_len % 4, 0);
|
||||
|
||||
let mut ciphertexts = uint.into_blocks().into_iter().rev();
|
||||
|
||||
let mut ascii_vec = vec![];
|
||||
|
||||
for _ in 0..blocks_len / 4 {
|
||||
let mut byte_vec: Vec<_> = ciphertexts.by_ref().take(4).collect();
|
||||
byte_vec.reverse();
|
||||
|
||||
let byte = RadixCiphertext::from_blocks(byte_vec);
|
||||
|
||||
ascii_vec.push(FheAsciiChar { enc_char: byte })
|
||||
}
|
||||
|
||||
FheString {
|
||||
enc_string: ascii_vec,
|
||||
padded,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a `FheString` to a `RadixCiphertext`, taking 4 blocks for each `FheAsciiChar`.
|
||||
// We can then use a single large uint, that represents a string, in tfhe-rs operations.
|
||||
pub fn to_uint(&self, sk: &ServerKey) -> RadixCiphertext {
|
||||
self.clone().into_uint(sk)
|
||||
}
|
||||
|
||||
pub fn into_uint(self, sk: &ServerKey) -> RadixCiphertext {
|
||||
let blocks: Vec<_> = self
|
||||
.enc_string
|
||||
.into_iter()
|
||||
.rev()
|
||||
.flat_map(|c| c.enc_char.into_blocks())
|
||||
.collect();
|
||||
|
||||
let mut uint = RadixCiphertext::from_blocks(blocks);
|
||||
|
||||
if uint.blocks().is_empty() {
|
||||
sk.key()
|
||||
.extend_radix_with_trivial_zero_blocks_lsb_assign(&mut uint, 4);
|
||||
}
|
||||
|
||||
uint
|
||||
}
|
||||
|
||||
/// Makes the string padded. Useful for when a string is potentially padded and we need to
|
||||
/// ensure it's actually padded.
|
||||
pub fn append_null(&mut self, sk: &ServerKey) {
|
||||
let null = FheAsciiChar::null(sk);
|
||||
|
||||
self.enc_string.push(null);
|
||||
|
||||
self.padded = true;
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.chars().len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0 || (self.is_padded() && self.len() == 1)
|
||||
}
|
||||
|
||||
pub fn empty() -> FheString {
|
||||
FheString {
|
||||
enc_string: vec![],
|
||||
padded: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::server_key::gen_keys;
|
||||
|
||||
#[test]
|
||||
fn test_uint_conversion() {
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let str =
|
||||
"Los Sheikah fueron originalmente criados de la Diosa Hylia antes del sellado del \
|
||||
Heraldo de la Muerte.";
|
||||
|
||||
let enc = FheString::new(&ck, str, Some(7));
|
||||
|
||||
let uint = enc.to_uint(&sk);
|
||||
let mut converted = FheString::from_uint(uint, false);
|
||||
converted.set_is_padded(true);
|
||||
let dec = ck.decrypt_ascii(&converted);
|
||||
|
||||
assert_eq!(dec, str);
|
||||
|
||||
let uint_into = enc.into_uint(&sk);
|
||||
let mut converted = FheString::from_uint(uint_into, false);
|
||||
converted.set_is_padded(true);
|
||||
let dec = ck.decrypt_ascii(&converted);
|
||||
|
||||
assert_eq!(dec, str);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
use crate::ciphertext::{FheAsciiChar, FheString};
|
||||
use tfhe::integer::{ClientKey as FheClientKey, RadixCiphertext};
|
||||
use tfhe::shortint::prelude::PARAM_MESSAGE_2_CARRY_2;
|
||||
|
||||
/// Represents a client key for encryption and decryption of strings.
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct ClientKey {
|
||||
key: FheClientKey,
|
||||
}
|
||||
|
||||
/// Encrypted u16 value. It contains an optional `max` to restrict the range of the value.
|
||||
pub struct EncU16 {
|
||||
cipher: RadixCiphertext,
|
||||
max: Option<u16>,
|
||||
}
|
||||
|
||||
impl EncU16 {
|
||||
pub fn cipher(&self) -> &RadixCiphertext {
|
||||
&self.cipher
|
||||
}
|
||||
|
||||
pub fn max(&self) -> Option<u16> {
|
||||
self.max
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientKey {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
key: FheClientKey::new(PARAM_MESSAGE_2_CARRY_2),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&self) -> &FheClientKey {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Encrypts an ASCII string, optionally padding it with the specified amount of 0s, and returns
|
||||
/// an [`FheString`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the provided string is not ASCII or contains null characters
|
||||
/// "\0".
|
||||
pub fn encrypt_ascii(&self, str: &str, padding: Option<u32>) -> FheString {
|
||||
assert!(str.is_ascii() & !str.contains('\0'));
|
||||
|
||||
let padded = padding.map_or(false, |p| p != 0);
|
||||
|
||||
let mut enc_string: Vec<_> = str
|
||||
.bytes()
|
||||
.map(|char| FheAsciiChar {
|
||||
enc_char: self.key.encrypt_radix(char, 4),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Optional padding
|
||||
if let Some(count) = padding {
|
||||
let null = (0..count).map(|_| FheAsciiChar {
|
||||
enc_char: self.key.encrypt_radix(0u8, 4),
|
||||
});
|
||||
|
||||
enc_string.extend(null);
|
||||
}
|
||||
|
||||
FheString { enc_string, padded }
|
||||
}
|
||||
|
||||
/// Decrypts a `FheString`, removes any padding and returns the ASCII string.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the decrypted string is not ASCII or the `FheString` padding
|
||||
/// flag doesn't match the actual string.
|
||||
pub fn decrypt_ascii(&self, enc_str: &FheString) -> String {
|
||||
let padded_flag = enc_str.is_padded();
|
||||
let mut prev_was_null = false;
|
||||
|
||||
let bytes: Vec<_> = enc_str
|
||||
.chars()
|
||||
.iter()
|
||||
.filter_map(|enc_char| {
|
||||
let byte = self.key.decrypt_radix(enc_char.ciphertext());
|
||||
|
||||
if byte == 0 {
|
||||
prev_was_null = true;
|
||||
|
||||
assert!(padded_flag, "NULL FOUND BUT PADDED FLAG WAS FALSE");
|
||||
} else {
|
||||
assert!(!prev_was_null, "NON ZERO CHAR AFTER A NULL");
|
||||
|
||||
prev_was_null = false;
|
||||
}
|
||||
|
||||
if byte != 0 {
|
||||
Some(byte)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if padded_flag {
|
||||
assert!(
|
||||
prev_was_null,
|
||||
"LAST CHAR WAS NOT NULL BUT PADDING FLAG WAS SET"
|
||||
)
|
||||
}
|
||||
|
||||
String::from_utf8(bytes).unwrap()
|
||||
}
|
||||
|
||||
/// Encrypts a u16 value. It also takes an optional `max` value to restrict the range
|
||||
/// of the encrypted u16.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the u16 value exceeds the provided `max`.
|
||||
pub fn encrypt_u16(&self, val: u16, max: Option<u16>) -> EncU16 {
|
||||
if let Some(max_val) = max {
|
||||
assert!(val <= max_val, "val cannot be greater than max")
|
||||
}
|
||||
|
||||
EncU16 {
|
||||
cipher: self.key.encrypt_radix(val, 8),
|
||||
max,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
use crate::ciphertext::{FheString, UIntArg};
|
||||
use crate::client_key::ClientKey;
|
||||
use crate::server_key::{gen_keys, FheStringIsEmpty, FheStringIterator, FheStringLen, ServerKey};
|
||||
use clap::{value_parser, Arg, Command};
|
||||
use std::time::Instant;
|
||||
|
||||
mod ciphertext;
|
||||
mod client_key;
|
||||
mod server_key;
|
||||
|
||||
mod assert_functions;
|
||||
|
||||
// Used as the const argument for StaticUnsignedBigInt, specifying the max u64 length of a
|
||||
// ClearString
|
||||
const N: usize = 4;
|
||||
|
||||
fn main() {
|
||||
let matches = Command::new("FHE str API")
|
||||
.arg(
|
||||
Arg::new("string")
|
||||
.long("str")
|
||||
.help("str that will be used for the functions")
|
||||
.allow_hyphen_values(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("string_padding")
|
||||
.long("str_pad")
|
||||
.help("the number of padding nulls to use in str")
|
||||
.value_parser(value_parser!(u32))
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pattern")
|
||||
.long("pat")
|
||||
.help("pat that will be used for the functions")
|
||||
.allow_hyphen_values(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pattern_padding")
|
||||
.long("pat_pad")
|
||||
.help("the number of padding nulls to use in pat")
|
||||
.value_parser(value_parser!(u32))
|
||||
)
|
||||
.arg(
|
||||
Arg::new("to")
|
||||
.long("to")
|
||||
.help("the argument used to replace the pattern in `replace` and `replacen`")
|
||||
.allow_hyphen_values(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("to_padding")
|
||||
.long("to_pad")
|
||||
.help("The number of padding nulls to use in to")
|
||||
.value_parser(value_parser!(u32))
|
||||
)
|
||||
.arg(
|
||||
Arg::new("rhs")
|
||||
.long("rhs")
|
||||
.help("The right side string used in `concat` and all comparisons")
|
||||
.allow_hyphen_values(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("rhs_padding")
|
||||
.long("rhs_pad")
|
||||
.help("The number of padding nulls to use in rhs")
|
||||
.value_parser(value_parser!(u32))
|
||||
)
|
||||
.arg(
|
||||
Arg::new("count")
|
||||
.long("n")
|
||||
.help("The count number that will be used in some functions like `replacen` and `splitn`")
|
||||
.value_parser(value_parser!(u16))
|
||||
.required(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("max_count")
|
||||
.long("max")
|
||||
.help("The max number that the n argument can take (useful for when n is encrypted, \
|
||||
as we need a worst case scenario of how many times we will repeat something)")
|
||||
.value_parser(value_parser!(u16))
|
||||
.required(true)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let str = matches.get_one::<String>("string").unwrap();
|
||||
let str_pad = matches.get_one("string_padding").copied();
|
||||
|
||||
let pat = matches.get_one::<String>("pattern").unwrap();
|
||||
let pat_pad = matches.get_one("pattern_padding").copied();
|
||||
|
||||
let to = matches.get_one::<String>("to").unwrap();
|
||||
let to_pad: Option<u32> = matches.get_one("to_padding").copied();
|
||||
|
||||
let rhs = matches.get_one::<String>("rhs").unwrap();
|
||||
let rhs_pad = matches.get_one("rhs_padding").copied();
|
||||
|
||||
let n: u16 = matches.get_one("count").copied().unwrap();
|
||||
let max: u16 = matches.get_one("max_count").copied().unwrap();
|
||||
|
||||
let keys = Keys::new();
|
||||
|
||||
keys.assert_len(str, str_pad);
|
||||
keys.assert_is_empty(str, str_pad);
|
||||
|
||||
keys.assert_encrypt_decrypt(str, str_pad);
|
||||
|
||||
keys.assert_contains(str, str_pad, pat, pat_pad);
|
||||
keys.assert_ends_with(str, str_pad, pat, pat_pad);
|
||||
keys.assert_starts_with(str, str_pad, pat, pat_pad);
|
||||
|
||||
keys.assert_find(str, str_pad, pat, pat_pad);
|
||||
keys.assert_rfind(str, str_pad, pat, pat_pad);
|
||||
|
||||
keys.assert_strip_prefix(str, str_pad, pat, pat_pad);
|
||||
keys.assert_strip_suffix(str, str_pad, pat, pat_pad);
|
||||
|
||||
keys.assert_eq_ignore_case(str, str_pad, rhs, rhs_pad);
|
||||
keys.assert_comp(str, str_pad, rhs, rhs_pad);
|
||||
|
||||
keys.assert_to_lowercase(str, str_pad);
|
||||
keys.assert_to_uppercase(str, str_pad);
|
||||
|
||||
keys.assert_concat(str, str_pad, rhs, rhs_pad);
|
||||
keys.assert_repeat(str, str_pad, n, max);
|
||||
|
||||
keys.assert_trim_end(str, str_pad);
|
||||
keys.assert_trim_start(str, str_pad);
|
||||
keys.assert_trim(str, str_pad);
|
||||
keys.assert_split_ascii_whitespace(str, str_pad);
|
||||
|
||||
keys.assert_split_once(str, str_pad, pat, pat_pad);
|
||||
keys.assert_rsplit_once(str, str_pad, pat, pat_pad);
|
||||
|
||||
keys.assert_split(str, str_pad, pat, pat_pad);
|
||||
keys.assert_rsplit(str, str_pad, pat, pat_pad);
|
||||
|
||||
keys.assert_split_terminator(str, str_pad, pat, pat_pad);
|
||||
keys.assert_rsplit_terminator(str, str_pad, pat, pat_pad);
|
||||
keys.assert_split_inclusive(str, str_pad, pat, pat_pad);
|
||||
|
||||
keys.assert_splitn(str, str_pad, pat, pat_pad, n, max);
|
||||
keys.assert_rsplitn(str, str_pad, pat, pat_pad, n, max);
|
||||
|
||||
keys.assert_replace(str, str_pad, pat, pat_pad, to, to_pad);
|
||||
keys.assert_replacen((str, str_pad), (pat, pat_pad), (to, to_pad), n, max);
|
||||
}
|
||||
|
||||
struct Keys {
|
||||
ck: ClientKey,
|
||||
sk: ServerKey,
|
||||
}
|
||||
|
||||
impl Keys {
|
||||
fn new() -> Self {
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
Keys { ck, sk }
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
use crate::ciphertext::{FheString, GenericPattern};
|
||||
use crate::server_key::{FheStringIsEmpty, ServerKey};
|
||||
use tfhe::integer::BooleanBlock;
|
||||
|
||||
impl ServerKey {
|
||||
fn eq_length_checks(&self, lhs: &FheString, rhs: &FheString) -> Option<BooleanBlock> {
|
||||
let lhs_len = lhs.len();
|
||||
let rhs_len = rhs.len();
|
||||
|
||||
// If lhs is empty, rhs must also be empty in order to be equal (the case where lhs is
|
||||
// empty with > 1 padding zeros is handled next)
|
||||
if lhs_len == 0 || (lhs.is_padded() && lhs_len == 1) {
|
||||
return match self.is_empty(rhs) {
|
||||
FheStringIsEmpty::Padding(enc_val) => Some(enc_val),
|
||||
FheStringIsEmpty::NoPadding(val) => {
|
||||
Some(self.key.create_trivial_boolean_block(val))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If rhs is empty, lhs must also be empty in order to be equal (only case remaining is if
|
||||
// lhs padding zeros > 1)
|
||||
if rhs_len == 0 || (rhs.is_padded() && rhs_len == 1) {
|
||||
return match self.is_empty(lhs) {
|
||||
FheStringIsEmpty::Padding(enc_val) => Some(enc_val),
|
||||
_ => Some(self.key.create_trivial_boolean_block(false)),
|
||||
};
|
||||
}
|
||||
|
||||
// Two strings without padding that have different lengths cannot be equal
|
||||
if (!lhs.is_padded() && !rhs.is_padded()) && (lhs.len() != rhs.len()) {
|
||||
return Some(self.key.create_trivial_boolean_block(false));
|
||||
}
|
||||
|
||||
// A string without padding cannot be equal to a string with padding that has the same or
|
||||
// lower length
|
||||
if (!lhs.is_padded() && rhs.is_padded()) && (rhs.len() <= lhs.len())
|
||||
|| (!rhs.is_padded() && lhs.is_padded()) && (lhs.len() <= rhs.len())
|
||||
{
|
||||
return Some(self.key.create_trivial_boolean_block(false));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if an encrypted string and a pattern (either encrypted or clear) are equal.
|
||||
///
|
||||
/// Returns `false` if they are not equal.
|
||||
///
|
||||
/// The pattern for comparison (`rhs`) can be specified as either `GenericPattern::Clear` for a
|
||||
/// clear string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("hello", "hello");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = GenericPattern::Enc(FheString::new(&ck, &s2, None));
|
||||
///
|
||||
/// let result = sk.eq(&enc_s1, &enc_s2);
|
||||
/// let are_equal = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(are_equal);
|
||||
/// ```
|
||||
pub fn eq(&self, lhs: &FheString, rhs: &GenericPattern) -> BooleanBlock {
|
||||
let early_return = match rhs {
|
||||
GenericPattern::Clear(rhs) => {
|
||||
self.eq_length_checks(lhs, &FheString::trivial(self, rhs.str()))
|
||||
}
|
||||
GenericPattern::Enc(rhs) => self.eq_length_checks(lhs, rhs),
|
||||
};
|
||||
|
||||
if let Some(val) = early_return {
|
||||
return val;
|
||||
}
|
||||
|
||||
let mut lhs_uint = lhs.to_uint(self);
|
||||
match rhs {
|
||||
GenericPattern::Clear(rhs) => {
|
||||
let rhs_clear_uint = self.pad_cipher_and_cleartext_lsb(&mut lhs_uint, rhs.str());
|
||||
|
||||
self.key.scalar_eq_parallelized(&lhs_uint, rhs_clear_uint)
|
||||
}
|
||||
GenericPattern::Enc(rhs) => {
|
||||
let mut rhs_uint = rhs.to_uint(self);
|
||||
|
||||
self.pad_ciphertexts_lsb(&mut lhs_uint, &mut rhs_uint);
|
||||
|
||||
self.key.eq_parallelized(&lhs_uint, &rhs_uint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if an encrypted string and a pattern (either encrypted or clear) are not
|
||||
/// equal.
|
||||
///
|
||||
/// Returns `false` if they are equal.
|
||||
///
|
||||
/// The pattern for comparison (`rhs`) can be specified as either `GenericPattern::Clear` for a
|
||||
/// clear string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("hello", "world");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = GenericPattern::Enc(FheString::new(&ck, &s2, None));
|
||||
///
|
||||
/// let result = sk.ne(&enc_s1, &enc_s2);
|
||||
/// let are_not_equal = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(are_not_equal);
|
||||
/// ```
|
||||
pub fn ne(&self, lhs: &FheString, rhs: &GenericPattern) -> BooleanBlock {
|
||||
let eq = self.eq(lhs, rhs);
|
||||
|
||||
self.key.boolean_bitnot(&eq)
|
||||
}
|
||||
|
||||
/// Returns `true` if the first encrypted string is less than the second encrypted string.
|
||||
///
|
||||
/// Returns `false` otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("apple", "banana");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = FheString::new(&ck, &s2, None);
|
||||
///
|
||||
/// let result = sk.lt(&enc_s1, &enc_s2);
|
||||
/// let is_lt = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(is_lt); // "apple" is less than "banana"
|
||||
/// ```
|
||||
pub fn lt(&self, lhs: &FheString, rhs: &FheString) -> BooleanBlock {
|
||||
let mut lhs_uint = lhs.to_uint(self);
|
||||
let mut rhs_uint = rhs.to_uint(self);
|
||||
|
||||
self.pad_ciphertexts_lsb(&mut lhs_uint, &mut rhs_uint);
|
||||
|
||||
self.key.lt_parallelized(&lhs_uint, &rhs_uint)
|
||||
}
|
||||
|
||||
/// Returns `true` if the first encrypted string is greater than the second encrypted string.
|
||||
///
|
||||
/// Returns `false` otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("banana", "apple");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = FheString::new(&ck, &s2, None);
|
||||
///
|
||||
/// let result = sk.gt(&enc_s1, &enc_s2);
|
||||
/// let is_gt = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(is_gt); // "banana" is greater than "apple"
|
||||
/// ```
|
||||
pub fn gt(&self, lhs: &FheString, rhs: &FheString) -> BooleanBlock {
|
||||
let mut lhs_uint = lhs.to_uint(self);
|
||||
let mut rhs_uint = rhs.to_uint(self);
|
||||
|
||||
self.pad_ciphertexts_lsb(&mut lhs_uint, &mut rhs_uint);
|
||||
|
||||
self.key.gt_parallelized(&lhs_uint, &rhs_uint)
|
||||
}
|
||||
|
||||
/// Returns `true` if the first encrypted string is less than or equal to the second encrypted
|
||||
/// string.
|
||||
///
|
||||
/// Returns `false` otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("apple", "banana");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = FheString::new(&ck, &s2, None);
|
||||
///
|
||||
/// let result = sk.le(&enc_s1, &enc_s2);
|
||||
/// let is_le = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(is_le); // "apple" is less than or equal to "banana"
|
||||
/// ```
|
||||
pub fn le(&self, lhs: &FheString, rhs: &FheString) -> BooleanBlock {
|
||||
let mut lhs_uint = lhs.to_uint(self);
|
||||
let mut rhs_uint = rhs.to_uint(self);
|
||||
|
||||
self.pad_ciphertexts_lsb(&mut lhs_uint, &mut rhs_uint);
|
||||
|
||||
self.key.le_parallelized(&lhs_uint, &rhs_uint)
|
||||
}
|
||||
|
||||
/// Returns `true` if the first encrypted string is greater than or equal to the second
|
||||
/// encrypted string.
|
||||
///
|
||||
/// Returns `false` otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("banana", "apple");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = FheString::new(&ck, &s2, None);
|
||||
///
|
||||
/// let result = sk.ge(&enc_s1, &enc_s2);
|
||||
/// let is_ge = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(is_ge); // "banana" is greater than or equal to "apple"
|
||||
/// ```
|
||||
pub fn ge(&self, lhs: &FheString, rhs: &FheString) -> BooleanBlock {
|
||||
let mut lhs_uint = lhs.to_uint(self);
|
||||
let mut rhs_uint = rhs.to_uint(self);
|
||||
|
||||
self.pad_ciphertexts_lsb(&mut lhs_uint, &mut rhs_uint);
|
||||
|
||||
self.key.ge_parallelized(&lhs_uint, &rhs_uint)
|
||||
}
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
mod comp;
|
||||
mod no_patterns;
|
||||
mod pattern;
|
||||
mod trim;
|
||||
|
||||
use crate::ciphertext::{FheAsciiChar, FheString};
|
||||
use crate::client_key::ClientKey;
|
||||
use crate::N;
|
||||
use rayon::prelude::*;
|
||||
use std::cmp::Ordering;
|
||||
use tfhe::integer::bigint::static_unsigned::StaticUnsignedBigInt;
|
||||
use tfhe::integer::prelude::*;
|
||||
use tfhe::integer::{BooleanBlock, RadixCiphertext, ServerKey as FheServerKey};
|
||||
|
||||
/// Represents a server key to operate homomorphically on [`FheString`].
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct ServerKey {
|
||||
key: FheServerKey,
|
||||
}
|
||||
|
||||
pub fn gen_keys() -> (ClientKey, ServerKey) {
|
||||
let ck = ClientKey::new();
|
||||
let sk = ServerKey::new(&ck);
|
||||
|
||||
(ck, sk)
|
||||
}
|
||||
|
||||
impl ServerKey {
|
||||
pub fn new(from: &ClientKey) -> Self {
|
||||
Self {
|
||||
key: FheServerKey::new_radix_server_key(from.key()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&self) -> &FheServerKey {
|
||||
&self.key
|
||||
}
|
||||
}
|
||||
|
||||
// With no padding, the length is just the vector's length (clear result). With padding it requires
|
||||
// homomorphically counting the non zero elements (encrypted result).
|
||||
pub enum FheStringLen {
|
||||
NoPadding(usize),
|
||||
Padding(RadixCiphertext),
|
||||
}
|
||||
|
||||
pub enum FheStringIsEmpty {
|
||||
NoPadding(bool),
|
||||
Padding(BooleanBlock),
|
||||
}
|
||||
|
||||
// A few helper functions for the implementations
|
||||
impl ServerKey {
|
||||
// If an iterator is longer than the other, the "excess" characters are ignored. This function
|
||||
// performs the equality check by transforming the `str` and `pat` chars into two UInts
|
||||
fn asciis_eq<'a, I, U>(&self, str: I, pat: U) -> BooleanBlock
|
||||
where
|
||||
I: DoubleEndedIterator<Item = &'a FheAsciiChar>,
|
||||
U: DoubleEndedIterator<Item = &'a FheAsciiChar>,
|
||||
{
|
||||
let blocks_str = str
|
||||
.into_iter()
|
||||
.rev()
|
||||
.flat_map(|c| c.ciphertext().blocks().to_owned())
|
||||
.collect();
|
||||
|
||||
let blocks_pat = pat
|
||||
.into_iter()
|
||||
.rev()
|
||||
.flat_map(|c| c.ciphertext().blocks().to_owned())
|
||||
.collect();
|
||||
|
||||
let mut uint_str = RadixCiphertext::from_blocks(blocks_str);
|
||||
let mut uint_pat = RadixCiphertext::from_blocks(blocks_pat);
|
||||
|
||||
self.trim_ciphertexts_lsb(&mut uint_str, &mut uint_pat);
|
||||
|
||||
self.key.eq_parallelized(&uint_str, &uint_pat)
|
||||
}
|
||||
|
||||
fn clear_asciis_eq<'a, I>(&self, str: I, pat: &str) -> BooleanBlock
|
||||
where
|
||||
I: DoubleEndedIterator<Item = &'a FheAsciiChar>,
|
||||
{
|
||||
let blocks_str: Vec<_> = str
|
||||
.into_iter()
|
||||
.rev()
|
||||
.flat_map(|c| c.ciphertext().blocks().to_owned())
|
||||
.collect();
|
||||
let mut clear_pat = pat;
|
||||
|
||||
let str_block_len = blocks_str.len();
|
||||
let pat_block_len = clear_pat.len() * 4;
|
||||
|
||||
let mut uint_str = RadixCiphertext::from_blocks(blocks_str);
|
||||
|
||||
// Trim the str or pat such that the exceeding bytes are removed
|
||||
match str_block_len.cmp(&pat_block_len) {
|
||||
Ordering::Less => {
|
||||
// `str_block_len` is always a multiple of 4 as each char is 4 blocks
|
||||
clear_pat = &clear_pat[..str_block_len / 4];
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let diff = str_block_len - pat_block_len;
|
||||
self.key.trim_radix_blocks_lsb_assign(&mut uint_str, diff);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let clear_pat_uint = self.pad_cipher_and_cleartext_lsb(&mut uint_str, clear_pat);
|
||||
|
||||
self.key.scalar_eq_parallelized(&uint_str, clear_pat_uint)
|
||||
}
|
||||
|
||||
fn asciis_eq_ignore_pat_pad<'a, I>(&self, str_pat: I) -> BooleanBlock
|
||||
where
|
||||
I: ParallelIterator<Item = (&'a FheAsciiChar, &'a FheAsciiChar)>,
|
||||
{
|
||||
let mut result = self.key.create_trivial_boolean_block(true);
|
||||
|
||||
let eq_or_null_pat: Vec<_> = str_pat
|
||||
.map(|(str_char, pat_char)| {
|
||||
let (are_eq, pat_is_null) = rayon::join(
|
||||
|| {
|
||||
self.key
|
||||
.eq_parallelized(str_char.ciphertext(), pat_char.ciphertext())
|
||||
},
|
||||
|| self.key.scalar_eq_parallelized(pat_char.ciphertext(), 0u8),
|
||||
);
|
||||
|
||||
// If `pat_char` is null then `are_eq` is set to true. Hence if ALL `pat_char`s are
|
||||
// null, the result is always true, which is correct since the pattern is empty
|
||||
self.key.boolean_bitor(&are_eq, &pat_is_null)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for eq_or_null in eq_or_null_pat {
|
||||
// Will be false if `str_char` != `pat_char` and `pat_char` isn't null
|
||||
self.key.boolean_bitand_assign(&mut result, &eq_or_null);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn pad_cipher_and_cleartext_lsb(
|
||||
&self,
|
||||
lhs: &mut RadixCiphertext,
|
||||
rhs: &str,
|
||||
) -> StaticUnsignedBigInt<N> {
|
||||
let mut rhs_bytes = rhs.as_bytes().to_vec();
|
||||
|
||||
// Resize rhs with nulls at the end such that it matches the N const u64 length (for the
|
||||
// StaticUnsignedBigInt)
|
||||
rhs_bytes.resize(N * 8, 0);
|
||||
|
||||
let mut rhs_clear_uint = StaticUnsignedBigInt::<N>::from(0u8);
|
||||
rhs_clear_uint.copy_from_be_byte_slice(&rhs_bytes);
|
||||
|
||||
// Also fill the lhs with null blocks at the end
|
||||
if lhs.blocks().len() < N * 8 * 4 {
|
||||
let diff = N * 8 * 4 - lhs.blocks().len();
|
||||
self.key
|
||||
.extend_radix_with_trivial_zero_blocks_lsb_assign(lhs, diff);
|
||||
}
|
||||
|
||||
rhs_clear_uint
|
||||
}
|
||||
|
||||
fn pad_ciphertexts_lsb(&self, lhs: &mut RadixCiphertext, rhs: &mut RadixCiphertext) {
|
||||
let lhs_blocks = lhs.blocks().len();
|
||||
let rhs_blocks = rhs.blocks().len();
|
||||
|
||||
match lhs_blocks.cmp(&rhs_blocks) {
|
||||
Ordering::Less => {
|
||||
let diff = rhs_blocks - lhs_blocks;
|
||||
self.key
|
||||
.extend_radix_with_trivial_zero_blocks_lsb_assign(lhs, diff);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let diff = lhs_blocks - rhs_blocks;
|
||||
self.key
|
||||
.extend_radix_with_trivial_zero_blocks_lsb_assign(rhs, diff);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn pad_or_trim_ciphertext(&self, cipher: &mut RadixCiphertext, len: usize) {
|
||||
let cipher_len = cipher.blocks().len();
|
||||
|
||||
match cipher_len.cmp(&len) {
|
||||
Ordering::Less => {
|
||||
let diff = len - cipher_len;
|
||||
self.key
|
||||
.extend_radix_with_trivial_zero_blocks_msb_assign(cipher, diff);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let diff = cipher_len - len;
|
||||
self.key.trim_radix_blocks_msb_assign(cipher, diff);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_ciphertexts_lsb(&self, lhs: &mut RadixCiphertext, rhs: &mut RadixCiphertext) {
|
||||
let lhs_blocks = lhs.blocks().len();
|
||||
let rhs_blocks = rhs.blocks().len();
|
||||
|
||||
match lhs_blocks.cmp(&rhs_blocks) {
|
||||
Ordering::Less => {
|
||||
let diff = rhs_blocks - lhs_blocks;
|
||||
self.key.trim_radix_blocks_lsb_assign(rhs, diff);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let diff = lhs_blocks - rhs_blocks;
|
||||
self.key.trim_radix_blocks_lsb_assign(lhs, diff);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn conditional_string(
|
||||
&self,
|
||||
condition: &BooleanBlock,
|
||||
true_ct: FheString,
|
||||
false_ct: &FheString,
|
||||
) -> FheString {
|
||||
let padded = true_ct.is_padded() && false_ct.is_padded();
|
||||
let potentially_padded = true_ct.is_padded() || false_ct.is_padded();
|
||||
|
||||
let mut true_ct_uint = true_ct.into_uint(self);
|
||||
let mut false_ct_uint = false_ct.to_uint(self);
|
||||
|
||||
self.pad_ciphertexts_lsb(&mut true_ct_uint, &mut false_ct_uint);
|
||||
|
||||
let result_uint =
|
||||
self.key
|
||||
.if_then_else_parallelized(condition, &true_ct_uint, &false_ct_uint);
|
||||
|
||||
let mut result = FheString::from_uint(result_uint, false);
|
||||
if padded {
|
||||
result.set_is_padded(true);
|
||||
} else if potentially_padded {
|
||||
// If the result is potentially padded we cannot assume it's not padded. We ensure that
|
||||
// result is padded with a single null that is ignored by our implementations
|
||||
result.append_null(self);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn left_shift_chars(&self, str: &FheString, shift: &RadixCiphertext) -> FheString {
|
||||
let uint = str.to_uint(self);
|
||||
let mut shift_bits = self.key.scalar_left_shift_parallelized(shift, 3);
|
||||
|
||||
// `shift_bits` needs to have the same block len as `uint` for the tfhe-rs shift to work
|
||||
self.pad_or_trim_ciphertext(&mut shift_bits, uint.blocks().len());
|
||||
|
||||
let shifted = self.key.left_shift_parallelized(&uint, &shift_bits);
|
||||
|
||||
// If the shifting amount is >= than the str length we get zero i.e. all chars are out of
|
||||
// range (instead of wrapping, which is the behavior of Rust and tfhe-rs)
|
||||
let bit_len = (str.len() * 8) as u32;
|
||||
let shift_ge_than_str = self.key.scalar_ge_parallelized(&shift_bits, bit_len);
|
||||
|
||||
let result = self.key.if_then_else_parallelized(
|
||||
&shift_ge_than_str,
|
||||
&self.key.create_trivial_zero_radix(uint.blocks().len()),
|
||||
&shifted,
|
||||
);
|
||||
|
||||
FheString::from_uint(result, false)
|
||||
}
|
||||
|
||||
fn right_shift_chars(&self, str: &FheString, shift: &RadixCiphertext) -> FheString {
|
||||
let uint = str.to_uint(self);
|
||||
let mut shift_bits = self.key.scalar_left_shift_parallelized(shift, 3);
|
||||
|
||||
// `shift_bits` needs to have the same block len as `uint` for the tfhe-rs shift to work
|
||||
self.pad_or_trim_ciphertext(&mut shift_bits, uint.blocks().len());
|
||||
|
||||
let shifted = self.key.right_shift_parallelized(&uint, &shift_bits);
|
||||
|
||||
// If the shifting amount is >= than the str length we get zero i.e. all chars are out of
|
||||
// range (instead of wrapping, which is the behavior of Rust and tfhe-rs)
|
||||
let bit_len = (str.len() * 8) as u32;
|
||||
let shift_ge_than_str = self.key.scalar_ge_parallelized(&shift_bits, bit_len);
|
||||
|
||||
let result = self.key.if_then_else_parallelized(
|
||||
&shift_ge_than_str,
|
||||
&self.key.create_trivial_zero_radix(uint.blocks().len()),
|
||||
&shifted,
|
||||
);
|
||||
|
||||
FheString::from_uint(result, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FheStringIterator {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock);
|
||||
}
|
||||
|
||||
type CharIter<'a> = Vec<&'a FheAsciiChar>;
|
||||
@@ -1,385 +0,0 @@
|
||||
use crate::ciphertext::{ClearString, FheString, GenericPattern, UIntArg};
|
||||
use crate::server_key::{FheStringIsEmpty, FheStringLen, ServerKey};
|
||||
use rayon::prelude::*;
|
||||
use tfhe::integer::BooleanBlock;
|
||||
|
||||
impl ServerKey {
|
||||
/// Returns the length of an encrypted string as an `FheStringLen` enum.
|
||||
///
|
||||
/// If the encrypted string has no padding, the length is the clear length of the char vector.
|
||||
/// If there is padding, the length is calculated homomorphically and returned as an
|
||||
/// encrypted `RadixCiphertext`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::{gen_keys, FheStringLen};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "hello";
|
||||
/// let number_of_nulls = 3;
|
||||
///
|
||||
/// let enc_s_no_padding = FheString::new(&ck, &s, None);
|
||||
/// let enc_s_with_padding = FheString::new(&ck, &s, Some(number_of_nulls));
|
||||
///
|
||||
/// let result_no_padding = sk.len(&enc_s_no_padding);
|
||||
/// let result_with_padding = sk.len(&enc_s_with_padding);
|
||||
///
|
||||
/// match result_no_padding {
|
||||
/// FheStringLen::NoPadding(length) => assert_eq!(length, 5),
|
||||
/// FheStringLen::Padding(_) => panic!("Unexpected padding"),
|
||||
/// }
|
||||
///
|
||||
/// match result_with_padding {
|
||||
/// FheStringLen::NoPadding(_) => panic!("Unexpected no padding"),
|
||||
/// FheStringLen::Padding(ciphertext) => {
|
||||
/// // Homomorphically computed length, requires decryption for actual length
|
||||
/// let length = ck.key().decrypt_radix::<u32>(&ciphertext);
|
||||
/// assert_eq!(length, 5)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn len(&self, str: &FheString) -> FheStringLen {
|
||||
if str.is_padded() {
|
||||
let non_zero_chars: Vec<_> = str
|
||||
.chars()
|
||||
.par_iter()
|
||||
.map(|char| {
|
||||
let bool = self.key.scalar_ne_parallelized(char.ciphertext(), 0u8);
|
||||
bool.into_radix(16, &self.key)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// If we add the number of non-zero elements we get the actual length, without padding
|
||||
let len = self
|
||||
.key
|
||||
.sum_ciphertexts_parallelized(non_zero_chars.iter())
|
||||
.expect("There's at least one padding character");
|
||||
|
||||
FheStringLen::Padding(len)
|
||||
} else {
|
||||
FheStringLen::NoPadding(str.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether an encrypted string is empty or not as an `FheStringIsEmpty` enum.
|
||||
///
|
||||
/// If the encrypted string has no padding, the result is a clear boolean.
|
||||
/// If there is padding, the result is calculated homomorphically and returned as an
|
||||
/// encrypted `RadixCiphertext`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::{gen_keys, FheStringIsEmpty};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "";
|
||||
/// let number_of_nulls = 2;
|
||||
///
|
||||
/// let enc_s_no_padding = FheString::new(&ck, &s, None);
|
||||
/// let enc_s_with_padding = FheString::new(&ck, &s, Some(number_of_nulls));
|
||||
///
|
||||
/// let result_no_padding = sk.is_empty(&enc_s_no_padding);
|
||||
/// let result_with_padding = sk.is_empty(&enc_s_with_padding);
|
||||
///
|
||||
/// match result_no_padding {
|
||||
/// FheStringIsEmpty::NoPadding(is_empty) => assert!(is_empty),
|
||||
/// FheStringIsEmpty::Padding(_) => panic!("Unexpected padding"),
|
||||
/// }
|
||||
///
|
||||
/// match result_with_padding {
|
||||
/// FheStringIsEmpty::NoPadding(_) => panic!("Unexpected no padding"),
|
||||
/// FheStringIsEmpty::Padding(ciphertext) => {
|
||||
/// // Homomorphically computed emptiness, requires decryption for actual value
|
||||
/// let is_empty = ck.key().decrypt_bool(&ciphertext);
|
||||
/// assert!(is_empty)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn is_empty(&self, str: &FheString) -> FheStringIsEmpty {
|
||||
if str.is_padded() {
|
||||
if str.len() == 1 {
|
||||
return FheStringIsEmpty::Padding(self.key.create_trivial_boolean_block(true));
|
||||
}
|
||||
|
||||
let str_uint = str.to_uint(self);
|
||||
let result = self.key.scalar_eq_parallelized(&str_uint, 0u8);
|
||||
|
||||
FheStringIsEmpty::Padding(result)
|
||||
} else {
|
||||
FheStringIsEmpty::NoPadding(str.len() == 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with all characters converted to uppercase.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "Hello World";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// let result = sk.to_uppercase(&enc_s);
|
||||
/// let uppercased = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(uppercased, "HELLO WORLD");
|
||||
/// ```
|
||||
pub fn to_uppercase(&self, str: &FheString) -> FheString {
|
||||
let mut uppercase = str.clone();
|
||||
|
||||
// Returns 1 if the corresponding character is lowercase, 0 otherwise
|
||||
let lowercase_chars: Vec<_> = str
|
||||
.chars()
|
||||
.par_iter()
|
||||
.map(|char| {
|
||||
let (ge_97, le_122) = rayon::join(
|
||||
|| self.key.scalar_ge_parallelized(char.ciphertext(), 97u8),
|
||||
|| self.key.scalar_le_parallelized(char.ciphertext(), 122u8),
|
||||
);
|
||||
|
||||
self.key.boolean_bitand(&ge_97, &le_122)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Subtraction by 32 makes the character uppercase
|
||||
uppercase
|
||||
.chars_mut()
|
||||
.par_iter_mut()
|
||||
.zip(lowercase_chars.into_par_iter())
|
||||
.for_each(|(char, is_lowercase)| {
|
||||
let mut subtract = self.key.create_trivial_radix(32, 4);
|
||||
|
||||
self.key
|
||||
.mul_assign_parallelized(&mut subtract, &is_lowercase.into_radix(1, &self.key));
|
||||
|
||||
self.key
|
||||
.sub_assign_parallelized(char.ciphertext_mut(), &subtract);
|
||||
});
|
||||
|
||||
uppercase
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with all characters converted to lowercase.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "Hello World";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// let result = sk.to_lowercase(&enc_s);
|
||||
/// let lowercased = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(lowercased, "hello world");
|
||||
/// ```
|
||||
pub fn to_lowercase(&self, str: &FheString) -> FheString {
|
||||
let mut lowercase = str.clone();
|
||||
|
||||
// Returns 1 if the corresponding character is uppercase, 0 otherwise
|
||||
let uppercase_chars: Vec<_> = str
|
||||
.chars()
|
||||
.par_iter()
|
||||
.map(|char| {
|
||||
let (ge_65, le_90) = rayon::join(
|
||||
|| self.key.scalar_ge_parallelized(char.ciphertext(), 65u8),
|
||||
|| self.key.scalar_le_parallelized(char.ciphertext(), 90u8),
|
||||
);
|
||||
|
||||
self.key.boolean_bitand(&ge_65, &le_90)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Addition by 32 makes the character lowercase
|
||||
lowercase
|
||||
.chars_mut()
|
||||
.par_iter_mut()
|
||||
.zip(uppercase_chars)
|
||||
.for_each(|(char, is_uppercase)| {
|
||||
let mut add = self.key.create_trivial_radix(32, 4);
|
||||
|
||||
self.key
|
||||
.mul_assign_parallelized(&mut add, &is_uppercase.into_radix(1, &self.key));
|
||||
|
||||
self.key
|
||||
.add_assign_parallelized(char.ciphertext_mut(), &add);
|
||||
});
|
||||
|
||||
lowercase
|
||||
}
|
||||
|
||||
/// Returns `true` if an encrypted string and a pattern (either encrypted or clear) are equal,
|
||||
/// ignoring case differences.
|
||||
///
|
||||
/// Returns `false` if they are not equal.
|
||||
///
|
||||
/// The pattern for comparison (`rhs`) can be specified as either `GenericPattern::Clear` for a
|
||||
/// clear string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s1, s2) = ("Hello", "hello");
|
||||
///
|
||||
/// let enc_s1 = FheString::new(&ck, &s1, None);
|
||||
/// let enc_s2 = GenericPattern::Enc(FheString::new(&ck, &s2, None));
|
||||
///
|
||||
/// let result = sk.eq_ignore_case(&enc_s1, &enc_s2);
|
||||
/// let are_equal = ck.key().decrypt_bool(&result);
|
||||
///
|
||||
/// assert!(are_equal);
|
||||
/// ```
|
||||
pub fn eq_ignore_case(&self, lhs: &FheString, rhs: &GenericPattern) -> BooleanBlock {
|
||||
let (lhs, rhs) = rayon::join(
|
||||
|| self.to_lowercase(lhs),
|
||||
|| match rhs {
|
||||
GenericPattern::Clear(rhs) => {
|
||||
GenericPattern::Clear(ClearString::new(rhs.str().to_lowercase()))
|
||||
}
|
||||
GenericPattern::Enc(rhs) => GenericPattern::Enc(self.to_lowercase(rhs)),
|
||||
},
|
||||
);
|
||||
|
||||
self.eq(&lhs, &rhs)
|
||||
}
|
||||
|
||||
/// Concatenates two encrypted strings and returns the result as a new encrypted string.
|
||||
///
|
||||
/// This function is equivalent to using the `+` operator on standard strings.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (lhs, rhs) = ("Hello, ", "world!");
|
||||
///
|
||||
/// let enc_lhs = FheString::new(&ck, &lhs, None);
|
||||
/// let enc_rhs = FheString::new(&ck, &rhs, None);
|
||||
///
|
||||
/// let result = sk.concat(&enc_lhs, &enc_rhs);
|
||||
/// let concatenated = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(concatenated, "Hello, world!");
|
||||
/// ```
|
||||
pub fn concat(&self, lhs: &FheString, rhs: &FheString) -> FheString {
|
||||
let mut result = lhs.clone();
|
||||
|
||||
match self.len(lhs) {
|
||||
// No homomorphic operation required if the lhs is not padded
|
||||
FheStringLen::NoPadding(_) => {
|
||||
result.chars_vec().extend_from_slice(rhs.chars());
|
||||
result.set_is_padded(rhs.is_padded());
|
||||
}
|
||||
|
||||
// If lhs is padded we can shift it right such that all nulls move to the start, then
|
||||
// we append the rhs and shift it left again to move the nulls to the new end
|
||||
FheStringLen::Padding(len) => {
|
||||
let padded_len = self.key.create_trivial_radix(lhs.len() as u32, 16);
|
||||
let number_of_nulls = self.key.sub_parallelized(&padded_len, &len);
|
||||
|
||||
result = self.right_shift_chars(&result, &number_of_nulls);
|
||||
|
||||
result.chars_vec().extend_from_slice(rhs.chars());
|
||||
|
||||
result = self.left_shift_chars(&result, &number_of_nulls);
|
||||
|
||||
result.set_is_padded(true);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string which is the original encrypted string repeated `n` times.
|
||||
///
|
||||
/// The number of repetitions `n` is specified by a `UIntArg`, which can be either `Clear` or
|
||||
/// `Enc`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, UIntArg};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "hi";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// // Using Clear count
|
||||
/// let clear_count = UIntArg::Clear(3);
|
||||
/// let result_clear = sk.repeat(&enc_s, &clear_count);
|
||||
/// let repeated_clear = ck.decrypt_ascii(&result_clear);
|
||||
///
|
||||
/// assert_eq!(repeated_clear, "hihihi");
|
||||
///
|
||||
/// // Using Encrypted count
|
||||
/// let max = 3; // Restricts the range of enc_n to 0..=max
|
||||
/// let enc_n = ck.encrypt_u16(3, Some(max));
|
||||
/// let enc_count = UIntArg::Enc(enc_n);
|
||||
/// let result_enc = sk.repeat(&enc_s, &enc_count);
|
||||
/// let repeated_enc = ck.decrypt_ascii(&result_enc);
|
||||
///
|
||||
/// assert_eq!(repeated_enc, "hihihi");
|
||||
/// ```
|
||||
pub fn repeat(&self, str: &FheString, n: &UIntArg) -> FheString {
|
||||
if let UIntArg::Clear(0) = n {
|
||||
return FheString::empty();
|
||||
}
|
||||
|
||||
let str_len = str.len();
|
||||
if str_len == 0 || (str.is_padded() && str_len == 1) {
|
||||
return FheString::empty();
|
||||
}
|
||||
|
||||
let mut result = str.clone();
|
||||
|
||||
// Note that if n = 3, at most we have to append the str 2 times
|
||||
match n {
|
||||
UIntArg::Clear(clear_n) => {
|
||||
for _ in 0..*clear_n - 1 {
|
||||
result = self.concat(&result, str);
|
||||
}
|
||||
}
|
||||
UIntArg::Enc(enc_n) => {
|
||||
let n_is_zero = self.key.scalar_eq_parallelized(enc_n.cipher(), 0);
|
||||
result = self.conditional_string(&n_is_zero, FheString::empty(), &result);
|
||||
|
||||
for i in 0..enc_n.max().unwrap_or(u16::MAX) - 1 {
|
||||
let n_is_exceeded = self.key.scalar_le_parallelized(enc_n.cipher(), i + 1);
|
||||
let append = self.conditional_string(&n_is_exceeded, FheString::empty(), str);
|
||||
|
||||
result = self.concat(&result, &append);
|
||||
}
|
||||
|
||||
// If str was not padded and n == max we don't get nulls at the end. However if
|
||||
// n < max we do, and as these conditions are unknown we have to ensure result is
|
||||
// actually padded
|
||||
if !str.is_padded() {
|
||||
result.append_null(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
use crate::ciphertext::{FheAsciiChar, FheString, GenericPattern};
|
||||
use crate::server_key::pattern::{CharIter, IsMatch};
|
||||
use crate::server_key::ServerKey;
|
||||
use itertools::Itertools;
|
||||
use rayon::prelude::*;
|
||||
use rayon::range::Iter;
|
||||
use tfhe::integer::{BooleanBlock, IntegerRadixCiphertext, RadixCiphertext};
|
||||
|
||||
impl ServerKey {
|
||||
// Compare pat with str, with pat shifted right (in relation to str) the number given by iter
|
||||
fn compare_shifted(
|
||||
&self,
|
||||
str_pat: (CharIter, CharIter),
|
||||
par_iter: Iter<usize>,
|
||||
ignore_pat_pad: bool,
|
||||
) -> BooleanBlock {
|
||||
let (str, pat) = str_pat;
|
||||
|
||||
let matched: Vec<_> = par_iter
|
||||
.map(|start| {
|
||||
if ignore_pat_pad {
|
||||
let str_chars = str
|
||||
.par_iter()
|
||||
.copied()
|
||||
.skip(start)
|
||||
.zip(pat.par_iter().copied());
|
||||
|
||||
self.asciis_eq_ignore_pat_pad(str_chars)
|
||||
} else {
|
||||
self.asciis_eq(str.iter().copied().skip(start), pat.iter().copied())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let block_vec: Vec<_> = matched
|
||||
.into_iter()
|
||||
.map(|bool| {
|
||||
let radix: RadixCiphertext = bool.into_radix(1, &self.key);
|
||||
radix.into_blocks()[0].clone()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// This will be 0 if there was no match, non-zero otherwise
|
||||
let combined_radix = RadixCiphertext::from(block_vec);
|
||||
|
||||
self.key.scalar_ne_parallelized(&combined_radix, 0)
|
||||
}
|
||||
|
||||
fn clear_compare_shifted(
|
||||
&self,
|
||||
str_pat: (CharIter, &str),
|
||||
par_iter: Iter<usize>,
|
||||
) -> BooleanBlock {
|
||||
let (str, pat) = str_pat;
|
||||
|
||||
let matched: Vec<_> = par_iter
|
||||
.map(|start| self.clear_asciis_eq(str.iter().skip(start).copied(), pat))
|
||||
.collect();
|
||||
|
||||
let block_vec: Vec<_> = matched
|
||||
.into_iter()
|
||||
.map(|bool| {
|
||||
let radix: RadixCiphertext = bool.into_radix(1, &self.key);
|
||||
radix.into_blocks()[0].clone()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// This will be 0 if there was no match, non-zero otherwise
|
||||
let combined_radix = RadixCiphertext::from(block_vec);
|
||||
|
||||
self.key.scalar_ne_parallelized(&combined_radix, 0)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given pattern (either encrypted or clear) matches a substring of this
|
||||
/// encrypted string.
|
||||
///
|
||||
/// Returns `false` if the pattern does not match any substring.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{ClearString, FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (bananas, nana, apples) = ("bananas", "nana", "apples");
|
||||
///
|
||||
/// let enc_bananas = FheString::new(&ck, &bananas, None);
|
||||
/// let enc_nana = GenericPattern::Enc(FheString::new(&ck, &nana, None));
|
||||
/// let clear_apples = GenericPattern::Clear(ClearString::new(apples.to_string()));
|
||||
///
|
||||
/// let result1 = sk.contains(&enc_bananas, &enc_nana);
|
||||
/// let result2 = sk.contains(&enc_bananas, &clear_apples);
|
||||
///
|
||||
/// let should_be_true = ck.key().decrypt_bool(&result1);
|
||||
/// let should_be_false = ck.key().decrypt_bool(&result2);
|
||||
///
|
||||
/// assert!(should_be_true);
|
||||
/// assert!(!should_be_false);
|
||||
/// ```
|
||||
pub fn contains(&self, str: &FheString, pat: &GenericPattern) -> BooleanBlock {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
IsMatch::Clear(val) => return self.key.create_trivial_boolean_block(val),
|
||||
IsMatch::Cipher(val) => return val,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let ignore_pat_pad = trivial_or_enc_pat.is_padded();
|
||||
|
||||
let null = (!str.is_padded() && trivial_or_enc_pat.is_padded())
|
||||
.then_some(FheAsciiChar::null(self));
|
||||
|
||||
let (str_iter, pat_iter, iter) =
|
||||
self.contains_cases(str, &trivial_or_enc_pat, null.as_ref());
|
||||
|
||||
match pat {
|
||||
GenericPattern::Clear(pat) => {
|
||||
self.clear_compare_shifted((str_iter, pat.str()), iter.into_par_iter())
|
||||
}
|
||||
GenericPattern::Enc(_) => {
|
||||
self.compare_shifted((str_iter, pat_iter), iter.into_par_iter(), ignore_pat_pad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the given pattern (either encrypted or clear) matches a prefix of this
|
||||
/// encrypted string.
|
||||
///
|
||||
/// Returns `false` if the pattern does not match the prefix.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{ClearString, FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (bananas, ba, nan) = ("bananas", "ba", "nan");
|
||||
///
|
||||
/// let enc_bananas = FheString::new(&ck, &bananas, None);
|
||||
/// let enc_ba = GenericPattern::Enc(FheString::new(&ck, &ba, None));
|
||||
/// let clear_nan = GenericPattern::Clear(ClearString::new(nan.to_string()));
|
||||
///
|
||||
/// let result1 = sk.starts_with(&enc_bananas, &enc_ba);
|
||||
/// let result2 = sk.starts_with(&enc_bananas, &clear_nan);
|
||||
///
|
||||
/// let should_be_true = ck.key().decrypt_bool(&result1);
|
||||
/// let should_be_false = ck.key().decrypt_bool(&result2);
|
||||
///
|
||||
/// assert!(should_be_true);
|
||||
/// assert!(!should_be_false);
|
||||
/// ```
|
||||
pub fn starts_with(&self, str: &FheString, pat: &GenericPattern) -> BooleanBlock {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
IsMatch::Clear(val) => return self.key.create_trivial_boolean_block(val),
|
||||
IsMatch::Cipher(val) => return val,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if !trivial_or_enc_pat.is_padded() {
|
||||
return match pat {
|
||||
GenericPattern::Clear(pat) => self.clear_asciis_eq(str.chars().iter(), pat.str()),
|
||||
GenericPattern::Enc(pat) => self.asciis_eq(str.chars().iter(), pat.chars().iter()),
|
||||
};
|
||||
}
|
||||
|
||||
let str_len = str.len();
|
||||
let pat_len = trivial_or_enc_pat.len();
|
||||
|
||||
// In the padded pattern case we can remove the last char (as it's always null)
|
||||
let pat_chars = &trivial_or_enc_pat.chars()[..pat_len - 1];
|
||||
|
||||
let null = FheAsciiChar::null(self);
|
||||
let str_chars = if !str.is_padded() && (str_len < pat_len - 1) {
|
||||
// If str = "xy" and pat = "xyz\0", then str[..] == pat[..2], but instead we have
|
||||
// to check if "xy\0" == pat[..3] (i.e. check that the actual pattern isn't longer)
|
||||
str.chars()
|
||||
.iter()
|
||||
.chain(std::iter::once(&null))
|
||||
.collect_vec()
|
||||
} else {
|
||||
str.chars().iter().collect_vec()
|
||||
};
|
||||
|
||||
let str_pat = str_chars.par_iter().copied().zip(pat_chars.par_iter());
|
||||
|
||||
self.asciis_eq_ignore_pat_pad(str_pat)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given pattern (either encrypted or clear) matches a suffix of this
|
||||
/// encrypted string.
|
||||
///
|
||||
/// Returns `false` if the pattern does not match the suffix.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{ClearString, FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (bananas, anas, nana) = ("bananas", "anas", "nana");
|
||||
///
|
||||
/// let enc_bananas = FheString::new(&ck, &bananas, None);
|
||||
/// let enc_anas = GenericPattern::Enc(FheString::new(&ck, &anas, None));
|
||||
/// let clear_nana = GenericPattern::Clear(ClearString::new(nana.to_string()));
|
||||
///
|
||||
/// let result1 = sk.ends_with(&enc_bananas, &enc_anas);
|
||||
/// let result2 = sk.ends_with(&enc_bananas, &clear_nana);
|
||||
///
|
||||
/// let should_be_true = ck.key().decrypt_bool(&result1);
|
||||
/// let should_be_false = ck.key().decrypt_bool(&result2);
|
||||
///
|
||||
/// assert!(should_be_true);
|
||||
/// assert!(!should_be_false);
|
||||
/// ```
|
||||
pub fn ends_with(&self, str: &FheString, pat: &GenericPattern) -> BooleanBlock {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
IsMatch::Clear(val) => return self.key.create_trivial_boolean_block(val),
|
||||
IsMatch::Cipher(val) => return val,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match pat {
|
||||
GenericPattern::Clear(pat) => {
|
||||
let (str_iter, clear_pat, iter) = self.clear_ends_with_cases(str, pat.str());
|
||||
|
||||
self.clear_compare_shifted((str_iter, &clear_pat), iter.into_par_iter())
|
||||
}
|
||||
GenericPattern::Enc(pat) => {
|
||||
let null = (str.is_padded() ^ pat.is_padded()).then_some(FheAsciiChar::null(self));
|
||||
|
||||
let (str_iter, pat_iter, iter) = self.ends_with_cases(str, pat, null.as_ref());
|
||||
|
||||
self.compare_shifted((str_iter, pat_iter), iter.into_par_iter(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
use crate::ciphertext::{FheAsciiChar, FheString, GenericPattern};
|
||||
use crate::server_key::pattern::IsMatch;
|
||||
use crate::server_key::{CharIter, FheStringIsEmpty, FheStringLen, ServerKey};
|
||||
use rayon::prelude::*;
|
||||
use rayon::vec::IntoIter;
|
||||
use tfhe::integer::prelude::*;
|
||||
use tfhe::integer::{BooleanBlock, RadixCiphertext};
|
||||
|
||||
impl ServerKey {
|
||||
// Compare pat with str, with pat shifted right (in relation to str) the number of times given
|
||||
// by iter. Returns the first character index of the last match, or the first character index
|
||||
// of the first match if the range is reversed. If there's no match defaults to 0
|
||||
fn compare_shifted_index(
|
||||
&self,
|
||||
str_pat: (CharIter, CharIter),
|
||||
par_iter: IntoIter<usize>,
|
||||
ignore_pat_pad: bool,
|
||||
) -> (RadixCiphertext, BooleanBlock) {
|
||||
let mut result = self.key.create_trivial_boolean_block(false);
|
||||
let mut last_match_index = self.key.create_trivial_zero_radix(16);
|
||||
let (str, pat) = str_pat;
|
||||
|
||||
let matched: Vec<_> = par_iter
|
||||
.map(|start| {
|
||||
let is_matched = if ignore_pat_pad {
|
||||
let str_pat = str
|
||||
.par_iter()
|
||||
.copied()
|
||||
.skip(start)
|
||||
.zip(pat.par_iter().copied());
|
||||
|
||||
self.asciis_eq_ignore_pat_pad(str_pat)
|
||||
} else {
|
||||
self.asciis_eq(str.iter().skip(start).copied(), pat.iter().copied())
|
||||
};
|
||||
|
||||
(start, is_matched)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (i, is_matched) in matched {
|
||||
let index = self.key.create_trivial_radix(i as u32, 16);
|
||||
|
||||
rayon::join(
|
||||
|| {
|
||||
last_match_index =
|
||||
self.key
|
||||
.if_then_else_parallelized(&is_matched, &index, &last_match_index)
|
||||
},
|
||||
// One of the possible values of the padded pat must match the str
|
||||
|| self.key.boolean_bitor_assign(&mut result, &is_matched),
|
||||
);
|
||||
}
|
||||
|
||||
(last_match_index, result)
|
||||
}
|
||||
|
||||
fn clear_compare_shifted_index(
|
||||
&self,
|
||||
str_pat: (CharIter, &str),
|
||||
par_iter: IntoIter<usize>,
|
||||
) -> (RadixCiphertext, BooleanBlock) {
|
||||
let mut result = self.key.create_trivial_boolean_block(false);
|
||||
let mut last_match_index = self.key.create_trivial_zero_radix(16);
|
||||
let (str, pat) = str_pat;
|
||||
|
||||
let matched: Vec<_> = par_iter
|
||||
.map(|start| {
|
||||
let str_chars = &str[start..];
|
||||
|
||||
let is_matched = self.clear_asciis_eq(str_chars.iter().copied(), pat);
|
||||
|
||||
(start, is_matched)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (i, is_matched) in matched {
|
||||
let index = self.key.create_trivial_radix(i as u32, 16);
|
||||
|
||||
rayon::join(
|
||||
|| {
|
||||
last_match_index =
|
||||
self.key
|
||||
.if_then_else_parallelized(&is_matched, &index, &last_match_index)
|
||||
},
|
||||
// One of the possible values of the padded pat must match the str
|
||||
|| self.key.boolean_bitor_assign(&mut result, &is_matched),
|
||||
);
|
||||
}
|
||||
|
||||
(last_match_index, result)
|
||||
}
|
||||
|
||||
/// Returns a tuple containing the byte index of the first character of this encrypted string
|
||||
/// that matches the given pattern (either encrypted or clear), and a boolean indicating if a
|
||||
/// match was found.
|
||||
///
|
||||
/// If the pattern doesn’t match, the function returns a tuple where the boolean part is
|
||||
/// `false`, indicating the equivalent of `None`.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (haystack, needle) = ("hello world", "world");
|
||||
///
|
||||
/// let enc_haystack = FheString::new(&ck, &haystack, None);
|
||||
/// let enc_needle = GenericPattern::Enc(FheString::new(&ck, &needle, None));
|
||||
///
|
||||
/// let (index, found) = sk.find(&enc_haystack, &enc_needle);
|
||||
///
|
||||
/// let index = ck.key().decrypt_radix::<u32>(&index);
|
||||
/// let found = ck.key().decrypt_bool(&found);
|
||||
///
|
||||
/// assert!(found);
|
||||
/// assert_eq!(index, 6); // "world" starts at index 6 in "hello world"
|
||||
/// ```
|
||||
pub fn find(&self, str: &FheString, pat: &GenericPattern) -> (RadixCiphertext, BooleanBlock) {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
let zero = self.key.create_trivial_zero_radix(16);
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
// bool is true if pattern is empty, in which the first match index is 0. If it's false
|
||||
// we default to 0 as well
|
||||
IsMatch::Clear(bool) => return (zero, self.key.create_trivial_boolean_block(bool)),
|
||||
|
||||
// This variant is only returned in the empty string case so in any case index is 0
|
||||
IsMatch::Cipher(val) => return (zero, val),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let ignore_pat_pad = trivial_or_enc_pat.is_padded();
|
||||
|
||||
let null = (!str.is_padded() && trivial_or_enc_pat.is_padded())
|
||||
.then_some(FheAsciiChar::null(self));
|
||||
|
||||
let (str_iter, pat_iter, iter) =
|
||||
self.contains_cases(str, &trivial_or_enc_pat, null.as_ref());
|
||||
|
||||
let iter_values: Vec<_> = iter.rev().collect();
|
||||
|
||||
match pat {
|
||||
GenericPattern::Clear(pat) => {
|
||||
self.clear_compare_shifted_index((str_iter, pat.str()), iter_values.into_par_iter())
|
||||
}
|
||||
GenericPattern::Enc(_) => self.compare_shifted_index(
|
||||
(str_iter, pat_iter),
|
||||
iter_values.into_par_iter(),
|
||||
ignore_pat_pad,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a tuple containing the byte index of the first character from the end of this
|
||||
/// encrypted string that matches the given pattern (either encrypted or clear), and a
|
||||
/// boolean indicating if a match was found.
|
||||
///
|
||||
/// If the pattern doesn’t match, the function returns a tuple where the boolean part is
|
||||
/// `false`, indicating the equivalent of `None`.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (haystack, needle) = ("hello world world", "world");
|
||||
///
|
||||
/// let enc_haystack = FheString::new(&ck, &haystack, None);
|
||||
/// let enc_needle = GenericPattern::Enc(FheString::new(&ck, &needle, None));
|
||||
///
|
||||
/// let (index, found) = sk.rfind(&enc_haystack, &enc_needle);
|
||||
///
|
||||
/// let index = ck.key().decrypt_radix::<u32>(&index);
|
||||
/// let found = ck.key().decrypt_bool(&found);
|
||||
///
|
||||
/// assert!(found);
|
||||
/// assert_eq!(index, 12); // The last "world" starts at index 12 in "hello world world"
|
||||
/// ```
|
||||
pub fn rfind(&self, str: &FheString, pat: &GenericPattern) -> (RadixCiphertext, BooleanBlock) {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
let zero = self.key.create_trivial_zero_radix(16);
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
IsMatch::Clear(val) => {
|
||||
// val = true if pattern is empty, in which the last match index = str.len()
|
||||
let index = if val {
|
||||
match self.len(str) {
|
||||
FheStringLen::Padding(cipher_len) => cipher_len,
|
||||
FheStringLen::NoPadding(len) => {
|
||||
self.key.create_trivial_radix(len as u32, 16)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
zero
|
||||
};
|
||||
|
||||
return (index, self.key.create_trivial_boolean_block(val));
|
||||
}
|
||||
|
||||
// This variant is only returned in the empty string case so in any case index is 0
|
||||
IsMatch::Cipher(val) => return (zero, val),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let ignore_pat_pad = trivial_or_enc_pat.is_padded();
|
||||
|
||||
let str_len = str.len();
|
||||
let (null, ext_iter) = if !str.is_padded() && trivial_or_enc_pat.is_padded() {
|
||||
(Some(FheAsciiChar::null(self)), Some(0..str_len + 1))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let (str_iter, pat_iter, iter) =
|
||||
self.contains_cases(str, &trivial_or_enc_pat, null.as_ref());
|
||||
|
||||
let iter_values: Vec<_> = ext_iter.unwrap_or(iter).collect();
|
||||
|
||||
let ((mut last_match_index, result), option) = rayon::join(
|
||||
|| match pat {
|
||||
GenericPattern::Clear(pat) => self.clear_compare_shifted_index(
|
||||
(str_iter, pat.str()),
|
||||
iter_values.into_par_iter(),
|
||||
),
|
||||
GenericPattern::Enc(_) => self.compare_shifted_index(
|
||||
(str_iter, pat_iter),
|
||||
iter_values.into_par_iter(),
|
||||
ignore_pat_pad,
|
||||
),
|
||||
},
|
||||
|| {
|
||||
// We have to check if pat is empty as in that case the returned index is str.len()
|
||||
// (the actual length) which doesn't correspond to our `last_match_index`
|
||||
let padded_pat_is_empty = match self.is_empty(&trivial_or_enc_pat) {
|
||||
FheStringIsEmpty::Padding(is_empty) => Some(is_empty),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// The non padded str case was handled thanks to + 1 in the ext_iter
|
||||
if str.is_padded() && padded_pat_is_empty.is_some() {
|
||||
let str_true_len = match self.len(str) {
|
||||
FheStringLen::Padding(cipher_len) => cipher_len,
|
||||
FheStringLen::NoPadding(len) => {
|
||||
self.key.create_trivial_radix(len as u32, 16)
|
||||
}
|
||||
};
|
||||
|
||||
Some((padded_pat_is_empty.unwrap(), str_true_len))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if let Some((pat_is_empty, str_true_len)) = option {
|
||||
last_match_index =
|
||||
self.key
|
||||
.if_then_else_parallelized(&pat_is_empty, &str_true_len, &last_match_index);
|
||||
}
|
||||
|
||||
(last_match_index, result)
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
mod contains;
|
||||
mod find;
|
||||
mod replace;
|
||||
mod split;
|
||||
mod strip;
|
||||
|
||||
use crate::ciphertext::{FheAsciiChar, FheString};
|
||||
use crate::server_key::{CharIter, FheStringIsEmpty, ServerKey};
|
||||
use itertools::Itertools;
|
||||
use std::ops::Range;
|
||||
use tfhe::integer::BooleanBlock;
|
||||
|
||||
// Useful for handling cases in which we know if there is or there isn't a match just by looking at
|
||||
// the lengths
|
||||
enum IsMatch {
|
||||
Clear(bool),
|
||||
Cipher(BooleanBlock),
|
||||
None,
|
||||
}
|
||||
|
||||
// `length_checks` allow us to return early in the pattern matching functions, while the other
|
||||
// methods below contain logic for the different cases
|
||||
impl ServerKey {
|
||||
fn length_checks(&self, str: &FheString, pat: &FheString) -> IsMatch {
|
||||
let pat_len = pat.len();
|
||||
let str_len = str.len();
|
||||
|
||||
// If the pattern is empty it will match any string, this is the behavior of core::str
|
||||
// Note that this doesn't handle the case where pattern is empty and has > 1 padding zeros
|
||||
if pat_len == 0 || (pat.is_padded() && pat_len == 1) {
|
||||
return IsMatch::Clear(true);
|
||||
}
|
||||
|
||||
// If our string is an empty string we are just looking if the pattern is also empty (the
|
||||
// only case remaining is if pattern padding > 1).
|
||||
if str_len == 0 || (str.is_padded() && str_len == 1) {
|
||||
return match self.is_empty(pat) {
|
||||
FheStringIsEmpty::Padding(value) => IsMatch::Cipher(value),
|
||||
|
||||
_ => IsMatch::Clear(false),
|
||||
};
|
||||
}
|
||||
|
||||
if !pat.is_padded() {
|
||||
// A pattern without padding cannot be contained in a shorter string without padding
|
||||
if !str.is_padded() && (str_len < pat_len) {
|
||||
return IsMatch::Clear(false);
|
||||
}
|
||||
|
||||
// A pattern without padding cannot be contained in a string with padding that is
|
||||
// shorter or of the same length
|
||||
if str.is_padded() && (str_len <= pat_len) {
|
||||
return IsMatch::Clear(false);
|
||||
}
|
||||
}
|
||||
|
||||
IsMatch::None
|
||||
}
|
||||
|
||||
fn ends_with_cases<'a>(
|
||||
&'a self,
|
||||
str: &'a FheString,
|
||||
pat: &'a FheString,
|
||||
null: Option<&'a FheAsciiChar>,
|
||||
) -> (CharIter<'a>, CharIter<'a>, Range<usize>) {
|
||||
let pat_len = pat.len();
|
||||
let str_len = str.len();
|
||||
|
||||
let range;
|
||||
|
||||
let str_chars;
|
||||
let pat_chars;
|
||||
|
||||
match (str.is_padded(), pat.is_padded()) {
|
||||
// If neither has padding we just check if pat matches the `pat_len` last chars or str
|
||||
(false, false) => {
|
||||
str_chars = str.chars().iter().collect_vec();
|
||||
pat_chars = pat.chars().iter().collect_vec();
|
||||
|
||||
let start = str_len - pat_len;
|
||||
|
||||
range = start..start + 1;
|
||||
}
|
||||
|
||||
// If only str is padded we have to check all the possible padding cases. If str is 3
|
||||
// chars long, then it could be "xx\0", "x\0\0" or "\0\0\0", where x != '\0'
|
||||
(true, false) => {
|
||||
str_chars = str.chars()[..str_len - 1].iter().collect_vec();
|
||||
pat_chars = pat
|
||||
.chars()
|
||||
.iter()
|
||||
.chain(std::iter::once(null.unwrap()))
|
||||
.collect_vec();
|
||||
|
||||
let diff = (str_len - 1) - pat_len;
|
||||
|
||||
range = 0..diff + 1;
|
||||
}
|
||||
|
||||
// If only pat is padded we have to check all the possible padding cases as well
|
||||
// If str = "abc" and pat = "abcd\0", we check if "abc\0" == pat[..4]
|
||||
(false, true) => {
|
||||
str_chars = str
|
||||
.chars()
|
||||
.iter()
|
||||
.chain(std::iter::once(null.unwrap()))
|
||||
.collect_vec();
|
||||
pat_chars = pat.chars().iter().collect_vec();
|
||||
|
||||
if pat_len - 1 > str_len {
|
||||
// Pat without last char is longer than str so we check all the str chars
|
||||
range = 0..str_len + 1;
|
||||
} else {
|
||||
// Pat without last char is equal or shorter than str so we check the
|
||||
// `pat_len` - 1 last chars of str
|
||||
let start = str_len - (pat_len - 1);
|
||||
|
||||
range = start..start + pat_len;
|
||||
};
|
||||
}
|
||||
|
||||
(true, true) => {
|
||||
str_chars = str.chars().iter().collect_vec();
|
||||
pat_chars = pat.chars().iter().collect_vec();
|
||||
|
||||
range = 0..str_len;
|
||||
}
|
||||
}
|
||||
|
||||
(str_chars, pat_chars, range)
|
||||
}
|
||||
|
||||
fn clear_ends_with_cases<'a>(
|
||||
&'a self,
|
||||
str: &'a FheString,
|
||||
pat: &str,
|
||||
) -> (CharIter<'a>, String, Range<usize>) {
|
||||
let pat_len = pat.len();
|
||||
let str_len = str.len();
|
||||
|
||||
if str.is_padded() {
|
||||
let str_chars = str.chars()[..str_len - 1].iter().collect();
|
||||
let pat_chars = format!("{pat}\0");
|
||||
|
||||
let diff = (str_len - 1) - pat_len;
|
||||
let range = 0..diff + 1;
|
||||
|
||||
(str_chars, pat_chars, range)
|
||||
} else {
|
||||
let str_chars = str.chars().iter().collect();
|
||||
|
||||
let start = str_len - pat_len;
|
||||
let range = start..start + 1;
|
||||
|
||||
(str_chars, pat.to_owned(), range)
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_cases<'a>(
|
||||
&'a self,
|
||||
str: &'a FheString,
|
||||
pat: &'a FheString,
|
||||
null: Option<&'a FheAsciiChar>,
|
||||
) -> (CharIter<'a>, CharIter<'a>, Range<usize>) {
|
||||
let pat_len = pat.len();
|
||||
let str_len = str.len();
|
||||
|
||||
let str_chars;
|
||||
let pat_chars;
|
||||
|
||||
let range;
|
||||
|
||||
match (str.is_padded(), pat.is_padded()) {
|
||||
(_, false) => {
|
||||
str_chars = str.chars().iter().collect_vec();
|
||||
pat_chars = pat.chars().iter().collect_vec();
|
||||
|
||||
let diff = (str_len - pat_len) - if str.is_padded() { 1 } else { 0 };
|
||||
|
||||
range = 0..diff + 1;
|
||||
}
|
||||
(true, true) => {
|
||||
str_chars = str.chars().iter().collect_vec();
|
||||
pat_chars = pat.chars()[..pat_len - 1].iter().collect_vec();
|
||||
|
||||
range = 0..str_len - 1;
|
||||
}
|
||||
(false, true) => {
|
||||
str_chars = str
|
||||
.chars()
|
||||
.iter()
|
||||
.chain(std::iter::once(null.unwrap()))
|
||||
.collect_vec();
|
||||
|
||||
pat_chars = pat.chars()[..pat_len - 1].iter().collect_vec();
|
||||
|
||||
range = 0..str_len;
|
||||
}
|
||||
}
|
||||
|
||||
(str_chars, pat_chars, range)
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
use crate::ciphertext::{FheString, GenericPattern, UIntArg};
|
||||
use crate::server_key::pattern::IsMatch;
|
||||
use crate::server_key::{FheStringIsEmpty, FheStringLen, ServerKey};
|
||||
use tfhe::integer::prelude::*;
|
||||
use tfhe::integer::{BooleanBlock, RadixCiphertext};
|
||||
|
||||
impl ServerKey {
|
||||
// Replaces the pattern ignoring the first `start` chars (i.e. these are not replaced)
|
||||
// Also returns the length up to the end of `to` in the replaced str, or 0 if there's no match
|
||||
fn replace_once(
|
||||
&self,
|
||||
replace: &BooleanBlock,
|
||||
find_index: &RadixCiphertext,
|
||||
from_len: &FheStringLen,
|
||||
enc_to_len: &RadixCiphertext,
|
||||
str: &FheString,
|
||||
to: &FheString,
|
||||
) -> (FheString, RadixCiphertext) {
|
||||
// When there's match we get the part of the str before and after the pattern by shifting.
|
||||
// Then we concatenate the left part with `to` and with the right part.
|
||||
// Visually:
|
||||
//
|
||||
// 1. We have str = [lhs, from, rhs]
|
||||
//
|
||||
// 2. Get the [lhs] and [rhs] by shifting str right and left, respectively
|
||||
//
|
||||
// 3. Concat [lhs] + [to] + [rhs]
|
||||
//
|
||||
// 4. We get [lhs, to, rhs]
|
||||
|
||||
let (mut replaced, rhs) = rayon::join(
|
||||
|| {
|
||||
let str_len = self.key.create_trivial_radix(str.len() as u32, 16);
|
||||
|
||||
// Get the [lhs] shifting right by [from, rhs].len()
|
||||
let shift_right = self.key.sub_parallelized(&str_len, find_index);
|
||||
let mut lhs = self.right_shift_chars(str, &shift_right);
|
||||
// As lhs is shifted right we know there aren't nulls on the right, unless empty
|
||||
lhs.set_is_padded(false);
|
||||
|
||||
let mut replaced = self.concat(&lhs, to);
|
||||
|
||||
// Reverse the shifting such that nulls go to the new end
|
||||
replaced = self.left_shift_chars(&replaced, &shift_right);
|
||||
replaced.set_is_padded(true);
|
||||
|
||||
replaced
|
||||
},
|
||||
|| {
|
||||
// Get the [rhs] shifting left by [lhs, from].len()
|
||||
let shift_left = match from_len {
|
||||
FheStringLen::NoPadding(len) => {
|
||||
self.key.scalar_add_parallelized(find_index, *len as u32)
|
||||
}
|
||||
FheStringLen::Padding(enc_len) => {
|
||||
self.key.add_parallelized(find_index, enc_len)
|
||||
}
|
||||
};
|
||||
|
||||
let mut rhs = self.left_shift_chars(str, &shift_left);
|
||||
rhs.set_is_padded(true);
|
||||
|
||||
rhs
|
||||
},
|
||||
);
|
||||
|
||||
replaced = self.concat(&replaced, &rhs);
|
||||
|
||||
rayon::join(
|
||||
// Return the replaced value only when there is match, else return the original str
|
||||
|| self.conditional_string(replace, replaced, str),
|
||||
|| {
|
||||
// If there's match we return [lhs, to].len(), else we return 0 (index default)
|
||||
let add_to_index = self.key.if_then_else_parallelized(
|
||||
replace,
|
||||
enc_to_len,
|
||||
&self.key.create_trivial_zero_radix(16),
|
||||
);
|
||||
self.key.add_parallelized(find_index, &add_to_index)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn replace_n_times(
|
||||
&self,
|
||||
iterations: u16,
|
||||
result: &mut FheString,
|
||||
from: &GenericPattern,
|
||||
to: &FheString,
|
||||
enc_n: Option<&RadixCiphertext>,
|
||||
) {
|
||||
let mut skip = self.key.create_trivial_zero_radix(16);
|
||||
let trivial_or_enc_from = match from {
|
||||
GenericPattern::Clear(from) => FheString::trivial(self, from.str()),
|
||||
GenericPattern::Enc(from) => from.clone(),
|
||||
};
|
||||
|
||||
let ((from_is_empty, from_len), (str_len, enc_to_len)) = rayon::join(
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.is_empty(&trivial_or_enc_from),
|
||||
|| self.len(&trivial_or_enc_from),
|
||||
)
|
||||
},
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.len(result),
|
||||
|| match self.len(to) {
|
||||
FheStringLen::Padding(enc_val) => enc_val,
|
||||
FheStringLen::NoPadding(val) => {
|
||||
self.key.create_trivial_radix(val as u32, 16)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
for i in 0..iterations {
|
||||
let prev = result.clone();
|
||||
|
||||
let (_, no_more_matches) = rayon::join(
|
||||
|| {
|
||||
// We first shift str `skip` chars left to ignore them and check if there's a
|
||||
// match
|
||||
let shifted_str = self.left_shift_chars(result, &skip);
|
||||
|
||||
let (mut index, is_match) = self.find(&shifted_str, from);
|
||||
|
||||
// We add `skip` to get the actual index of the pattern (in the non shifted str)
|
||||
self.key.add_assign_parallelized(&mut index, &skip);
|
||||
|
||||
(*result, skip) =
|
||||
self.replace_once(&is_match, &index, &from_len, &enc_to_len, result, to);
|
||||
},
|
||||
|| self.no_more_matches(&str_len, &from_is_empty, i, enc_n),
|
||||
);
|
||||
|
||||
let num_blocks = skip.blocks().len();
|
||||
|
||||
rayon::join(
|
||||
|| *result = self.conditional_string(&no_more_matches, prev, result),
|
||||
// If we replace "" to "a" in the "ww" str, we get "awawa". So when `from_is_empty`
|
||||
// we need to move to the next space between letters by adding 1 to the skip value
|
||||
|| match &from_is_empty {
|
||||
FheStringIsEmpty::Padding(enc) => self.key.add_assign_parallelized(
|
||||
&mut skip,
|
||||
&enc.clone().into_radix(num_blocks, &self.key),
|
||||
),
|
||||
FheStringIsEmpty::NoPadding(clear) => {
|
||||
self.key
|
||||
.scalar_add_assign_parallelized(&mut skip, *clear as u8);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn no_more_matches(
|
||||
&self,
|
||||
str_len: &FheStringLen,
|
||||
from_is_empty: &FheStringIsEmpty,
|
||||
current_iteration: u16,
|
||||
enc_n: Option<&RadixCiphertext>,
|
||||
) -> BooleanBlock {
|
||||
let (mut no_more_matches, enc_n_is_exceeded) = rayon::join(
|
||||
// If `from_is_empty` and our iteration exceeds the length of the str, that means
|
||||
// there cannot be more empty string matches.
|
||||
//
|
||||
// For instance "ww" can at most have 3 empty string matches, so we only take the
|
||||
// result at iteration 0, 1, and 2
|
||||
|| {
|
||||
let no_more_matches = match &str_len {
|
||||
FheStringLen::Padding(enc) => {
|
||||
self.key.scalar_lt_parallelized(enc, current_iteration)
|
||||
}
|
||||
FheStringLen::NoPadding(clear) => self
|
||||
.key
|
||||
.create_trivial_boolean_block(*clear < current_iteration as usize),
|
||||
};
|
||||
|
||||
match &from_is_empty {
|
||||
FheStringIsEmpty::Padding(enc) => {
|
||||
self.key.boolean_bitand(&no_more_matches, enc)
|
||||
}
|
||||
FheStringIsEmpty::NoPadding(clear) => {
|
||||
let trivial = self.key.create_trivial_boolean_block(*clear);
|
||||
self.key.boolean_bitand(&no_more_matches, &trivial)
|
||||
}
|
||||
}
|
||||
},
|
||||
|| enc_n.map(|n| self.key.scalar_le_parallelized(n, current_iteration)),
|
||||
);
|
||||
|
||||
if let Some(exceeded) = enc_n_is_exceeded {
|
||||
self.key
|
||||
.boolean_bitor_assign(&mut no_more_matches, &exceeded);
|
||||
}
|
||||
|
||||
no_more_matches
|
||||
}
|
||||
|
||||
fn max_matches(&self, str: &FheString, pat: &FheString) -> u16 {
|
||||
let str_len = str.len() - if str.is_padded() { 1 } else { 0 };
|
||||
|
||||
// Max number of matches is str_len + 1 when pattern is empty
|
||||
let mut max: u16 = (str_len + 1).try_into().expect("str should be shorter");
|
||||
|
||||
// If we know the actual `from` length, the max number of matches can be computed as
|
||||
// str_len - pat_len + 1. For instance "xx" matches "xxxx" at most 4 - 2 + 1 = 3 times.
|
||||
// This works as long as str_len >= pat_len (guaranteed due to the outer length checks)
|
||||
if !pat.is_padded() {
|
||||
let pat_len = pat.len() as u16;
|
||||
max = str_len as u16 - pat_len + 1;
|
||||
}
|
||||
|
||||
max
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with a specified number of non-overlapping occurrences of a
|
||||
/// pattern (either encrypted or clear) replaced by another specified encrypted pattern.
|
||||
///
|
||||
/// The number of replacements to perform is specified by a `UIntArg`, which can be either
|
||||
/// `Clear` or `Enc`. In the `Clear` case, the function uses a plain `u16` value for the count.
|
||||
/// In the `Enc` case, the count is an encrypted `u16` value, encrypted with `ck.encrypt_u16`.
|
||||
///
|
||||
/// If the pattern to be replaced is not found or the count is zero, returns the original
|
||||
/// encrypted string unmodified.
|
||||
///
|
||||
/// The pattern to search for can be either `GenericPattern::Clear` for a clear string or
|
||||
/// `GenericPattern::Enc` for an encrypted string, while the replacement pattern is always
|
||||
/// encrypted.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern, UIntArg};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, from, to) = ("hello", "l", "r");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_from = GenericPattern::Enc(FheString::new(&ck, &from, None));
|
||||
/// let enc_to = FheString::new(&ck, &to, None);
|
||||
///
|
||||
/// // Using Clear count
|
||||
/// let clear_count = UIntArg::Clear(1);
|
||||
/// let result_clear = sk.replacen(&enc_s, &enc_from, &enc_to, &clear_count);
|
||||
/// let replaced_clear = ck.decrypt_ascii(&result_clear);
|
||||
///
|
||||
/// assert_eq!(replaced_clear, "herlo");
|
||||
///
|
||||
/// // Using Encrypted count
|
||||
/// let max = 1; // Restricts the range of enc_n to 0..=max
|
||||
/// let enc_n = ck.encrypt_u16(1, Some(max));
|
||||
/// let enc_count = UIntArg::Enc(enc_n);
|
||||
/// let result_enc = sk.replacen(&enc_s, &enc_from, &enc_to, &enc_count);
|
||||
/// let replaced_enc = ck.decrypt_ascii(&result_enc);
|
||||
///
|
||||
/// assert_eq!(replaced_enc, "herlo");
|
||||
/// ```
|
||||
pub fn replacen(
|
||||
&self,
|
||||
str: &FheString,
|
||||
from: &GenericPattern,
|
||||
to: &FheString,
|
||||
count: &UIntArg,
|
||||
) -> FheString {
|
||||
let mut result = str.clone();
|
||||
|
||||
if let UIntArg::Clear(0) = count {
|
||||
return result;
|
||||
}
|
||||
|
||||
let trivial_or_enc_from = match from {
|
||||
GenericPattern::Clear(from) => FheString::trivial(self, from.str()),
|
||||
GenericPattern::Enc(from) => from.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_from) {
|
||||
IsMatch::Clear(false) => return result,
|
||||
|
||||
IsMatch::Clear(true) => {
|
||||
// If `from` is empty and str too, there's only one match and one replacement
|
||||
if str.is_empty() {
|
||||
if let UIntArg::Clear(_) = count {
|
||||
return to.clone();
|
||||
}
|
||||
|
||||
// We have to take into account that encrypted n could be 0
|
||||
if let UIntArg::Enc(enc_n) = count {
|
||||
let n_is_zero = self.key.scalar_eq_parallelized(enc_n.cipher(), 0);
|
||||
|
||||
let mut re = self.conditional_string(&n_is_zero, result, to);
|
||||
|
||||
// When result or to are empty we get padding via the conditional_string
|
||||
// (pad_ciphertexts_lsb). And the condition result may or may not have
|
||||
// padding in this case.
|
||||
re.append_null(self);
|
||||
return re;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This happens when str is empty, so it's again one replacement if there's match or
|
||||
// if there isn't we return the str
|
||||
IsMatch::Cipher(val) => {
|
||||
if let UIntArg::Clear(_) = count {
|
||||
return self.conditional_string(&val, to.clone(), str);
|
||||
}
|
||||
|
||||
if let UIntArg::Enc(enc_n) = count {
|
||||
let n_not_zero = self.key.scalar_ne_parallelized(enc_n.cipher(), 0);
|
||||
let and_val = self.key.boolean_bitand(&n_not_zero, &val);
|
||||
|
||||
let mut re = self.conditional_string(&and_val, to.clone(), str);
|
||||
|
||||
// When result or to are empty we get padding via the conditional_string
|
||||
// (pad_ciphertexts_lsb). And the condition result may or may not have
|
||||
// padding in this case.
|
||||
re.append_null(self);
|
||||
return re;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match count {
|
||||
UIntArg::Clear(n) => {
|
||||
let max = self.max_matches(str, &trivial_or_enc_from);
|
||||
|
||||
// If n > max number of matches we use that max to avoid unnecessary iterations
|
||||
let iterations = if *n > max { max } else { *n };
|
||||
|
||||
self.replace_n_times(iterations, &mut result, from, to, None);
|
||||
}
|
||||
|
||||
UIntArg::Enc(enc_n) => {
|
||||
// As we don't know the number n we perform the maximum number of iterations
|
||||
let max = enc_n.max().unwrap_or(u16::MAX);
|
||||
|
||||
self.replace_n_times(max, &mut result, from, to, Some(enc_n.cipher()));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with all non-overlapping occurrences of a pattern (either
|
||||
/// encrypted or clear) replaced by another specified encrypted pattern.
|
||||
///
|
||||
/// If the pattern to be replaced is not found, returns the original encrypted string
|
||||
/// unmodified.
|
||||
///
|
||||
/// The pattern to search for can be either `GenericPattern::Clear` for a clear string or
|
||||
/// `GenericPattern::Enc` for an encrypted string, while the replacement pattern is always
|
||||
/// encrypted.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{ClearString, FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, from, to) = ("hi", "i", "o");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_from = GenericPattern::Enc(FheString::new(&ck, &from, None));
|
||||
/// let enc_to = FheString::new(&ck, &to, None);
|
||||
///
|
||||
/// let result = sk.replace(&enc_s, &enc_from, &enc_to);
|
||||
/// let replaced = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(replaced, "ho"); // "i" is replaced by "o" in "hi"
|
||||
///
|
||||
/// let clear_from_not_found = GenericPattern::Clear(ClearString::new(String::from("x")));
|
||||
/// let result_no_change = sk.replace(&enc_s, &clear_from_not_found, &enc_to);
|
||||
/// let not_replaced = ck.decrypt_ascii(&result_no_change);
|
||||
///
|
||||
/// assert_eq!(not_replaced, "hi"); // No match, original string returned
|
||||
/// ```
|
||||
pub fn replace(&self, str: &FheString, from: &GenericPattern, to: &FheString) -> FheString {
|
||||
let mut result = str.clone();
|
||||
let trivial_or_enc_from = match from {
|
||||
GenericPattern::Clear(from) => FheString::trivial(self, from.str()),
|
||||
GenericPattern::Enc(from) => from.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_from) {
|
||||
IsMatch::Clear(false) => return result,
|
||||
IsMatch::Clear(true) => {
|
||||
// If `from` is empty and str too, there's only one match and one replacement
|
||||
if str.is_empty() {
|
||||
return to.clone();
|
||||
}
|
||||
}
|
||||
// This happens when str is empty, so it's again one replacement if there's match or
|
||||
// if there isn't we return the str
|
||||
IsMatch::Cipher(val) => return self.conditional_string(&val, to.clone(), str),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let max = self.max_matches(str, &trivial_or_enc_from);
|
||||
|
||||
self.replace_n_times(max, &mut result, from, to, None);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
mod split_iters;
|
||||
|
||||
use crate::ciphertext::{FheString, GenericPattern, UIntArg};
|
||||
use crate::server_key::pattern::IsMatch;
|
||||
use crate::server_key::{FheStringIsEmpty, FheStringIterator, FheStringLen, ServerKey};
|
||||
use tfhe::integer::{BooleanBlock, RadixCiphertext};
|
||||
|
||||
impl ServerKey {
|
||||
fn split_pat_at_index(
|
||||
&self,
|
||||
str: &FheString,
|
||||
pat: &GenericPattern,
|
||||
index: &RadixCiphertext,
|
||||
inclusive: bool,
|
||||
) -> (FheString, FheString) {
|
||||
let str_len = self.key.create_trivial_radix(str.len() as u32, 16);
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
let (mut shift_right, real_pat_len) = rayon::join(
|
||||
|| self.key.sub_parallelized(&str_len, index),
|
||||
|| match self.len(&trivial_or_enc_pat) {
|
||||
FheStringLen::Padding(enc_val) => enc_val,
|
||||
FheStringLen::NoPadding(val) => self.key.create_trivial_radix(val as u32, 16),
|
||||
},
|
||||
);
|
||||
|
||||
let (mut lhs, mut rhs) = rayon::join(
|
||||
|| {
|
||||
if inclusive {
|
||||
// Remove the real pattern length from the amount to shift
|
||||
self.key
|
||||
.sub_assign_parallelized(&mut shift_right, &real_pat_len);
|
||||
}
|
||||
|
||||
let lhs = self.right_shift_chars(str, &shift_right);
|
||||
|
||||
// lhs potentially has nulls in the leftmost chars as we have shifted str right, so
|
||||
// we move back the nulls to the end by performing the reverse shift
|
||||
self.left_shift_chars(&lhs, &shift_right)
|
||||
},
|
||||
|| {
|
||||
let shift_left = self.key.add_parallelized(&real_pat_len, index);
|
||||
|
||||
self.left_shift_chars(str, &shift_left)
|
||||
},
|
||||
);
|
||||
|
||||
// If original str is padded we set both sub strings padded as well. If str was not padded,
|
||||
// then we don't know if a sub string is padded or not, so we add a null to both
|
||||
// because we cannot assume one isn't padded
|
||||
if str.is_padded() {
|
||||
lhs.set_is_padded(true);
|
||||
rhs.set_is_padded(true);
|
||||
} else {
|
||||
lhs.append_null(self);
|
||||
rhs.append_null(self);
|
||||
}
|
||||
|
||||
(lhs, rhs)
|
||||
}
|
||||
|
||||
/// Splits the encrypted string into two substrings at the last occurrence of the pattern
|
||||
/// (either encrypted or clear) and returns a tuple of the two substrings along with a boolean
|
||||
/// indicating if the split occurred.
|
||||
///
|
||||
/// If the pattern is not found returns `false`, indicating the equivalent of `None`.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = (" hello world", " ");
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let (lhs, rhs, split_occurred) = sk.rsplit_once(&enc_s, &enc_pat);
|
||||
///
|
||||
/// let lhs_decrypted = ck.decrypt_ascii(&lhs);
|
||||
/// let rhs_decrypted = ck.decrypt_ascii(&rhs);
|
||||
/// let split_occurred = ck.key().decrypt_bool(&split_occurred);
|
||||
///
|
||||
/// assert_eq!(lhs_decrypted, " hello");
|
||||
/// assert_eq!(rhs_decrypted, "world");
|
||||
/// assert!(split_occurred);
|
||||
/// ```
|
||||
pub fn rsplit_once(
|
||||
&self,
|
||||
str: &FheString,
|
||||
pat: &GenericPattern,
|
||||
) -> (FheString, FheString, BooleanBlock) {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
IsMatch::Clear(val) => {
|
||||
return if val {
|
||||
// `val` is set only when the pattern is empty, so the last match is at the end
|
||||
(
|
||||
str.clone(),
|
||||
FheString::empty(),
|
||||
self.key.create_trivial_boolean_block(true),
|
||||
)
|
||||
} else {
|
||||
// There's no match so we default to empty string and str
|
||||
(
|
||||
FheString::empty(),
|
||||
str.clone(),
|
||||
self.key.create_trivial_boolean_block(false),
|
||||
)
|
||||
};
|
||||
}
|
||||
// This is only returned when str is empty so both sub-strings are empty as well
|
||||
IsMatch::Cipher(enc_val) => return (FheString::empty(), FheString::empty(), enc_val),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (index, is_match) = self.rfind(str, pat);
|
||||
|
||||
let (lhs, rhs) = self.split_pat_at_index(str, pat, &index, false);
|
||||
|
||||
(lhs, rhs, is_match)
|
||||
}
|
||||
|
||||
/// Splits the encrypted string into two substrings at the first occurrence of the pattern
|
||||
/// (either encrypted or clear) and returns a tuple of the two substrings along with a boolean
|
||||
/// indicating if the split occurred.
|
||||
///
|
||||
/// If the pattern is not found returns `false`, indicating the equivalent of `None`.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = (" hello world", " ");
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let (lhs, rhs, split_occurred) = sk.split_once(&enc_s, &enc_pat);
|
||||
///
|
||||
/// let lhs_decrypted = ck.decrypt_ascii(&lhs);
|
||||
/// let rhs_decrypted = ck.decrypt_ascii(&rhs);
|
||||
/// let split_occurred = ck.key().decrypt_bool(&split_occurred);
|
||||
///
|
||||
/// assert_eq!(lhs_decrypted, "");
|
||||
/// assert_eq!(rhs_decrypted, "hello world");
|
||||
/// assert!(split_occurred);
|
||||
/// ```
|
||||
pub fn split_once(
|
||||
&self,
|
||||
str: &FheString,
|
||||
pat: &GenericPattern,
|
||||
) -> (FheString, FheString, BooleanBlock) {
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
IsMatch::Clear(val) => {
|
||||
return if val {
|
||||
// `val` is set only when the pattern is empty, so the first match is index 0
|
||||
(
|
||||
FheString::empty(),
|
||||
str.clone(),
|
||||
self.key.create_trivial_boolean_block(true),
|
||||
)
|
||||
} else {
|
||||
// There's no match so we default to empty string and str
|
||||
(
|
||||
FheString::empty(),
|
||||
str.clone(),
|
||||
self.key.create_trivial_boolean_block(false),
|
||||
)
|
||||
};
|
||||
}
|
||||
// This is only returned when str is empty so both sub-strings are empty as well
|
||||
IsMatch::Cipher(enc_val) => return (FheString::empty(), FheString::empty(), enc_val),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (index, is_match) = self.find(str, pat);
|
||||
|
||||
let (lhs, rhs) = self.split_pat_at_index(str, pat, &index, false);
|
||||
|
||||
(lhs, rhs, is_match)
|
||||
}
|
||||
|
||||
fn split_internal(
|
||||
&self,
|
||||
str: &FheString,
|
||||
pat: &GenericPattern,
|
||||
split_type: SplitType,
|
||||
) -> SplitInternal {
|
||||
let mut max_counter = match self.len(str) {
|
||||
FheStringLen::Padding(enc_val) => enc_val,
|
||||
FheStringLen::NoPadding(val) => self.key.create_trivial_radix(val as u32, 16),
|
||||
};
|
||||
|
||||
self.key.scalar_add_assign_parallelized(&mut max_counter, 1);
|
||||
|
||||
SplitInternal {
|
||||
split_type,
|
||||
state: str.clone(),
|
||||
pat: pat.clone(),
|
||||
prev_was_some: self.key.create_trivial_boolean_block(true),
|
||||
counter: 0,
|
||||
max_counter,
|
||||
counter_lt_max: self.key.create_trivial_boolean_block(true),
|
||||
}
|
||||
}
|
||||
|
||||
fn splitn_internal(
|
||||
&self,
|
||||
str: &FheString,
|
||||
pat: &GenericPattern,
|
||||
n: UIntArg,
|
||||
split_type: SplitType,
|
||||
) -> SplitNInternal {
|
||||
if let SplitType::SplitInclusive = split_type {
|
||||
panic!("We have either SplitN or RSplitN")
|
||||
}
|
||||
|
||||
let uint_not_0 = match &n {
|
||||
UIntArg::Clear(val) => {
|
||||
if *val != 0 {
|
||||
self.key.create_trivial_boolean_block(true)
|
||||
} else {
|
||||
self.key.create_trivial_boolean_block(false)
|
||||
}
|
||||
}
|
||||
UIntArg::Enc(enc) => self.key.scalar_ne_parallelized(enc.cipher(), 0),
|
||||
};
|
||||
|
||||
let internal = self.split_internal(str, pat, split_type);
|
||||
|
||||
SplitNInternal {
|
||||
internal,
|
||||
n,
|
||||
counter: 0,
|
||||
not_exceeded: uint_not_0,
|
||||
}
|
||||
}
|
||||
|
||||
fn split_no_trailing(
|
||||
&self,
|
||||
str: &FheString,
|
||||
pat: &GenericPattern,
|
||||
split_type: SplitType,
|
||||
) -> SplitNoTrailing {
|
||||
if let SplitType::RSplit = split_type {
|
||||
panic!("Only Split or SplitInclusive")
|
||||
}
|
||||
|
||||
let max_counter = match self.len(str) {
|
||||
FheStringLen::Padding(enc_val) => enc_val,
|
||||
FheStringLen::NoPadding(val) => self.key.create_trivial_radix(val as u32, 16),
|
||||
};
|
||||
|
||||
let internal = SplitInternal {
|
||||
split_type,
|
||||
state: str.clone(),
|
||||
pat: pat.clone(),
|
||||
prev_was_some: self.key.create_trivial_boolean_block(true),
|
||||
counter: 0,
|
||||
max_counter,
|
||||
counter_lt_max: self.key.create_trivial_boolean_block(true),
|
||||
};
|
||||
|
||||
SplitNoTrailing { internal }
|
||||
}
|
||||
|
||||
fn split_no_leading(&self, str: &FheString, pat: &GenericPattern) -> SplitNoLeading {
|
||||
let mut internal = self.split_internal(str, pat, SplitType::RSplit);
|
||||
|
||||
let prev_return = internal.next(self);
|
||||
|
||||
let leading_empty_str = match self.is_empty(&prev_return.0) {
|
||||
FheStringIsEmpty::Padding(enc) => enc,
|
||||
FheStringIsEmpty::NoPadding(clear) => self.key.create_trivial_boolean_block(clear),
|
||||
};
|
||||
|
||||
SplitNoLeading {
|
||||
internal,
|
||||
prev_return,
|
||||
leading_empty_str,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SplitType {
|
||||
Split,
|
||||
RSplit,
|
||||
SplitInclusive,
|
||||
}
|
||||
|
||||
struct SplitInternal {
|
||||
split_type: SplitType,
|
||||
state: FheString,
|
||||
pat: GenericPattern,
|
||||
prev_was_some: BooleanBlock,
|
||||
counter: u16,
|
||||
max_counter: RadixCiphertext,
|
||||
counter_lt_max: BooleanBlock,
|
||||
}
|
||||
|
||||
struct SplitNInternal {
|
||||
internal: SplitInternal,
|
||||
n: UIntArg,
|
||||
counter: u16,
|
||||
not_exceeded: BooleanBlock,
|
||||
}
|
||||
|
||||
struct SplitNoTrailing {
|
||||
internal: SplitInternal,
|
||||
}
|
||||
|
||||
struct SplitNoLeading {
|
||||
internal: SplitInternal,
|
||||
prev_return: (FheString, BooleanBlock),
|
||||
leading_empty_str: BooleanBlock,
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitInternal {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
let trivial_or_enc_pat = match &self.pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(sk, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
let ((mut index, mut is_some), pat_is_empty) = rayon::join(
|
||||
|| {
|
||||
if let SplitType::RSplit = self.split_type {
|
||||
sk.rfind(&self.state, &self.pat)
|
||||
} else {
|
||||
sk.find(&self.state, &self.pat)
|
||||
}
|
||||
},
|
||||
|| match sk.is_empty(&trivial_or_enc_pat) {
|
||||
FheStringIsEmpty::Padding(enc) => enc.into_radix(16, &sk.key),
|
||||
FheStringIsEmpty::NoPadding(clear) => sk.key.create_trivial_radix(clear as u32, 16),
|
||||
},
|
||||
);
|
||||
|
||||
if self.counter > 0 {
|
||||
// If pattern is empty and we aren't in the first next call, we add (in the Split case)
|
||||
// or subtract (in the RSplit case) 1 to the index at which we split the str.
|
||||
//
|
||||
// This is because "ab".split("") returns ["", "a", "b", ""] and, in our case, we have
|
||||
// to manually advance the match index as an empty pattern always matches at the very
|
||||
// start (or end in the rsplit case)
|
||||
|
||||
if let SplitType::RSplit = self.split_type {
|
||||
sk.key.sub_assign_parallelized(&mut index, &pat_is_empty);
|
||||
} else {
|
||||
sk.key.add_assign_parallelized(&mut index, &pat_is_empty);
|
||||
}
|
||||
}
|
||||
|
||||
let (lhs, rhs) = if let SplitType::SplitInclusive = self.split_type {
|
||||
sk.split_pat_at_index(&self.state, &self.pat, &index, true)
|
||||
} else {
|
||||
sk.split_pat_at_index(&self.state, &self.pat, &index, false)
|
||||
};
|
||||
|
||||
let current_is_some = is_some.clone();
|
||||
|
||||
// The moment it's None (no match) we return the remaining state
|
||||
let result = if let SplitType::RSplit = self.split_type {
|
||||
let re = sk.conditional_string(¤t_is_some, rhs, &self.state);
|
||||
|
||||
self.state = lhs;
|
||||
re
|
||||
} else {
|
||||
let re = sk.conditional_string(¤t_is_some, lhs, &self.state);
|
||||
|
||||
self.state = rhs;
|
||||
re
|
||||
};
|
||||
|
||||
// Even if there isn't match, we return Some if there was match in the previous next call,
|
||||
// as we are returning the remaining state "wrapped" in Some
|
||||
sk.key
|
||||
.boolean_bitor_assign(&mut is_some, &self.prev_was_some);
|
||||
|
||||
// If pattern is empty, `is_some` is always true, so we make it false when we have reached
|
||||
// the last possible counter value
|
||||
sk.key
|
||||
.boolean_bitand_assign(&mut is_some, &self.counter_lt_max);
|
||||
|
||||
self.prev_was_some = current_is_some;
|
||||
self.counter_lt_max = sk
|
||||
.key
|
||||
.scalar_gt_parallelized(&self.max_counter, self.counter);
|
||||
|
||||
self.counter += 1;
|
||||
|
||||
(result, is_some)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitNInternal {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
let state = self.internal.state.clone();
|
||||
|
||||
let (mut result, mut is_some) = self.internal.next(sk);
|
||||
|
||||
// This keeps the original `is_some` value unless we have exceeded n
|
||||
sk.key
|
||||
.boolean_bitand_assign(&mut is_some, &self.not_exceeded);
|
||||
|
||||
// The moment counter is at least one less than n we return the remaining state, and make
|
||||
// `not_exceeded` false such that next calls are always None
|
||||
match &self.n {
|
||||
UIntArg::Clear(clear_n) => {
|
||||
if self.counter >= clear_n - 1 {
|
||||
result = state;
|
||||
self.not_exceeded = sk.key.create_trivial_boolean_block(false);
|
||||
}
|
||||
}
|
||||
UIntArg::Enc(enc_n) => {
|
||||
// Note that when `enc_n` is zero `n_minus_one` wraps to a very large number and so
|
||||
// `exceeded` will be false. Nonetheless the initial value of `not_exceeded`
|
||||
// was set to false in the n is zero case, so we return None
|
||||
let n_minus_one = sk.key.scalar_sub_parallelized(enc_n.cipher(), 1);
|
||||
let exceeded = sk.key.scalar_le_parallelized(&n_minus_one, self.counter);
|
||||
|
||||
rayon::join(
|
||||
|| result = sk.conditional_string(&exceeded, state, &result),
|
||||
|| {
|
||||
let current_not_exceeded = sk.key.boolean_bitnot(&exceeded);
|
||||
|
||||
// If current is not exceeded we use the previous not_exceeded value,
|
||||
// or false if it's exceeded
|
||||
sk.key
|
||||
.boolean_bitand_assign(&mut self.not_exceeded, ¤t_not_exceeded);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.counter += 1;
|
||||
|
||||
(result, is_some)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitNoTrailing {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
let (result, mut is_some) = self.internal.next(sk);
|
||||
|
||||
let (result_is_empty, prev_was_none) = rayon::join(
|
||||
// It's possible that the returned value is Some but it's wrapping the remaining state
|
||||
// (if prev_was_some is false). If this is the case and we have a trailing empty
|
||||
// string, we return None to remove it
|
||||
|| match sk.is_empty(&result) {
|
||||
FheStringIsEmpty::Padding(enc) => enc,
|
||||
FheStringIsEmpty::NoPadding(clear) => sk.key.create_trivial_boolean_block(clear),
|
||||
},
|
||||
|| sk.key.boolean_bitnot(&self.internal.prev_was_some),
|
||||
);
|
||||
|
||||
let trailing_empty_str = sk.key.boolean_bitand(&result_is_empty, &prev_was_none);
|
||||
|
||||
let not_trailing_empty_str = sk.key.boolean_bitnot(&trailing_empty_str);
|
||||
|
||||
// If there's no empty trailing string we get the previous `is_some`,
|
||||
// else we get false (None)
|
||||
sk.key
|
||||
.boolean_bitand_assign(&mut is_some, ¬_trailing_empty_str);
|
||||
|
||||
(result, is_some)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitNoLeading {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
// We want to remove the leading empty string i.e. the first returned substring should be
|
||||
// skipped if empty.
|
||||
//
|
||||
// To achieve that we have computed a next call in advance and conditionally assign values
|
||||
// based on the `trailing_empty_str` flag
|
||||
|
||||
let (result, is_some) = self.internal.next(sk);
|
||||
|
||||
let (return_result, return_is_some) = rayon::join(
|
||||
|| sk.conditional_string(&self.leading_empty_str, result.clone(), &self.prev_return.0),
|
||||
|| {
|
||||
let (lhs, rhs) = rayon::join(
|
||||
// This is `is_some` if `leading_empty_str` is true, false otherwise
|
||||
|| sk.key.boolean_bitand(&self.leading_empty_str, &is_some),
|
||||
// This is the flag from the previous next call if `leading_empty_str` is true,
|
||||
// false otherwise
|
||||
|| {
|
||||
sk.key.boolean_bitand(
|
||||
&sk.key.boolean_bitnot(&self.leading_empty_str),
|
||||
&self.prev_return.1,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
sk.key.boolean_bitor(&lhs, &rhs)
|
||||
},
|
||||
);
|
||||
|
||||
self.prev_return = (result, is_some);
|
||||
|
||||
(return_result, return_is_some)
|
||||
}
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
use crate::ciphertext::{FheString, GenericPattern, UIntArg};
|
||||
use crate::server_key::pattern::split::{
|
||||
SplitInternal, SplitNInternal, SplitNoLeading, SplitNoTrailing, SplitType,
|
||||
};
|
||||
use crate::server_key::{FheStringIterator, ServerKey};
|
||||
use tfhe::integer::BooleanBlock;
|
||||
|
||||
pub struct RSplit {
|
||||
internal: SplitInternal,
|
||||
}
|
||||
|
||||
pub struct Split {
|
||||
internal: SplitInternal,
|
||||
}
|
||||
|
||||
pub struct SplitInclusive {
|
||||
internal: SplitNoTrailing,
|
||||
}
|
||||
|
||||
pub struct RSplitN {
|
||||
internal: SplitNInternal,
|
||||
}
|
||||
|
||||
pub struct SplitN {
|
||||
internal: SplitNInternal,
|
||||
}
|
||||
|
||||
pub struct SplitTerminator {
|
||||
internal: SplitNoTrailing,
|
||||
}
|
||||
|
||||
pub struct RSplitTerminator {
|
||||
internal: SplitNoLeading,
|
||||
}
|
||||
|
||||
impl ServerKey {
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string based
|
||||
/// on a specified pattern (either encrypted or clear).
|
||||
///
|
||||
/// The iterator, of type `Split`, can be used to sequentially retrieve the substrings. Each
|
||||
/// call to `next` on the iterator returns a tuple with the next split substring as an encrypted
|
||||
/// string and a boolean indicating `Some` (true) or `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello ", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let mut split_iter = sk.split(&enc_s, &enc_pat);
|
||||
/// let (first_item, first_is_some) = split_iter.next(&sk);
|
||||
/// let (second_item, second_is_some) = split_iter.next(&sk);
|
||||
/// let (_, no_more_items) = split_iter.next(&sk); // Attempting to get a third item
|
||||
///
|
||||
/// let first_decrypted = ck.decrypt_ascii(&first_item);
|
||||
/// let first_is_some = ck.key().decrypt_bool(&first_is_some);
|
||||
/// let second_decrypted = ck.decrypt_ascii(&second_item);
|
||||
/// let second_is_some = ck.key().decrypt_bool(&second_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// assert_eq!(first_decrypted, "hello");
|
||||
/// assert!(first_is_some); // There is a first item
|
||||
/// assert_eq!(second_decrypted, "");
|
||||
/// assert!(second_is_some); // There is a second item
|
||||
/// assert!(!no_more_items); // No more items in the iterator
|
||||
/// ```
|
||||
pub fn split(&self, str: &FheString, pat: &GenericPattern) -> Split {
|
||||
let internal = self.split_internal(str, pat, SplitType::Split);
|
||||
|
||||
Split { internal }
|
||||
}
|
||||
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string from
|
||||
/// the end based on a specified pattern (either encrypted or clear).
|
||||
///
|
||||
/// The iterator, of type `RSplit`, can be used to sequentially retrieve the substrings in
|
||||
/// reverse order. Each call to `next` on the iterator returns a tuple with the next split
|
||||
/// substring as an encrypted string and a boolean indicating `Some` (true) or `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello ", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let mut rsplit_iter = sk.rsplit(&enc_s, &enc_pat);
|
||||
/// let (last_item, last_is_some) = rsplit_iter.next(&sk);
|
||||
/// let (second_last_item, second_last_is_some) = rsplit_iter.next(&sk);
|
||||
/// let (_, no_more_items) = rsplit_iter.next(&sk); // Attempting to get a third item
|
||||
///
|
||||
/// let last_decrypted = ck.decrypt_ascii(&last_item);
|
||||
/// let last_is_some = ck.key().decrypt_bool(&last_is_some);
|
||||
/// let second_last_decrypted = ck.decrypt_ascii(&second_last_item);
|
||||
/// let second_last_is_some = ck.key().decrypt_bool(&second_last_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// assert_eq!(last_decrypted, "");
|
||||
/// assert!(last_is_some); // The last item is empty
|
||||
/// assert_eq!(second_last_decrypted, "hello");
|
||||
/// assert!(second_last_is_some); // The second last item is "hello"
|
||||
/// assert!(!no_more_items); // No more items in the reverse iterator
|
||||
/// ```
|
||||
pub fn rsplit(&self, str: &FheString, pat: &GenericPattern) -> RSplit {
|
||||
let internal = self.split_internal(str, pat, SplitType::RSplit);
|
||||
|
||||
RSplit { internal }
|
||||
}
|
||||
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string based
|
||||
/// on a specified pattern (either encrypted or clear), limited to at most `n` results.
|
||||
///
|
||||
/// The `n` is specified by a `UIntArg`, which can be either `Clear` or `Enc`. The iterator, of
|
||||
/// type `SplitN`, can be used to sequentially retrieve the substrings. Each call to `next` on
|
||||
/// the iterator returns a tuple with the next split substring as an encrypted string and a
|
||||
/// boolean indicating `Some` (true) or `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern, UIntArg};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello world", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// // Using Clear count
|
||||
/// let clear_count = UIntArg::Clear(1);
|
||||
/// let mut splitn_iter = sk.splitn(&enc_s, &enc_pat, clear_count);
|
||||
/// let (first_item, first_is_some) = splitn_iter.next(&sk);
|
||||
/// let (_, no_more_items) = splitn_iter.next(&sk); // Attempting to get a second item
|
||||
///
|
||||
/// let first_decrypted = ck.decrypt_ascii(&first_item);
|
||||
/// let first_is_some = ck.key().decrypt_bool(&first_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// // We get the whole str as n is 1
|
||||
/// assert_eq!(first_decrypted, "hello world");
|
||||
/// assert!(first_is_some);
|
||||
/// assert!(!no_more_items);
|
||||
///
|
||||
/// // Using Encrypted count
|
||||
/// let max = 2; // Restricts the range of enc_n to 0..=max
|
||||
/// let enc_n = ck.encrypt_u16(1, Some(max));
|
||||
/// let enc_count = UIntArg::Enc(enc_n);
|
||||
/// let _splitn_iter_enc = sk.splitn(&enc_s, &enc_pat, enc_count);
|
||||
/// // Similar usage as with Clear count
|
||||
/// ```
|
||||
pub fn splitn(&self, str: &FheString, pat: &GenericPattern, n: UIntArg) -> SplitN {
|
||||
let internal = self.splitn_internal(str, pat, n, SplitType::Split);
|
||||
|
||||
SplitN { internal }
|
||||
}
|
||||
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string from
|
||||
/// the end based on a specified pattern (either encrypted or clear), limited to at most `n`
|
||||
/// results.
|
||||
///
|
||||
/// The `n` is specified by a `UIntArg`, which can be either `Clear` or `Enc`. The iterator, of
|
||||
/// type `RSplitN`, can be used to sequentially retrieve the substrings in reverse order. Each
|
||||
/// call to `next` on the iterator returns a tuple with the next split substring as an encrypted
|
||||
/// string and a boolean indicating `Some` (true) or `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern, UIntArg};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello world", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// // Using Clear count
|
||||
/// let clear_count = UIntArg::Clear(1);
|
||||
/// let mut rsplitn_iter = sk.rsplitn(&enc_s, &enc_pat, clear_count);
|
||||
/// let (last_item, last_is_some) = rsplitn_iter.next(&sk);
|
||||
/// let (_, no_more_items) = rsplitn_iter.next(&sk); // Attempting to get a second item
|
||||
///
|
||||
/// let last_decrypted = ck.decrypt_ascii(&last_item);
|
||||
/// let last_is_some = ck.key().decrypt_bool(&last_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// // We get the whole str as n is 1
|
||||
/// assert_eq!(last_decrypted, "hello world");
|
||||
/// assert!(last_is_some);
|
||||
/// assert!(!no_more_items);
|
||||
///
|
||||
/// // Using Encrypted count
|
||||
/// let max = 2; // Restricts the range of enc_n to 0..=max
|
||||
/// let enc_n = ck.encrypt_u16(1, Some(max));
|
||||
/// let enc_count = UIntArg::Enc(enc_n);
|
||||
/// let _rsplitn_iter_enc = sk.rsplitn(&enc_s, &enc_pat, enc_count);
|
||||
/// // Similar usage as with Clear count
|
||||
/// ```
|
||||
pub fn rsplitn(&self, str: &FheString, pat: &GenericPattern, n: UIntArg) -> RSplitN {
|
||||
let internal = self.splitn_internal(str, pat, n, SplitType::RSplit);
|
||||
|
||||
RSplitN { internal }
|
||||
}
|
||||
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string based
|
||||
/// on a specified pattern (either encrypted or clear), excluding trailing empty substrings.
|
||||
///
|
||||
/// The iterator, of type `SplitTerminator`, can be used to sequentially retrieve the
|
||||
/// substrings. Each call to `next` on the iterator returns a tuple with the next split
|
||||
/// substring as an encrypted string and a boolean indicating `Some` (true) or `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello world ", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let mut split_terminator_iter = sk.split_terminator(&enc_s, &enc_pat);
|
||||
/// let (first_item, first_is_some) = split_terminator_iter.next(&sk);
|
||||
/// let (second_item, second_is_some) = split_terminator_iter.next(&sk);
|
||||
/// let (_, no_more_items) = split_terminator_iter.next(&sk); // Attempting to get a third item
|
||||
///
|
||||
/// let first_decrypted = ck.decrypt_ascii(&first_item);
|
||||
/// let first_is_some = ck.key().decrypt_bool(&first_is_some);
|
||||
/// let second_decrypted = ck.decrypt_ascii(&second_item);
|
||||
/// let second_is_some = ck.key().decrypt_bool(&second_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// assert_eq!(first_decrypted, "hello");
|
||||
/// assert!(first_is_some); // There is a first item
|
||||
/// assert_eq!(second_decrypted, "world");
|
||||
/// assert!(second_is_some); // There is a second item
|
||||
/// assert!(!no_more_items); // No more items in the iterator
|
||||
/// ```
|
||||
pub fn split_terminator(&self, str: &FheString, pat: &GenericPattern) -> SplitTerminator {
|
||||
let internal = self.split_no_trailing(str, pat, SplitType::Split);
|
||||
|
||||
SplitTerminator { internal }
|
||||
}
|
||||
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string from
|
||||
/// the end based on a specified pattern (either encrypted or clear), excluding leading empty
|
||||
/// substrings in the reverse order.
|
||||
///
|
||||
/// The iterator, of type `RSplitTerminator`, can be used to sequentially retrieve the
|
||||
/// substrings in reverse order, ignoring any leading empty substring that would result from
|
||||
/// splitting at the end of the string. Each call to `next` on the iterator returns a tuple with
|
||||
/// the next split substring as an encrypted string and a boolean indicating `Some` (true) or
|
||||
/// `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello world ", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let mut rsplit_terminator_iter = sk.rsplit_terminator(&enc_s, &enc_pat);
|
||||
/// let (last_item, last_is_some) = rsplit_terminator_iter.next(&sk);
|
||||
/// let (second_last_item, second_last_is_some) = rsplit_terminator_iter.next(&sk);
|
||||
/// let (_, no_more_items) = rsplit_terminator_iter.next(&sk); // Attempting to get a third item
|
||||
///
|
||||
/// let last_decrypted = ck.decrypt_ascii(&last_item);
|
||||
/// let last_is_some = ck.key().decrypt_bool(&last_is_some);
|
||||
/// let second_last_decrypted = ck.decrypt_ascii(&second_last_item);
|
||||
/// let second_last_is_some = ck.key().decrypt_bool(&second_last_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// assert_eq!(last_decrypted, "world");
|
||||
/// assert!(last_is_some); // The last item is "world" instead of ""
|
||||
/// assert_eq!(second_last_decrypted, "hello");
|
||||
/// assert!(second_last_is_some); // The second last item is "hello"
|
||||
/// assert!(!no_more_items); // No more items in the reverse iterator
|
||||
/// ```
|
||||
pub fn rsplit_terminator(&self, str: &FheString, pat: &GenericPattern) -> RSplitTerminator {
|
||||
let internal = self.split_no_leading(str, pat);
|
||||
|
||||
RSplitTerminator { internal }
|
||||
}
|
||||
|
||||
/// Creates an iterator of encrypted substrings by splitting the original encrypted string based
|
||||
/// on a specified pattern (either encrypted or clear), where each substring includes the
|
||||
/// delimiter. If the string ends with the delimiter, it does not create a trailing empty
|
||||
/// substring.
|
||||
///
|
||||
/// The iterator, of type `SplitInclusive`, can be used to sequentially retrieve the substrings.
|
||||
/// Each call to `next` on the iterator returns a tuple with the next split substring as an
|
||||
/// encrypted string and a boolean indicating `Some` (true) or `None` (false).
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{FheString, GenericPattern};
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, pat) = ("hello world ", " ");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_pat = GenericPattern::Enc(FheString::new(&ck, &pat, None));
|
||||
///
|
||||
/// let mut split_inclusive_iter = sk.split_inclusive(&enc_s, &enc_pat);
|
||||
/// let (first_item, first_is_some) = split_inclusive_iter.next(&sk);
|
||||
/// let (second_item, second_is_some) = split_inclusive_iter.next(&sk);
|
||||
/// let (_, no_more_items) = split_inclusive_iter.next(&sk); // Attempting to get a third item
|
||||
///
|
||||
/// let first_decrypted = ck.decrypt_ascii(&first_item);
|
||||
/// let first_is_some = ck.key().decrypt_bool(&first_is_some);
|
||||
/// let second_decrypted = ck.decrypt_ascii(&second_item);
|
||||
/// let second_is_some = ck.key().decrypt_bool(&second_is_some);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// assert_eq!(first_decrypted, "hello ");
|
||||
/// assert!(first_is_some); // The first item includes the delimiter
|
||||
/// assert_eq!(second_decrypted, "world ");
|
||||
/// assert!(second_is_some); // The second item includes the delimiter
|
||||
/// assert!(!no_more_items); // No more items in the iterator, no trailing empty string
|
||||
/// ```
|
||||
pub fn split_inclusive(&self, str: &FheString, pat: &GenericPattern) -> SplitInclusive {
|
||||
let internal = self.split_no_trailing(str, pat, SplitType::SplitInclusive);
|
||||
|
||||
SplitInclusive { internal }
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for Split {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for RSplit {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitN {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for RSplitN {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitTerminator {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for RSplitTerminator {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitInclusive {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
self.internal.next(sk)
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
use crate::ciphertext::{FheAsciiChar, FheString, GenericPattern};
|
||||
use crate::server_key::pattern::IsMatch;
|
||||
use crate::server_key::{CharIter, FheStringLen, ServerKey};
|
||||
use rayon::prelude::*;
|
||||
use std::ops::Range;
|
||||
use tfhe::integer::prelude::*;
|
||||
use tfhe::integer::BooleanBlock;
|
||||
|
||||
impl ServerKey {
|
||||
fn compare_shifted_strip(
|
||||
&self,
|
||||
strip_str: &mut FheString,
|
||||
str_pat: (CharIter, CharIter),
|
||||
iter: Range<usize>,
|
||||
) -> BooleanBlock {
|
||||
let mut result = self.key.create_trivial_boolean_block(false);
|
||||
let (str, pat) = str_pat;
|
||||
|
||||
let pat_len = pat.len();
|
||||
let str_len = str.len();
|
||||
|
||||
for start in iter {
|
||||
let is_matched = self.asciis_eq(str.iter().copied().skip(start), pat.iter().copied());
|
||||
|
||||
let mut mask = is_matched.clone().into_radix(4, &self.key);
|
||||
|
||||
// If mask == 0u8, it will now be 255u8. If it was 1u8, it will now be 0u8
|
||||
self.key.scalar_sub_assign_parallelized(&mut mask, 1);
|
||||
|
||||
let mutate_chars = strip_str.chars_mut().par_iter_mut().skip(start).take(
|
||||
if start + pat_len < str_len {
|
||||
pat_len
|
||||
} else {
|
||||
str_len
|
||||
},
|
||||
);
|
||||
|
||||
rayon::join(
|
||||
|| {
|
||||
mutate_chars.for_each(|char| {
|
||||
self.key
|
||||
.bitand_assign_parallelized(char.ciphertext_mut(), &mask);
|
||||
});
|
||||
},
|
||||
// One of the possible values of pat must match the str
|
||||
|| self.key.boolean_bitor_assign(&mut result, &is_matched),
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn clear_compare_shifted_strip(
|
||||
&self,
|
||||
strip_str: &mut FheString,
|
||||
str_pat: (CharIter, &str),
|
||||
iter: Range<usize>,
|
||||
) -> BooleanBlock {
|
||||
let mut result = self.key.create_trivial_boolean_block(false);
|
||||
let (str, pat) = str_pat;
|
||||
|
||||
let pat_len = pat.len();
|
||||
let str_len = str.len();
|
||||
for start in iter {
|
||||
let is_matched = self.clear_asciis_eq(str.iter().copied().skip(start), pat);
|
||||
|
||||
let mut mask = is_matched.clone().into_radix(4, &self.key);
|
||||
|
||||
// If mask == 0u8, it will now be 255u8. If it was 1u8, it will now be 0u8
|
||||
self.key.scalar_sub_assign_parallelized(&mut mask, 1);
|
||||
|
||||
let mutate_chars = strip_str.chars_mut().par_iter_mut().skip(start).take(
|
||||
if start + pat_len < str_len {
|
||||
pat_len
|
||||
} else {
|
||||
str_len
|
||||
},
|
||||
);
|
||||
|
||||
rayon::join(
|
||||
|| {
|
||||
mutate_chars.for_each(|char| {
|
||||
self.key
|
||||
.bitand_assign_parallelized(char.ciphertext_mut(), &mask);
|
||||
});
|
||||
},
|
||||
// One of the possible values of pat must match the str
|
||||
|| self.key.boolean_bitor_assign(&mut result, &is_matched),
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with the specified pattern (either encrypted or clear)
|
||||
/// removed from the start of this encrypted string, if it matches. Also returns a boolean
|
||||
/// indicating if the pattern was found and removed.
|
||||
///
|
||||
/// If the pattern does not match the start of the string, returns the original encrypted
|
||||
/// string and a boolean set to `false`, indicating the equivalent of `None`.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{ClearString, FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, prefix, not_prefix) = ("hello world", "hello", "world");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_prefix = GenericPattern::Enc(FheString::new(&ck, &prefix, None));
|
||||
/// let clear_not_prefix = GenericPattern::Clear(ClearString::new(not_prefix.to_string()));
|
||||
///
|
||||
/// let (result, found) = sk.strip_prefix(&enc_s, &enc_prefix);
|
||||
/// let stripped = ck.decrypt_ascii(&result);
|
||||
/// let found = ck.key().decrypt_bool(&found);
|
||||
///
|
||||
/// let (result_no_match, not_found) = sk.strip_prefix(&enc_s, &clear_not_prefix);
|
||||
/// let not_stripped = ck.decrypt_ascii(&result_no_match);
|
||||
/// let not_found = ck.key().decrypt_bool(¬_found);
|
||||
///
|
||||
/// assert!(found);
|
||||
/// assert_eq!(stripped, " world"); // "hello" is stripped from "hello world"
|
||||
///
|
||||
/// assert!(!not_found);
|
||||
/// assert_eq!(not_stripped, "hello world"); // No match, original string returned
|
||||
/// ```
|
||||
pub fn strip_prefix(&self, str: &FheString, pat: &GenericPattern) -> (FheString, BooleanBlock) {
|
||||
let mut result = str.clone();
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
// If IsMatch is Clear we return the same string (a true means the pattern is empty)
|
||||
IsMatch::Clear(bool) => return (result, self.key.create_trivial_boolean_block(bool)),
|
||||
|
||||
// If IsMatch is Cipher it means str is empty so in any case we return the same string
|
||||
IsMatch::Cipher(val) => return (result, val),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (starts_with, real_pat_len) = rayon::join(
|
||||
|| self.starts_with(str, pat),
|
||||
|| match self.len(&trivial_or_enc_pat) {
|
||||
FheStringLen::Padding(enc_val) => enc_val,
|
||||
FheStringLen::NoPadding(val) => self.key.create_trivial_radix(val as u32, 16),
|
||||
},
|
||||
);
|
||||
|
||||
// If there's match we shift the str left by `real_pat_len` (removing the prefix and adding
|
||||
// nulls at the end), else we shift it left by 0
|
||||
let shift_left = self.key.if_then_else_parallelized(
|
||||
&starts_with,
|
||||
&real_pat_len,
|
||||
&self.key.create_trivial_zero_radix(16),
|
||||
);
|
||||
|
||||
result = self.left_shift_chars(str, &shift_left);
|
||||
|
||||
// If str was not padded originally we don't know if result has nulls at the end or not (we
|
||||
// don't know if str was shifted or not) so we ensure it's padded in order to be
|
||||
// used in other functions safely
|
||||
if !str.is_padded() {
|
||||
result.append_null(self);
|
||||
} else {
|
||||
result.set_is_padded(true);
|
||||
}
|
||||
|
||||
(result, starts_with)
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with the specified pattern (either encrypted or clear)
|
||||
/// removed from the end of this encrypted string, if it matches. Also returns a boolean
|
||||
/// indicating if the pattern was found and removed.
|
||||
///
|
||||
/// If the pattern does not match the end of the string, returns the original encrypted string
|
||||
/// and a boolean set to `false`, indicating the equivalent of `None`.
|
||||
///
|
||||
/// The pattern to search for can be specified as either `GenericPattern::Clear` for a clear
|
||||
/// string or `GenericPattern::Enc` for an encrypted string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::{ClearString, FheString, GenericPattern};
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let (s, suffix, not_suffix) = ("hello world", "world", "hello");
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
/// let enc_suffix = GenericPattern::Enc(FheString::new(&ck, &suffix, None));
|
||||
/// let clear_not_suffix = GenericPattern::Clear(ClearString::new(not_suffix.to_string()));
|
||||
///
|
||||
/// let (result, found) = sk.strip_suffix(&enc_s, &enc_suffix);
|
||||
/// let stripped = ck.decrypt_ascii(&result);
|
||||
/// let found = ck.key().decrypt_bool(&found);
|
||||
///
|
||||
/// let (result_no_match, not_found) = sk.strip_suffix(&enc_s, &clear_not_suffix);
|
||||
/// let not_stripped = ck.decrypt_ascii(&result_no_match);
|
||||
/// let not_found = ck.key().decrypt_bool(¬_found);
|
||||
///
|
||||
/// assert!(found);
|
||||
/// assert_eq!(stripped, "hello "); // "world" is stripped from "hello world"
|
||||
///
|
||||
/// assert!(!not_found);
|
||||
/// assert_eq!(not_stripped, "hello world"); // No match, original string returned
|
||||
/// ```
|
||||
pub fn strip_suffix(&self, str: &FheString, pat: &GenericPattern) -> (FheString, BooleanBlock) {
|
||||
let mut result = str.clone();
|
||||
|
||||
let trivial_or_enc_pat = match pat {
|
||||
GenericPattern::Clear(pat) => FheString::trivial(self, pat.str()),
|
||||
GenericPattern::Enc(pat) => pat.clone(),
|
||||
};
|
||||
|
||||
match self.length_checks(str, &trivial_or_enc_pat) {
|
||||
// If IsMatch is Clear we return the same string (a true means the pattern is empty)
|
||||
IsMatch::Clear(bool) => return (result, self.key.create_trivial_boolean_block(bool)),
|
||||
|
||||
// If IsMatch is Cipher it means str is empty so in any case we return the same string
|
||||
IsMatch::Cipher(val) => return (result, val),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let is_match = match pat {
|
||||
GenericPattern::Clear(pat) => {
|
||||
let (str_iter, clear_pat, iter) = self.clear_ends_with_cases(str, pat.str());
|
||||
|
||||
self.clear_compare_shifted_strip(&mut result, (str_iter, &clear_pat), iter)
|
||||
}
|
||||
GenericPattern::Enc(pat) => {
|
||||
let null = (str.is_padded() ^ pat.is_padded()).then_some(FheAsciiChar::null(self));
|
||||
|
||||
let (str_iter, pat_iter, iter) = self.ends_with_cases(str, pat, null.as_ref());
|
||||
|
||||
self.compare_shifted_strip(&mut result, (str_iter, pat_iter), iter)
|
||||
}
|
||||
};
|
||||
|
||||
// If str was originally non padded, the result is now potentially padded as we may have
|
||||
// made the last chars null, so we ensure it's padded in order to be used as input
|
||||
// to other functions safely
|
||||
if !str.is_padded() {
|
||||
result.append_null(self);
|
||||
}
|
||||
|
||||
(result, is_match)
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
use crate::ciphertext::{FheAsciiChar, FheString};
|
||||
use crate::server_key::{FheStringIsEmpty, FheStringIterator, FheStringLen, ServerKey};
|
||||
use rayon::prelude::*;
|
||||
use tfhe::integer::prelude::*;
|
||||
use tfhe::integer::{BooleanBlock, RadixCiphertext};
|
||||
|
||||
pub struct SplitAsciiWhitespace {
|
||||
state: FheString,
|
||||
current_mask: Option<FheString>,
|
||||
}
|
||||
|
||||
impl FheStringIterator for SplitAsciiWhitespace {
|
||||
fn next(&mut self, sk: &ServerKey) -> (FheString, BooleanBlock) {
|
||||
let str_len = self.state.len();
|
||||
|
||||
if str_len == 0 || (self.state.is_padded() && str_len == 1) {
|
||||
return (
|
||||
FheString::empty(),
|
||||
sk.key.create_trivial_boolean_block(false),
|
||||
);
|
||||
}
|
||||
|
||||
// If we aren't in the first next call `current_mask` is some
|
||||
if self.current_mask.is_some() {
|
||||
self.remaining_string(sk);
|
||||
}
|
||||
|
||||
let state_after_trim = sk.trim_start(&self.state);
|
||||
self.state = state_after_trim.clone();
|
||||
|
||||
rayon::join(
|
||||
|| self.create_and_apply_mask(sk),
|
||||
|| {
|
||||
// If state after trim_start is empty it means the remaining string was either
|
||||
// empty or only whitespace. Hence, there are no more elements to return
|
||||
if let FheStringIsEmpty::Padding(val) = sk.is_empty(&state_after_trim) {
|
||||
sk.key.boolean_bitnot(&val)
|
||||
} else {
|
||||
panic!("Empty str case was handled so 'state_after_trim' is padded")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SplitAsciiWhitespace {
|
||||
// The mask contains 255u8 until we find some whitespace, then will be 0u8
|
||||
fn create_and_apply_mask(&mut self, sk: &ServerKey) -> FheString {
|
||||
let mut mask = self.state.clone();
|
||||
let mut result = self.state.clone();
|
||||
|
||||
let mut prev_was_not = sk.key.create_trivial_boolean_block(true);
|
||||
for char in mask.chars_mut().iter_mut() {
|
||||
let mut is_not_ws = sk.is_not_whitespace(char);
|
||||
sk.key.boolean_bitand_assign(&mut is_not_ws, &prev_was_not);
|
||||
|
||||
let mut mask_u8 = is_not_ws.clone().into_radix(4, &sk.key);
|
||||
|
||||
// 0u8 is kept the same, but 1u8 is transformed into 255u8
|
||||
sk.key.scalar_sub_assign_parallelized(&mut mask_u8, 1);
|
||||
sk.key.bitnot_assign(&mut mask_u8);
|
||||
|
||||
*char.ciphertext_mut() = mask_u8;
|
||||
|
||||
prev_was_not = is_not_ws;
|
||||
}
|
||||
|
||||
// Apply the mask to get the result
|
||||
result
|
||||
.chars_mut()
|
||||
.par_iter_mut()
|
||||
.zip(mask.chars().par_iter())
|
||||
.for_each(|(char, mask_u8)| {
|
||||
sk.key
|
||||
.bitand_assign_parallelized(char.ciphertext_mut(), mask_u8.ciphertext());
|
||||
});
|
||||
|
||||
self.current_mask = Some(mask);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Shifts the string left to get the remaining string (starting at the next first whitespace)
|
||||
fn remaining_string(&mut self, sk: &ServerKey) {
|
||||
let mask = self.current_mask.as_ref().unwrap();
|
||||
|
||||
let mut number_of_trues: RadixCiphertext = sk.key.create_trivial_zero_radix(16);
|
||||
for mask_u8 in mask.chars() {
|
||||
let is_true = sk.key.scalar_eq_parallelized(mask_u8.ciphertext(), 255u8);
|
||||
|
||||
let num_blocks = number_of_trues.blocks().len();
|
||||
|
||||
sk.key.add_assign_parallelized(
|
||||
&mut number_of_trues,
|
||||
&is_true.into_radix(num_blocks, &sk.key),
|
||||
);
|
||||
}
|
||||
|
||||
let padded = self.state.is_padded();
|
||||
|
||||
self.state = sk.left_shift_chars(&self.state, &number_of_trues);
|
||||
|
||||
if padded {
|
||||
self.state.set_is_padded(true);
|
||||
} else {
|
||||
// If it was not padded now we cannot assume it's not padded (because of the left shift)
|
||||
// so we add a null to ensure it's always padded
|
||||
self.state.append_null(sk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerKey {
|
||||
// As specified in https://doc.rust-lang.org/core/primitive.char.html#method.is_ascii_whitespace
|
||||
fn is_whitespace(&self, char: &FheAsciiChar, or_null: bool) -> BooleanBlock {
|
||||
let (((is_space, is_tab), (is_new_line, is_form_feed)), (is_carriage_return, op_is_null)) =
|
||||
rayon::join(
|
||||
|| {
|
||||
rayon::join(
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.key.scalar_eq_parallelized(char.ciphertext(), 0x20u8),
|
||||
|| self.key.scalar_eq_parallelized(char.ciphertext(), 0x09u8),
|
||||
)
|
||||
},
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.key.scalar_eq_parallelized(char.ciphertext(), 0x0Au8),
|
||||
|| self.key.scalar_eq_parallelized(char.ciphertext(), 0x0Cu8),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.key.scalar_eq_parallelized(char.ciphertext(), 0x0Du8),
|
||||
|| {
|
||||
or_null
|
||||
.then_some(self.key.scalar_eq_parallelized(char.ciphertext(), 0u8))
|
||||
},
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let mut is_whitespace = self.key.boolean_bitor(&is_space, &is_tab);
|
||||
self.key
|
||||
.boolean_bitor_assign(&mut is_whitespace, &is_new_line);
|
||||
self.key
|
||||
.boolean_bitor_assign(&mut is_whitespace, &is_form_feed);
|
||||
self.key
|
||||
.boolean_bitor_assign(&mut is_whitespace, &is_carriage_return);
|
||||
|
||||
if let Some(is_null) = op_is_null {
|
||||
self.key.boolean_bitor_assign(&mut is_whitespace, &is_null);
|
||||
}
|
||||
|
||||
is_whitespace
|
||||
}
|
||||
|
||||
fn is_not_whitespace(&self, char: &FheAsciiChar) -> BooleanBlock {
|
||||
let result = self.is_whitespace(char, false);
|
||||
|
||||
self.key.boolean_bitnot(&result)
|
||||
}
|
||||
|
||||
fn compare_and_trim<'a, I>(&self, strip_str: I, starts_with_null: bool)
|
||||
where
|
||||
I: Iterator<Item = &'a mut FheAsciiChar>,
|
||||
{
|
||||
let mut prev_was_ws = self.key.create_trivial_boolean_block(true);
|
||||
for char in strip_str {
|
||||
let mut is_whitespace = self.is_whitespace(char, starts_with_null);
|
||||
self.key
|
||||
.boolean_bitand_assign(&mut is_whitespace, &prev_was_ws);
|
||||
|
||||
*char.ciphertext_mut() = self.key.if_then_else_parallelized(
|
||||
&is_whitespace,
|
||||
&self.key.create_trivial_zero_radix(4),
|
||||
char.ciphertext(),
|
||||
);
|
||||
|
||||
// Once one char isn't (leading / trailing) whitespace, next ones won't be either
|
||||
prev_was_ws = is_whitespace;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with whitespace removed from the start.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = " hello world";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// let result = sk.trim_start(&enc_s);
|
||||
/// let trimmed = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(trimmed, "hello world"); // Whitespace at the start is removed
|
||||
/// ```
|
||||
pub fn trim_start(&self, str: &FheString) -> FheString {
|
||||
let mut result = str.clone();
|
||||
|
||||
if str.is_empty() {
|
||||
return result;
|
||||
}
|
||||
|
||||
self.compare_and_trim(result.chars_mut().iter_mut(), false);
|
||||
|
||||
// Result has potential nulls in the leftmost chars, so we compute the length difference
|
||||
// before and after the trimming, and use that amount to shift the result left. This
|
||||
// makes the result nulls be at the end
|
||||
result.set_is_padded(true);
|
||||
if let FheStringLen::Padding(len_after_trim) = self.len(&result) {
|
||||
let original_str_len = match self.len(str) {
|
||||
FheStringLen::Padding(enc_val) => enc_val,
|
||||
FheStringLen::NoPadding(val) => self.key.create_trivial_radix(val as u32, 16),
|
||||
};
|
||||
|
||||
let shift_left = self
|
||||
.key
|
||||
.sub_parallelized(&original_str_len, &len_after_trim);
|
||||
|
||||
result = self.left_shift_chars(&result, &shift_left);
|
||||
}
|
||||
|
||||
// If str was not padded originally we don't know if result has nulls at the end or not (we
|
||||
// don't know if str was shifted or not) so we ensure it's padded in order to be
|
||||
// used in other functions safely
|
||||
if !str.is_padded() {
|
||||
result.append_null(self);
|
||||
} else {
|
||||
result.set_is_padded(true);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with whitespace removed from the end.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "hello world ";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// let result = sk.trim_end(&enc_s);
|
||||
/// let trimmed = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(trimmed, "hello world"); // Whitespace at the end is removed
|
||||
/// ```
|
||||
pub fn trim_end(&self, str: &FheString) -> FheString {
|
||||
let mut result = str.clone();
|
||||
|
||||
if str.is_empty() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If str is padded, when we check for whitespace from the left we have to ignore the nulls
|
||||
let include_null = str.is_padded();
|
||||
|
||||
self.compare_and_trim(result.chars_mut().iter_mut().rev(), include_null);
|
||||
|
||||
// If str was originally non-padded, the result is now potentially padded as we may have
|
||||
// made the last chars null, so we ensure it's padded in order to be used as input
|
||||
// to other functions safely
|
||||
if !str.is_padded() {
|
||||
result.append_null(self);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a new encrypted string with whitespace removed from both the start and end.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::gen_keys;
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = " hello world ";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// let result = sk.trim(&enc_s);
|
||||
/// let trimmed = ck.decrypt_ascii(&result);
|
||||
///
|
||||
/// assert_eq!(trimmed, "hello world"); // Whitespace at both ends is removed
|
||||
/// ```
|
||||
pub fn trim(&self, str: &FheString) -> FheString {
|
||||
if str.is_empty() {
|
||||
return str.clone();
|
||||
}
|
||||
|
||||
let result = self.trim_start(str);
|
||||
self.trim_end(&result)
|
||||
}
|
||||
|
||||
/// Creates an iterator over the substrings of this encrypted string, separated by any amount of
|
||||
/// whitespace.
|
||||
///
|
||||
/// Each call to `next` on the iterator returns a tuple with the next encrypted substring and a
|
||||
/// boolean indicating `Some` (true) or `None` (false) when no more substrings are available.
|
||||
///
|
||||
/// When the boolean is `true`, the iterator will yield non-empty encrypted substrings. When the
|
||||
/// boolean is `false`, the returned encrypted string is always empty.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::ciphertext::FheString;
|
||||
/// use crate::server_key::{gen_keys, FheStringIterator};
|
||||
///
|
||||
/// let (ck, sk) = gen_keys();
|
||||
/// let s = "hello \t\nworld ";
|
||||
///
|
||||
/// let enc_s = FheString::new(&ck, &s, None);
|
||||
///
|
||||
/// let mut whitespace_iter = sk.split_ascii_whitespace(&enc_s);
|
||||
/// let (first_item, first_is_some) = whitespace_iter.next(&sk);
|
||||
/// let (second_item, second_is_some) = whitespace_iter.next(&sk);
|
||||
/// let (empty, no_more_items) = whitespace_iter.next(&sk); // Attempting to get a third item
|
||||
///
|
||||
/// let first_decrypted = ck.decrypt_ascii(&first_item);
|
||||
/// let first_is_some = ck.key().decrypt_bool(&first_is_some);
|
||||
/// let second_decrypted = ck.decrypt_ascii(&second_item);
|
||||
/// let second_is_some = ck.key().decrypt_bool(&second_is_some);
|
||||
/// let empty = ck.decrypt_ascii(&empty);
|
||||
/// let no_more_items = ck.key().decrypt_bool(&no_more_items);
|
||||
///
|
||||
/// assert_eq!(first_decrypted, "hello");
|
||||
/// assert!(first_is_some);
|
||||
/// assert_eq!(second_decrypted, "world");
|
||||
/// assert!(second_is_some);
|
||||
/// assert_eq!(empty, ""); // There are no more items so we get an empty string
|
||||
/// assert!(!no_more_items);
|
||||
/// ```
|
||||
pub fn split_ascii_whitespace(&self, str: &FheString) -> SplitAsciiWhitespace {
|
||||
let result = str.clone();
|
||||
|
||||
SplitAsciiWhitespace {
|
||||
state: result,
|
||||
current_mask: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user