From c8cf7e472724a7b36a919bb9d235d8ff2ea842c1 Mon Sep 17 00:00:00 2001 From: tmontaigu Date: Mon, 6 Jan 2025 11:35:11 +0100 Subject: [PATCH] feat(hlapi): add strings --- .../backward_compatibility/mod.rs | 2 + .../backward_compatibility/strings.rs | 7 + .../integers/unsigned/tests/cpu.rs | 21 +- tfhe/src/high_level_api/keys/server.rs | 5 + tfhe/src/high_level_api/mod.rs | 8 +- tfhe/src/high_level_api/prelude.rs | 3 + tfhe/src/high_level_api/strings/ascii/comp.rs | 260 +++++++++++++ .../high_level_api/strings/ascii/contains.rs | 166 ++++++++ tfhe/src/high_level_api/strings/ascii/find.rs | 178 +++++++++ tfhe/src/high_level_api/strings/ascii/mod.rs | 277 ++++++++++++++ .../strings/ascii/no_pattern.rs | 356 ++++++++++++++++++ .../high_level_api/strings/ascii/replace.rs | 208 ++++++++++ .../src/high_level_api/strings/ascii/strip.rs | 194 ++++++++++ tfhe/src/high_level_api/strings/ascii/trim.rs | 101 +++++ tfhe/src/high_level_api/strings/mod.rs | 4 + tfhe/src/high_level_api/strings/tests/cpu.rs | 37 ++ tfhe/src/high_level_api/strings/tests/mod.rs | 182 +++++++++ tfhe/src/high_level_api/strings/traits.rs | 45 +++ tfhe/src/high_level_api/tests/mod.rs | 21 ++ .../src/strings/backward_compatibility/mod.rs | 12 + tfhe/src/strings/ciphertext.rs | 21 +- tfhe/src/strings/client_key.rs | 3 + tfhe/src/strings/mod.rs | 3 +- tfhe/src/strings/server_key/comp.rs | 46 ++- tfhe/src/strings/server_key/mod.rs | 2 + tfhe/src/strings/server_key/no_patterns.rs | 49 +-- 26 files changed, 2139 insertions(+), 72 deletions(-) create mode 100644 tfhe/src/high_level_api/backward_compatibility/strings.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/comp.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/contains.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/find.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/mod.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/no_pattern.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/replace.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/strip.rs create mode 100644 tfhe/src/high_level_api/strings/ascii/trim.rs create mode 100644 tfhe/src/high_level_api/strings/mod.rs create mode 100644 tfhe/src/high_level_api/strings/tests/cpu.rs create mode 100644 tfhe/src/high_level_api/strings/tests/mod.rs create mode 100644 tfhe/src/high_level_api/strings/traits.rs create mode 100644 tfhe/src/strings/backward_compatibility/mod.rs diff --git a/tfhe/src/high_level_api/backward_compatibility/mod.rs b/tfhe/src/high_level_api/backward_compatibility/mod.rs index 1907dfabf7..e12411d526 100644 --- a/tfhe/src/high_level_api/backward_compatibility/mod.rs +++ b/tfhe/src/high_level_api/backward_compatibility/mod.rs @@ -6,4 +6,6 @@ pub mod compressed_ciphertext_list; pub mod config; pub mod integers; pub mod keys; +#[cfg(feature = "strings")] +pub mod strings; pub mod tag; diff --git a/tfhe/src/high_level_api/backward_compatibility/strings.rs b/tfhe/src/high_level_api/backward_compatibility/strings.rs new file mode 100644 index 0000000000..b7935698cc --- /dev/null +++ b/tfhe/src/high_level_api/backward_compatibility/strings.rs @@ -0,0 +1,7 @@ +use crate::FheAsciiString; +use tfhe_versionable::VersionsDispatch; + +#[derive(VersionsDispatch)] +pub enum FheAsciiStringVersions { + V0(FheAsciiString), +} diff --git a/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs b/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs index 9d0a7853bb..adda10ae00 100644 --- a/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs +++ b/tfhe/src/high_level_api/integers/unsigned/tests/cpu.rs @@ -1,5 +1,6 @@ use crate::conformance::ListSizeConstraint; use crate::high_level_api::prelude::*; +use crate::high_level_api::tests::{setup_cpu, setup_default_cpu}; use crate::high_level_api::{generate_keys, set_server_key, ConfigBuilder, FheUint8}; use crate::integer::U256; use crate::safe_serialization::{DeserializationConfig, SerializationConfig}; @@ -15,26 +16,6 @@ use crate::{ }; use rand::prelude::*; -fn setup_cpu(params: Option>) -> ClientKey { - let config = params - .map_or_else(ConfigBuilder::default, |p| { - ConfigBuilder::with_custom_parameters(p.into()) - }) - .build(); - - let client_key = ClientKey::generate(config); - let csks = crate::CompressedServerKey::new(&client_key); - let server_key = csks.decompress(); - - set_server_key(server_key); - - client_key -} - -fn setup_default_cpu() -> ClientKey { - setup_cpu(Option::::None) -} - #[test] fn test_integer_compressed_can_be_serialized() { let config = ConfigBuilder::default().build(); diff --git a/tfhe/src/high_level_api/keys/server.rs b/tfhe/src/high_level_api/keys/server.rs index 9ecb81109a..e360482ced 100644 --- a/tfhe/src/high_level_api/keys/server.rs +++ b/tfhe/src/high_level_api/keys/server.rs @@ -93,6 +93,11 @@ impl ServerKey { self.key.pbs_key() } + #[cfg(feature = "strings")] + pub(in crate::high_level_api) fn string_key(&self) -> crate::strings::ServerKeyRef<'_> { + crate::strings::ServerKeyRef::new(self.key.pbs_key()) + } + pub(in crate::high_level_api) fn cpk_casting_key( &self, ) -> Option { diff --git a/tfhe/src/high_level_api/mod.rs b/tfhe/src/high_level_api/mod.rs index 0b58aba1db..99d925b1fd 100644 --- a/tfhe/src/high_level_api/mod.rs +++ b/tfhe/src/high_level_api/mod.rs @@ -87,6 +87,9 @@ export_concrete_array_types!( pub use crate::integer::parameters::CompactCiphertextListConformanceParams; pub use crate::safe_serialization::{DeserializationConfig, SerializationConfig}; +#[cfg(feature = "strings")] +pub use crate::strings::ciphertext::ClearString; + #[cfg(feature = "zk-pok")] pub use compact_list::ProvenCompactCiphertextList; pub use compact_list::{ @@ -95,7 +98,8 @@ pub use compact_list::{ pub use compressed_ciphertext_list::{ CompressedCiphertextList, CompressedCiphertextListBuilder, HlCompressible, HlExpandable, }; - +#[cfg(feature = "strings")] +pub use strings::ascii::{EncryptableString, FheAsciiString, FheStringIsEmpty, FheStringLen}; pub use tag::Tag; pub use traits::FheId; @@ -106,6 +110,8 @@ mod errors; mod global_state; mod integers; mod keys; +#[cfg(feature = "strings")] +mod strings; mod traits; mod utils; diff --git a/tfhe/src/high_level_api/prelude.rs b/tfhe/src/high_level_api/prelude.rs index 0e22700b95..4f97c39543 100644 --- a/tfhe/src/high_level_api/prelude.rs +++ b/tfhe/src/high_level_api/prelude.rs @@ -15,3 +15,6 @@ pub use crate::high_level_api::traits::{ pub use crate::conformance::ParameterSetConformant; pub use crate::core_crypto::prelude::{CastFrom, CastInto}; + +#[cfg(feature = "strings")] +pub use crate::high_level_api::strings::traits::*; diff --git a/tfhe/src/high_level_api/strings/ascii/comp.rs b/tfhe/src/high_level_api/strings/ascii/comp.rs new file mode 100644 index 0000000000..3fdf8e8ab8 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/comp.rs @@ -0,0 +1,260 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; +use crate::prelude::{FheEq, FheEqIgnoreCase, FheOrd}; +use crate::strings::ciphertext::ClearString; +use crate::FheBool; + +impl FheEq<&Self> for FheAsciiString { + fn eq(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .eq(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings eq"); + } + }) + } + + fn ne(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .ne(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings ne"); + } + }) + } +} + +impl FheEq<&ClearString> for FheAsciiString { + fn eq(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().eq(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings eq"); + } + }) + } + + fn ne(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().ne(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings ne"); + } + }) + } +} + +impl FheOrd<&Self> for FheAsciiString { + fn lt(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .lt(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings lt"); + } + }) + } + + fn le(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .le(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings le"); + } + }) + } + + fn gt(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .gt(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings gt"); + } + }) + } + + fn ge(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .ge(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings ge"); + } + }) + } +} + +impl FheOrd<&ClearString> for FheAsciiString { + fn lt(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().lt(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings lt"); + } + }) + } + + fn le(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().le(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings le"); + } + }) + } + + fn gt(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().gt(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings gt"); + } + }) + } + + fn ge(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().ge(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings ge"); + } + }) + } +} + +impl FheEqIgnoreCase for FheAsciiString { + /// checks if the strings are equal, ignoring the case + /// + /// Returns a [FheBool] that encrypts `true` if the substring was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string1 = FheAsciiString::try_encrypt("tfhe-RS", &client_key).unwrap(); + /// let string2 = FheAsciiString::try_encrypt("TFHE-rs", &client_key).unwrap(); + /// let is_eq = string1.eq_ignore_case(&string2); + /// + /// assert!(is_eq.decrypt(&client_key)); + /// ``` + fn eq_ignore_case(&self, rhs: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .eq_ignore_case(&self.inner.on_cpu(), (&*rhs.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings eq_ignore_case"); + } + }) + } +} + +impl FheEqIgnoreCase for FheAsciiString { + /// checks if the strings are equal, ignoring the case + /// + /// Returns a [FheBool] that encrypts `true` if the substring was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string1 = FheAsciiString::try_encrypt("tfhe-RS", &client_key).unwrap(); + /// let string2 = ClearString::new("TFHE-rs".into()); + /// let is_eq = string1.eq_ignore_case(&string2); + /// + /// assert!(is_eq.decrypt(&client_key)); + /// ``` + fn eq_ignore_case(&self, rhs: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .eq_ignore_case(&self.inner.on_cpu(), rhs.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings eq_ignore_case"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/contains.rs b/tfhe/src/high_level_api/strings/ascii/contains.rs new file mode 100644 index 0000000000..02870865db --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/contains.rs @@ -0,0 +1,166 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; +use crate::high_level_api::strings::traits::FheStringMatching; +use crate::strings::ciphertext::ClearString; +use crate::FheBool; + +impl FheStringMatching<&Self> for FheAsciiString { + /// checks if the string contains the given substring + /// + /// Returns a [FheBool] that encrypts `true` if the substring was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("fhe", &client_key).unwrap(); + /// let found = string.contains(&pattern); + /// + /// assert!(found.decrypt(&client_key)); + /// ``` + fn contains(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .contains(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings contains"); + } + }) + } + + /// checks if the string starts with the given substring + /// + /// Returns a [FheBool] that encrypts `true` if the substring was found at the beginning. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("fhe", &client_key).unwrap(); + /// let found = string.starts_with(&pattern); + /// + /// assert!(!found.decrypt(&client_key)); + /// ``` + fn starts_with(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .starts_with(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings starts_with"); + } + }) + } + + /// checks if the string ends with the given substring + /// + /// Returns a [FheBool] that encrypts `true` if the substring was found at the end. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("scheme", &client_key).unwrap(); + /// let found = string.ends_with(&pattern); + /// + /// assert!(found.decrypt(&client_key)); + /// ``` + fn ends_with(&self, other: &Self) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .ends_with(&self.inner.on_cpu(), (&*other.inner.on_cpu()).into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings ends_with"); + } + }) + } +} + +impl FheStringMatching<&ClearString> for FheAsciiString { + fn contains(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .contains(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings contains"); + } + }) + } + + fn starts_with(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .starts_with(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings starts_with"); + } + }) + } + + fn ends_with(&self, other: &ClearString) -> FheBool { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .ends_with(&self.inner.on_cpu(), other.into()); + FheBool::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings ends_with"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/find.rs b/tfhe/src/high_level_api/strings/ascii/find.rs new file mode 100644 index 0000000000..adf425b6b6 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/find.rs @@ -0,0 +1,178 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; +use crate::high_level_api::strings::traits::FheStringFind; +use crate::strings::ciphertext::ClearString; +use crate::{FheBool, FheUint32}; + +impl FheStringFind<&Self> for FheAsciiString { + /// find a substring inside a string + /// + /// Returns the index of the first character of this string that matches the pattern + /// as well as a [FheBool] that encrypts `true` if the pattern was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("fhe", &client_key).unwrap(); + /// let (position, found) = string.find(&pattern); + /// + /// assert!(found.decrypt(&client_key)); + /// let pos: u32 = position.decrypt(&client_key); + /// assert_eq!(pos, 1); + /// ``` + fn find(&self, pat: &Self) -> (FheUint32, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key + .string_key() + .find(&self.inner.on_cpu(), (&*pat.inner.on_cpu()).into()); + ( + FheUint32::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings find"); + } + }) + } + + /// find a substring inside a string + /// + /// Returns the index for the first character of the last match of the pattern in this string, + /// as well as a [FheBool] that encrypts `true` if the pattern was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("fhe", &client_key).unwrap(); + /// let (position, found) = string.rfind(&pattern); + /// + /// assert!(found.decrypt(&client_key)); + /// let pos: u32 = position.decrypt(&client_key); + /// assert_eq!(pos, 11); + /// ``` + fn rfind(&self, pat: &Self) -> (FheUint32, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key + .string_key() + .rfind(&self.inner.on_cpu(), (&*pat.inner.on_cpu()).into()); + ( + FheUint32::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings rfind"); + } + }) + } +} + +impl FheStringFind<&ClearString> for FheAsciiString { + /// find a substring inside a string + /// + /// Returns the index of the first character of this string that matches the pattern + /// as well as a [FheBool] that encrypts `true` if the pattern was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = ClearString::new("fhe".into()); + /// let (position, found) = string.find(&pattern); + /// + /// assert!(found.decrypt(&client_key)); + /// let pos: u32 = position.decrypt(&client_key); + /// assert_eq!(pos, 1); + /// ``` + fn find(&self, pat: &ClearString) -> (FheUint32, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key.string_key().find(&self.inner.on_cpu(), pat.into()); + ( + FheUint32::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings find"); + } + }) + } + + /// find a substring inside a string + /// + /// Returns the index for the first character of the last match of the pattern in this string, + /// as well as a [FheBool] that encrypts `true` if the pattern was found. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = ClearString::new("fhe".into()); + /// let (position, found) = string.rfind(&pattern); + /// + /// assert!(found.decrypt(&client_key)); + /// let pos: u32 = position.decrypt(&client_key); + /// assert_eq!(pos, 11); + /// ``` + fn rfind(&self, pat: &ClearString) -> (FheUint32, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key.string_key().rfind(&self.inner.on_cpu(), pat.into()); + ( + FheUint32::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings rfind"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/mod.rs b/tfhe/src/high_level_api/strings/ascii/mod.rs new file mode 100644 index 0000000000..51bdc13201 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/mod.rs @@ -0,0 +1,277 @@ +mod comp; +mod contains; +mod find; +mod no_pattern; +mod replace; +mod strip; +mod trim; + +pub use crate::high_level_api::backward_compatibility::strings::FheAsciiStringVersions; +use crate::high_level_api::details::MaybeCloned; +use crate::named::Named; +use crate::prelude::{FheDecrypt, FheTryEncrypt, Tagged}; +use crate::strings::ciphertext::FheString; +use crate::{ClientKey, Tag}; +pub use no_pattern::{FheStringIsEmpty, FheStringLen}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use tfhe_versionable::{Unversionize, UnversionizeError, Versionize, VersionizeOwned}; + +pub enum EncryptableString<'a> { + NoPadding(&'a str), + WithPadding { str: &'a str, padding: u32 }, +} + +impl EncryptableString<'_> { + fn str_and_padding(&self) -> (&str, Option) { + match self { + EncryptableString::NoPadding(str) => (str, None), + EncryptableString::WithPadding { str, padding } => (str, Some(*padding)), + } + } +} + +pub(crate) enum AsciiDevice { + Cpu(FheString), +} + +impl From for AsciiDevice { + fn from(value: FheString) -> Self { + Self::Cpu(value) + } +} + +impl AsciiDevice { + pub fn on_cpu(&self) -> MaybeCloned<'_, FheString> { + match self { + Self::Cpu(cpu_string) => MaybeCloned::Borrowed(cpu_string), + } + } +} + +impl Clone for AsciiDevice { + fn clone(&self) -> Self { + match self { + Self::Cpu(s) => Self::Cpu(s.clone()), + } + } +} + +impl serde::Serialize for AsciiDevice { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.on_cpu().serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for AsciiDevice { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let deserialized = Self::Cpu(FheString::deserialize(deserializer)?); + Ok(deserialized) + } +} + +// Only CPU data are serialized so we only versionize the CPU type. +#[derive(serde::Serialize, serde::Deserialize)] +#[cfg_attr(tfhe_lints, allow(tfhe_lints::serialize_without_versionize))] +pub(crate) struct AsciiDeviceVersionOwned(::VersionedOwned); + +#[derive(Serialize, Deserialize)] +#[cfg_attr(tfhe_lints, allow(tfhe_lints::serialize_without_versionize))] +pub(crate) enum AsciiDeviceVersionedOwned { + V0(AsciiDeviceVersionOwned), +} + +impl Versionize for AsciiDevice { + type Versioned<'vers> = AsciiDeviceVersionedOwned; + + fn versionize(&self) -> Self::Versioned<'_> { + let data = self.on_cpu(); + let versioned = data.into_owned().versionize_owned(); + AsciiDeviceVersionedOwned::V0(AsciiDeviceVersionOwned(versioned)) + } +} + +impl VersionizeOwned for AsciiDevice { + type VersionedOwned = AsciiDeviceVersionedOwned; + + fn versionize_owned(self) -> Self::VersionedOwned { + let cpu_data = self.on_cpu(); + AsciiDeviceVersionedOwned::V0(AsciiDeviceVersionOwned( + cpu_data.into_owned().versionize_owned(), + )) + } +} + +impl Unversionize for AsciiDevice { + fn unversionize(versioned: Self::VersionedOwned) -> Result { + match versioned { + AsciiDeviceVersionedOwned::V0(v0) => { + let unversioned = Self::Cpu(FheString::unversionize(v0.0)?); + Ok(unversioned) + } + } + } +} + +#[derive(Serialize, Deserialize, Versionize, Clone)] +#[versionize(FheAsciiStringVersions)] +pub struct FheAsciiString { + pub(crate) inner: AsciiDevice, + pub(crate) tag: Tag, +} + +impl Named for FheAsciiString { + const NAME: &'static str = "high_level_api::FheAsciiString"; +} + +impl Tagged for FheAsciiString { + fn tag(&self) -> &Tag { + &self.tag + } + + fn tag_mut(&mut self) -> &mut Tag { + &mut self.tag + } +} + +impl FheAsciiString { + pub(crate) fn new(inner: impl Into, tag: Tag) -> Self { + Self { + inner: inner.into(), + tag, + } + } + + /// Encrypts the string `str` and adds `padding` blocks of padding (encryption of zero) + pub fn try_encrypt_with_padding( + str: impl AsRef, + padding: u32, + client_key: &ClientKey, + ) -> crate::Result { + Self::try_encrypt( + EncryptableString::WithPadding { + str: str.as_ref(), + padding, + }, + client_key, + ) + } + + /// Encrypts the string `str` with a fixed size `size` + /// + /// * If the input str is shorter than size, it will be padded with encryptions of 0 + /// * If the input str is longer than size, it will be truncated + /// + /// # Example + /// + /// ``` + /// use tfhe::prelude::*; + /// use tfhe::safe_serialization::safe_serialize; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// // The input string is shorter + /// let string1 = FheAsciiString::try_encrypt_with_fixed_sized("tfhe", 5, &client_key).unwrap(); + /// match string1.len() { + /// FheStringLen::NoPadding(_) => { + /// panic!("Expected padding") + /// } + /// FheStringLen::Padding(len) => { + /// let len: u16 = len.decrypt(&client_key); + /// // The padding is not part of the len + /// assert_eq!(len, 4); + /// } + /// } + /// assert_eq!(string1.decrypt(&client_key), "tfhe".to_string()); + /// + /// // The input string is longer + /// let string2 = FheAsciiString::try_encrypt_with_fixed_sized("tfhe-rs", 5, &client_key).unwrap(); + /// match string2.len() { + /// FheStringLen::NoPadding(len) => { + /// assert_eq!(len, 5); + /// } + /// FheStringLen::Padding(len) => { + /// panic!("Unexpected padding"); + /// } + /// } + /// assert_eq!(string2.decrypt(&client_key), "tfhe-".to_string()); + /// + /// let mut buffer1 = vec![]; + /// safe_serialize(&string1, &mut buffer1, 1 << 30).unwrap(); + /// let mut buffer2 = vec![]; + /// safe_serialize(&string2, &mut buffer2, 1 << 30).unwrap(); + /// // But they have the same 'size' + /// assert_eq!(buffer1.len(), buffer2.len()) + /// ``` + pub fn try_encrypt_with_fixed_sized( + str: impl AsRef, + size: usize, + client_key: &ClientKey, + ) -> crate::Result { + let str = str.as_ref(); + let (sliced, padding) = if str.len() >= size { + (&str[..size], 0) + } else { + (str, (size - str.len()) as u32) + }; + + Self::try_encrypt( + EncryptableString::WithPadding { + str: sliced, + padding, + }, + client_key, + ) + } +} + +impl<'a> FheTryEncrypt, ClientKey> for FheAsciiString { + type Error = crate::Error; + + fn try_encrypt(value: EncryptableString<'a>, key: &ClientKey) -> Result { + let (str, padding) = value.str_and_padding(); + if !str.is_ascii() || str.contains('\0') { + return Err(crate::Error::new( + "Input is not an ASCII string".to_string(), + )); + } + + let inner = crate::strings::ClientKey::new(&key.key.key).encrypt_ascii(str, padding); + Ok(Self { + inner: inner.into(), + tag: key.tag.clone(), + }) + } +} + +impl FheTryEncrypt<&str, ClientKey> for FheAsciiString { + type Error = crate::Error; + + fn try_encrypt(value: &str, key: &ClientKey) -> Result { + Self::try_encrypt(EncryptableString::NoPadding(value), key) + } +} + +impl FheTryEncrypt<&String, ClientKey> for FheAsciiString { + type Error = crate::Error; + + fn try_encrypt(value: &String, key: &ClientKey) -> Result { + Self::try_encrypt(EncryptableString::NoPadding(value), key) + } +} + +impl FheDecrypt for FheAsciiString { + fn decrypt(&self, key: &ClientKey) -> String { + crate::strings::ClientKey::new(&key.key.key).decrypt_ascii(&self.inner.on_cpu()) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/no_pattern.rs b/tfhe/src/high_level_api/strings/ascii/no_pattern.rs new file mode 100644 index 0000000000..e898a24652 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/no_pattern.rs @@ -0,0 +1,356 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::integers::FheUint16; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; +use crate::high_level_api::traits::FheTrivialEncrypt; +use crate::prelude::FheStringRepeat; +use crate::strings::ciphertext::UIntArg; +use crate::strings::client_key::EncU16; +use crate::{FheBool, Tag}; + +pub enum FheStringLen { + NoPadding(u16), + Padding(FheUint16), +} + +impl FheStringLen { + /// Transforms self into a ciphertext if it is not already. + /// + /// If self contains a clear value, the ciphertext is a trivial encryption + pub fn into_ciphertext(self) -> FheUint16 { + match self { + Self::NoPadding(clear_len) => FheUint16::encrypt_trivial(clear_len), + Self::Padding(len) => len, + } + } +} + +impl From for FheStringLen { + fn from(value: crate::strings::server_key::FheStringLen) -> Self { + match value { + crate::strings::server_key::FheStringLen::NoPadding(v) => Self::NoPadding(v as u16), + crate::strings::server_key::FheStringLen::Padding(v) => { + Self::Padding(FheUint16::new(v, Tag::default())) + } + } + } +} + +pub enum FheStringIsEmpty { + NoPadding(bool), + Padding(FheBool), +} + +impl FheStringIsEmpty { + /// Transforms self into a ciphertext if it is not already. + /// + /// If self contains a clear value, the ciphertext is a trivial encryption + pub fn into_ciphertext(self) -> FheBool { + match self { + Self::NoPadding(clear_r) => FheBool::encrypt_trivial(clear_r), + Self::Padding(r) => r, + } + } +} + +impl From for FheStringIsEmpty { + fn from(value: crate::strings::server_key::FheStringIsEmpty) -> Self { + match value { + crate::strings::server_key::FheStringIsEmpty::NoPadding(v) => Self::NoPadding(v), + crate::strings::server_key::FheStringIsEmpty::Padding(bool_block) => { + Self::Padding(FheBool::new(bool_block, Tag::default())) + } + } + } +} + +impl FheAsciiString { + /// 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 encrypted. + /// + /// ``` + /// use tfhe::prelude::*; + /// use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringLen}; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe-rs", &client_key).unwrap(); + /// match string.len() { + /// FheStringLen::NoPadding(length) => assert_eq!(length, 7), + /// FheStringLen::Padding(_) => panic!("Unexpected padding"), + /// } + /// + /// let string = FheAsciiString::try_encrypt_with_padding("tfhe-rs", 5, &client_key).unwrap(); + /// match string.len() { + /// FheStringLen::NoPadding(_) => panic!("Unexpected no padding"), + /// FheStringLen::Padding(enc_len) => { + /// let len: u16 = enc_len.decrypt(&client_key); + /// assert_eq!(len, 7); + /// } + /// } + /// ``` + pub fn len(&self) -> FheStringLen { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let mut len = cpu_key.string_key().len(&self.inner.on_cpu()).into(); + if let FheStringLen::Padding(len) = &mut len { + len.tag = cpu_key.tag.clone(); + } + len + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings 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 [FheBool] + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty}; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("", &client_key).unwrap(); + /// match string.is_empty() { + /// FheStringIsEmpty::NoPadding(is_empty) => assert!(is_empty), + /// FheStringIsEmpty::Padding(_) => panic!("Unexpected padding"), + /// } + /// + /// let string = FheAsciiString::try_encrypt_with_padding("", 5, &client_key).unwrap(); + /// match string.is_empty() { + /// FheStringIsEmpty::NoPadding(_) => panic!("Unexpected no padding"), + /// FheStringIsEmpty::Padding(enc_is_empty) => { + /// let is_empty: bool = enc_is_empty.decrypt(&client_key); + /// assert!(is_empty); + /// } + /// } + /// ``` + pub fn is_empty(&self) -> FheStringIsEmpty { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let mut result = cpu_key.string_key().is_empty(&self.inner.on_cpu()).into(); + if let FheStringIsEmpty::Padding(r) = &mut result { + r.tag = cpu_key.tag.clone(); + } + result + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings len"); + } + }) + } + + /// Returns a new encrypted string with all characters converted to lowercase. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty}; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("TfHe-RS", &client_key).unwrap(); + /// let lower = string.to_lowercase(); + /// + /// let dec = lower.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe-rs"); + /// ``` + pub fn to_lowercase(&self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().to_lowercase(&self.inner.on_cpu()); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings to_lowercase"); + } + }) + } + + /// Returns a new encrypted string with all characters converted to uppercase. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty}; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("TfHe-RS", &client_key).unwrap(); + /// let upper = string.to_uppercase(); + /// + /// let dec = upper.decrypt(&client_key); + /// assert_eq!(&dec, "TFHE-RS"); + /// ``` + pub fn to_uppercase(&self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().to_uppercase(&self.inner.on_cpu()); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings to_uppercase"); + } + }) + } + + /// Concatenates two encrypted strings and returns the result as a new encrypted string. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string1 = FheAsciiString::try_encrypt("tfhe", &client_key).unwrap(); + /// let string2 = FheAsciiString::try_encrypt("-rs", &client_key).unwrap(); + /// let string = string1.concat(&string2); + /// + /// let dec = string.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe-rs"); + /// ``` + pub fn concat(&self, other: &Self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .concat(&self.inner.on_cpu(), &other.inner.on_cpu()); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings concatenating"); + } + }) + } +} + +// Overload for u32 as it's a common type +impl FheStringRepeat for FheAsciiString { + fn repeat(&self, count: u32) -> Self { + self.repeat(count as u16) + } +} + +// Overload for usize as it's a common type +impl FheStringRepeat for FheAsciiString { + fn repeat(&self, count: usize) -> Self { + self.repeat(count as u16) + } +} + +// Overload for usize as it's a common type (literals are i32 by default) +impl FheStringRepeat for FheAsciiString { + fn repeat(&self, count: i32) -> Self { + self.repeat(count as u16) + } +} + +impl FheStringRepeat for FheAsciiString { + /// Repeats the string by the given clear count. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe ", &client_key).unwrap(); + /// let repeated = string.repeat(3); + /// + /// let dec = repeated.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe tfhe tfhe "); + /// ``` + fn repeat(&self, count: u16) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key + .string_key() + .repeat(&self.inner.on_cpu(), &UIntArg::Clear(count)); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings repeat"); + } + }) + } +} + +impl FheStringRepeat<(FheUint16, u16)> for FheAsciiString { + /// Repeats the string by the given encrypted amount. + /// + /// The count amount is a tuple containing the encrypted value + /// as well as an upper bound for the count + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, FheUint16, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let max = 4; + /// let clear_amount = rand::random::() % max; + /// let clear_string = "tfhe "; + /// + /// let string = FheAsciiString::try_encrypt(clear_string, &client_key).unwrap(); + /// let amount = FheUint16::encrypt(clear_amount, &client_key); + /// let repeated = string.repeat((amount, max)); + /// + /// let expected = clear_string.repeat(clear_amount as usize); + /// let dec = repeated.decrypt(&client_key); + /// assert_eq!(&dec, &expected); + /// ``` + fn repeat(&self, (count, bound): (FheUint16, u16)) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().repeat( + &self.inner.on_cpu(), + &UIntArg::Enc(EncU16::new(count.ciphertext.into_cpu(), Some(bound))), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings repeat"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/replace.rs b/tfhe/src/high_level_api/strings/ascii/replace.rs new file mode 100644 index 0000000000..acbabc9ad1 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/replace.rs @@ -0,0 +1,208 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; +use crate::high_level_api::strings::traits::FheStringReplace; +use crate::prelude::FheStringReplaceN; +use crate::strings::ciphertext::{ClearString, UIntArg}; +use crate::strings::client_key::EncU16; +use crate::FheUint16; + +impl FheStringReplace<&Self> for FheAsciiString { + /// Returns a new encrypted string with all non-overlapping occurrences of a pattern + /// replaced by another specified encrypted pattern. + /// + /// # Example + /// + /// ```rust,no_run + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("fhe", &client_key).unwrap(); + /// let new_val = FheAsciiString::try_encrypt("cookie", &client_key).unwrap(); + /// let replaced = string.replace(&pattern, &new_val); + /// + /// let dec = replaced.decrypt(&client_key); + /// assert_eq!(&dec, "tcookie is an cookie scheme"); + /// ``` + fn replace(&self, from: &Self, to: &Self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().replace( + &self.inner.on_cpu(), + (&*from.inner.on_cpu()).into(), + &to.inner.on_cpu(), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings replace"); + } + }) + } +} + +impl FheStringReplace<&ClearString> for FheAsciiString { + /// Returns a new encrypted string with all non-overlapping occurrences of a pattern + /// replaced by another specified encrypted pattern. + /// + /// # Example + /// + /// ```rust,no_run + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe is an fhe scheme", &client_key).unwrap(); + /// let pattern = ClearString::new("fhe".into()); + /// let new_val = FheAsciiString::try_encrypt("cookie", &client_key).unwrap(); + /// let replaced = string.replace(&pattern, &new_val); + /// + /// let dec = replaced.decrypt(&client_key); + /// assert_eq!(&dec, "tcookie is an cookie scheme"); + /// ``` + fn replace(&self, from: &ClearString, to: &Self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().replace( + &self.inner.on_cpu(), + from.into(), + &to.inner.on_cpu(), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings replace"); + } + }) + } +} + +impl FheStringReplaceN<&Self, i32> for FheAsciiString { + fn replacen(&self, from: &Self, to: &Self, count: i32) -> Self { + self.replacen(from, to, count as u16) + } +} + +impl FheStringReplaceN<&Self, usize> for FheAsciiString { + fn replacen(&self, from: &Self, to: &Self, count: usize) -> Self { + self.replacen(from, to, count as u16) + } +} + +impl FheStringReplaceN<&Self, u32> for FheAsciiString { + fn replacen(&self, from: &Self, to: &Self, count: u32) -> Self { + self.replacen(from, to, count as u16) + } +} + +impl FheStringReplaceN<&Self, u16> for FheAsciiString { + fn replacen(&self, from: &Self, to: &Self, count: u16) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().replacen( + &self.inner.on_cpu(), + (&*from.inner.on_cpu()).into(), + &to.inner.on_cpu(), + &UIntArg::Clear(count), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings replacen"); + } + }) + } +} + +impl FheStringReplaceN<&Self, (FheUint16, u16)> for FheAsciiString { + fn replacen(&self, from: &Self, to: &Self, (count, max): (FheUint16, u16)) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().replacen( + &self.inner.on_cpu(), + (&*from.inner.on_cpu()).into(), + &to.inner.on_cpu(), + &UIntArg::Enc(EncU16::new(count.ciphertext.into_cpu(), Some(max))), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings replacen"); + } + }) + } +} + +impl FheStringReplaceN<&ClearString, i32> for FheAsciiString { + fn replacen(&self, from: &ClearString, to: &Self, count: i32) -> Self { + self.replacen(from, to, count as u16) + } +} + +impl FheStringReplaceN<&ClearString, usize> for FheAsciiString { + fn replacen(&self, from: &ClearString, to: &Self, count: usize) -> Self { + self.replacen(from, to, count as u16) + } +} + +impl FheStringReplaceN<&ClearString, u32> for FheAsciiString { + fn replacen(&self, from: &ClearString, to: &Self, count: u32) -> Self { + self.replacen(from, to, count as u16) + } +} + +impl FheStringReplaceN<&ClearString, u16> for FheAsciiString { + fn replacen(&self, from: &ClearString, to: &Self, count: u16) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().replacen( + &self.inner.on_cpu(), + from.into(), + &to.inner.on_cpu(), + &UIntArg::Clear(count), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings replacen"); + } + }) + } +} + +impl FheStringReplaceN<&ClearString, (FheUint16, u16)> for FheAsciiString { + fn replacen(&self, from: &ClearString, to: &Self, (count, max): (FheUint16, u16)) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().replacen( + &self.inner.on_cpu(), + from.into(), + &to.inner.on_cpu(), + &UIntArg::Enc(EncU16::new(count.ciphertext.into_cpu(), Some(max))), + ); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strings replacen"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/strip.rs b/tfhe/src/high_level_api/strings/ascii/strip.rs new file mode 100644 index 0000000000..53047f1235 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/strip.rs @@ -0,0 +1,194 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; +use crate::high_level_api::strings::traits::FheStringStrip; +use crate::high_level_api::FheBool; +use crate::strings::ciphertext::ClearString; + +impl FheStringStrip<&Self> for FheAsciiString { + /// If the pattern does match the start of the string, returns a new encrypted string + /// with the specified pattern stripped from the start, and boolean set to `true`, + /// indicating the equivalent of `Some(_)` + /// + /// 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`. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe-rs", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("tfhe", &client_key).unwrap(); + /// let (stripped, is_stripped) = string.strip_prefix(&pattern); + /// + /// assert!(is_stripped.decrypt(&client_key)); + /// + /// let dec = stripped.decrypt(&client_key); + /// assert_eq!(&dec, "-rs"); + /// ``` + fn strip_prefix<'a>(&self, pat: &Self) -> (Self, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key + .string_key() + .strip_prefix(&self.inner.on_cpu(), (&*pat.inner.on_cpu()).into()); + ( + Self::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strip_prefix"); + } + }) + } + + /// If the pattern does match the end of the string, returns a new encrypted string + /// with the specified pattern stripped from the end, and boolean set to `true`, + /// indicating the equivalent of `Some(_)` + /// + /// 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`. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe-rs", &client_key).unwrap(); + /// let pattern = FheAsciiString::try_encrypt("tfhe", &client_key).unwrap(); + /// let (stripped, is_stripped) = string.strip_suffix(&pattern); + /// + /// assert!(!is_stripped.decrypt(&client_key)); + /// + /// let dec = stripped.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe-rs"); + /// ``` + fn strip_suffix<'a>(&self, pat: &Self) -> (Self, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key + .string_key() + .strip_suffix(&self.inner.on_cpu(), (&*pat.inner.on_cpu()).into()); + ( + Self::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strip_suffix"); + } + }) + } +} + +impl FheStringStrip<&ClearString> for FheAsciiString { + /// If the pattern does match the start of the string, returns a new encrypted string + /// with the specified pattern stripped from the start, and boolean set to `true`, + /// indicating the equivalent of `Some(_)` + /// + /// 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`. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe-rs", &client_key).unwrap(); + /// let pattern = ClearString::new("tfhe".into()); + /// let (stripped, is_stripped) = string.strip_prefix(&pattern); + /// + /// assert!(is_stripped.decrypt(&client_key)); + /// + /// let dec = stripped.decrypt(&client_key); + /// assert_eq!(&dec, "-rs"); + /// ``` + fn strip_prefix<'a>(&self, pat: &ClearString) -> (Self, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key + .string_key() + .strip_prefix(&self.inner.on_cpu(), pat.into()); + ( + Self::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strip_prefix"); + } + }) + } + + /// If the pattern does match the end of the string, returns a new encrypted string + /// with the specified pattern stripped from the end, and boolean set to `true`, + /// indicating the equivalent of `Some(_)` + /// + /// 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`. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ClearString, ConfigBuilder, FheAsciiString, + /// FheStringIsEmpty, FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt("tfhe-rs", &client_key).unwrap(); + /// let pattern = ClearString::new("tfhe".into()); + /// let (stripped, is_stripped) = string.strip_suffix(&pattern); + /// + /// assert!(!is_stripped.decrypt(&client_key)); + /// + /// let dec = stripped.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe-rs"); + /// ``` + fn strip_suffix<'a>(&self, pat: &ClearString) -> (Self, FheBool) { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let (inner, block) = cpu_key + .string_key() + .strip_suffix(&self.inner.on_cpu(), pat.into()); + ( + Self::new(inner, cpu_key.tag.clone()), + FheBool::new(block, cpu_key.tag.clone()), + ) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support strip_suffix"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/ascii/trim.rs b/tfhe/src/high_level_api/strings/ascii/trim.rs new file mode 100644 index 0000000000..a0dd59b576 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/trim.rs @@ -0,0 +1,101 @@ +use crate::high_level_api::global_state::with_internal_keys; +use crate::high_level_api::keys::InternalServerKey; +use crate::high_level_api::strings::ascii::FheAsciiString; + +impl FheAsciiString { + /// Returns a new encrypted string with whitespace removed from the start. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt(" tfhe-rs ", &client_key).unwrap(); + /// let trimmed = string.trim_start(); + /// let dec = trimmed.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe-rs "); + /// ``` + pub fn trim_start(&self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().trim_start(&self.inner.on_cpu()); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support trim_start"); + } + }) + } + + /// Returns a new encrypted string with whitespace removed from the end. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt(" tfhe-rs ", &client_key).unwrap(); + /// let trimmed = string.trim_end(); + /// let dec = trimmed.decrypt(&client_key); + /// assert_eq!(&dec, " tfhe-rs"); + /// ``` + pub fn trim_end(&self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().trim_end(&self.inner.on_cpu()); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support trim_end"); + } + }) + } + + /// Returns a new encrypted string with whitespace removed from both the start and end. + /// + /// # Example + /// + /// ```rust + /// use tfhe::prelude::*; + /// use tfhe::{ + /// generate_keys, set_server_key, ConfigBuilder, FheAsciiString, FheStringIsEmpty, + /// FheStringLen, + /// }; + /// + /// let (client_key, server_key) = generate_keys(ConfigBuilder::default()); + /// set_server_key(server_key); + /// + /// let string = FheAsciiString::try_encrypt(" tfhe-rs ", &client_key).unwrap(); + /// let trimmed = string.trim(); + /// let dec = trimmed.decrypt(&client_key); + /// assert_eq!(&dec, "tfhe-rs"); + /// ``` + pub fn trim(&self) -> Self { + with_internal_keys(|keys| match keys { + InternalServerKey::Cpu(cpu_key) => { + let inner = cpu_key.string_key().trim(&self.inner.on_cpu()); + Self::new(inner, cpu_key.tag.clone()) + } + #[cfg(feature = "gpu")] + InternalServerKey::Cuda(_) => { + panic!("gpu does not support trim"); + } + }) + } +} diff --git a/tfhe/src/high_level_api/strings/mod.rs b/tfhe/src/high_level_api/strings/mod.rs new file mode 100644 index 0000000000..0d9d87acf0 --- /dev/null +++ b/tfhe/src/high_level_api/strings/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod ascii; +#[cfg(test)] +mod tests; +pub(in crate::high_level_api) mod traits; diff --git a/tfhe/src/high_level_api/strings/tests/cpu.rs b/tfhe/src/high_level_api/strings/tests/cpu.rs new file mode 100644 index 0000000000..9c8ffe31aa --- /dev/null +++ b/tfhe/src/high_level_api/strings/tests/cpu.rs @@ -0,0 +1,37 @@ +use crate::high_level_api::tests::setup_default_cpu; + +#[test] +fn test_string_eq_ne() { + let cks = setup_default_cpu(); + super::test_string_eq_ne(&cks); +} + +#[test] +fn test_string_find_rfind() { + let cks = setup_default_cpu(); + super::test_string_find_rfind(&cks); +} + +#[test] +fn test_string_len_is_empty() { + let cks = setup_default_cpu(); + super::test_string_len_is_empty(&cks); +} + +#[test] +fn test_string_lower_upper() { + let cks = setup_default_cpu(); + super::test_string_lower_upper(&cks); +} + +#[test] +fn test_string_trim() { + let cks = setup_default_cpu(); + super::test_string_trim(&cks); +} + +#[test] +fn test_string_strip() { + let cks = setup_default_cpu(); + super::test_string_strip(&cks); +} diff --git a/tfhe/src/high_level_api/strings/tests/mod.rs b/tfhe/src/high_level_api/strings/tests/mod.rs new file mode 100644 index 0000000000..18a33b0255 --- /dev/null +++ b/tfhe/src/high_level_api/strings/tests/mod.rs @@ -0,0 +1,182 @@ +use crate::prelude::*; +use crate::{ClearString, ClientKey, FheAsciiString, FheStringIsEmpty, FheStringLen}; + +mod cpu; + +fn test_string_eq_ne(client_key: &ClientKey) { + let string1 = FheAsciiString::try_encrypt("Zama", client_key).unwrap(); + let string2 = FheAsciiString::try_encrypt("zama", client_key).unwrap(); + + assert!(!string1.eq(&string2).decrypt(client_key)); + assert!(!string1 + .eq(&ClearString::new("zamA".into())) + .decrypt(client_key)); + + assert!(string1.eq(&string1).decrypt(client_key)); + assert!(string2.eq(&string2).decrypt(client_key)); + + assert!(string1.ne(&string2).decrypt(client_key)); + + assert!(!string1.ne(&string1).decrypt(client_key)); + assert!(!string2.ne(&string2).decrypt(client_key)); +} + +fn test_string_find_rfind(client_key: &ClientKey) { + // Simple case with no duplicate + { + let clear_string = "The quick brown fox jumps over the lazy dog"; + let string1 = FheAsciiString::try_encrypt(clear_string, client_key).unwrap(); + + let (index, found) = string1.find(&ClearString::new("brown".into())); + assert!(found.decrypt(client_key)); + let index: u32 = index.decrypt(client_key); + assert_eq!(index as usize, clear_string.find("brown").unwrap()); + + let (index, found) = string1.find(&ClearString::new("cat".into())); + assert!(!found.decrypt(client_key)); + let index: u32 = index.decrypt(client_key); + assert_eq!(index as usize, 0); + } + + { + let clear_string = "The quick brown dog jumps over the lazy dog"; + let string1 = FheAsciiString::try_encrypt(clear_string, client_key).unwrap(); + + let (index, found) = string1.find(&ClearString::new("dog".into())); + assert!(found.decrypt(client_key)); + let index: u32 = index.decrypt(client_key); + assert_eq!(index as usize, clear_string.find("dog").unwrap()); + + let (index, found) = string1.rfind(&ClearString::new("dog".into())); + assert!(found.decrypt(client_key)); + let index: u32 = index.decrypt(client_key); + assert_eq!(index as usize, clear_string.rfind("dog").unwrap()); + } +} + +fn test_string_len_is_empty(client_key: &ClientKey) { + let clear_string = "The quick brown fox jumps over the lazy dog"; + let string = FheAsciiString::try_encrypt(clear_string, client_key).unwrap(); + match string.len() { + FheStringLen::NoPadding(len) => assert_eq!(len, clear_string.len() as u16), + FheStringLen::Padding(_) => { + panic!("Unexpected result"); + } + } + match string.is_empty() { + FheStringIsEmpty::NoPadding(is_empty) => { + assert!(!is_empty); + } + FheStringIsEmpty::Padding(_) => { + panic!("Unexpected result"); + } + } + + let padding_len = 10; + let string = + FheAsciiString::try_encrypt_with_padding(clear_string, padding_len, client_key).unwrap(); + match string.len() { + FheStringLen::NoPadding(_) => panic!("Unexpected result"), + FheStringLen::Padding(enc_len) => { + let len: u16 = enc_len.decrypt(client_key); + assert_eq!(len, clear_string.len() as u16) + } + } + match string.is_empty() { + FheStringIsEmpty::NoPadding(_) => { + panic!("Unexpected result"); + } + FheStringIsEmpty::Padding(enc_is_empty) => { + let is_empty = enc_is_empty.decrypt(client_key); + assert!(!is_empty); + } + } + + let string = FheAsciiString::try_encrypt("", client_key).unwrap(); + match string.len() { + FheStringLen::NoPadding(len) => assert_eq!(len, 0u16), + FheStringLen::Padding(_) => { + panic!("Unexpected result"); + } + } + match string.is_empty() { + FheStringIsEmpty::NoPadding(is_empty) => { + assert!(is_empty); + } + FheStringIsEmpty::Padding(_) => { + panic!("Unexpected result"); + } + } + + let string = FheAsciiString::try_encrypt_with_padding("", 10, client_key).unwrap(); + match string.len() { + FheStringLen::NoPadding(len) => assert_eq!(len, 0u16), + FheStringLen::Padding(_) => { + panic!("Unexpected result"); + } + } + match string.is_empty() { + FheStringIsEmpty::NoPadding(is_empty) => { + assert!(is_empty); + } + FheStringIsEmpty::Padding(_) => { + panic!("Unexpected result"); + } + } +} + +fn test_string_lower_upper(client_key: &ClientKey) { + let string = FheAsciiString::try_encrypt("12TfHe3-8RS!@", client_key).unwrap(); + + let lower = string.to_lowercase(); + let dec = lower.decrypt(client_key); + assert_eq!(&dec, "12tfhe3-8rs!@"); + + let upper = string.to_uppercase(); + let dec = upper.decrypt(client_key); + assert_eq!(&dec, "12TFHE3-8RS!@"); +} + +fn test_string_trim(client_key: &ClientKey) { + let string = FheAsciiString::try_encrypt(" tfhe-rs zama ", client_key).unwrap(); + + let trimmed_start = string.trim_start(); + let dec = trimmed_start.decrypt(client_key); + assert_eq!(dec, "tfhe-rs zama "); + + let trimmed_end = string.trim_end(); + let dec = trimmed_end.decrypt(client_key); + assert_eq!(dec, " tfhe-rs zama"); + + let trimmed = string.trim(); + let dec = trimmed.decrypt(client_key); + assert_eq!(dec, "tfhe-rs zama"); +} + +fn test_string_strip(client_key: &ClientKey) { + let string = FheAsciiString::try_encrypt("The lazy cat", client_key).unwrap(); + + let prefix = FheAsciiString::try_encrypt("The", client_key).unwrap(); + let (stripped, is_stripped) = string.strip_prefix(&prefix); + assert!(is_stripped.decrypt(client_key)); + let dec = stripped.decrypt(client_key); + assert_eq!(dec, " lazy cat"); + + let prefix = FheAsciiString::try_encrypt("the", client_key).unwrap(); + let (stripped, is_stripped) = string.strip_prefix(&prefix); + assert!(!is_stripped.decrypt(client_key)); + let dec = stripped.decrypt(client_key); + assert_eq!(dec, "The lazy cat"); + + let prefix = ClearString::new("cat".into()); + let (stripped, is_stripped) = string.strip_suffix(&prefix); + assert!(is_stripped.decrypt(client_key)); + let dec = stripped.decrypt(client_key); + assert_eq!(dec, "The lazy "); + + let prefix = ClearString::new("dog".into()); + let (stripped, is_stripped) = string.strip_suffix(&prefix); + assert!(!is_stripped.decrypt(client_key)); + let dec = stripped.decrypt(client_key); + assert_eq!(dec, "The lazy cat"); +} diff --git a/tfhe/src/high_level_api/strings/traits.rs b/tfhe/src/high_level_api/strings/traits.rs new file mode 100644 index 0000000000..4a45f3d368 --- /dev/null +++ b/tfhe/src/high_level_api/strings/traits.rs @@ -0,0 +1,45 @@ +use crate::{FheBool, FheUint32}; + +pub trait FheEqIgnoreCase { + fn eq_ignore_case(&self, rhs: &Rhs) -> FheBool; +} + +pub trait FheStringMatching { + fn contains(&self, other: Rhs) -> FheBool; + fn starts_with(&self, other: Rhs) -> FheBool; + fn ends_with(&self, other: Rhs) -> FheBool; +} + +pub trait FheStringFind { + fn find(&self, other: Rhs) -> (FheUint32, FheBool); + fn rfind(&self, other: Rhs) -> (FheUint32, FheBool); +} + +pub trait FheStringStrip +where + Self: Sized, +{ + fn strip_prefix(&self, pat: Rhs) -> (Self, FheBool); + fn strip_suffix(&self, pat: Rhs) -> (Self, FheBool); +} + +pub trait FheStringReplace +where + Self: Sized, +{ + fn replace(&self, from: Rhs, to: &Self) -> Self; +} + +pub trait FheStringReplaceN +where + Self: Sized, +{ + fn replacen(&self, from: Rhs, to: &Self, count: Count) -> Self; +} + +pub trait FheStringRepeat +where + Self: Sized, +{ + fn repeat(&self, count: Count) -> Self; +} diff --git a/tfhe/src/high_level_api/tests/mod.rs b/tfhe/src/high_level_api/tests/mod.rs index 1cd94277a6..4edaa5ac11 100644 --- a/tfhe/src/high_level_api/tests/mod.rs +++ b/tfhe/src/high_level_api/tests/mod.rs @@ -5,11 +5,32 @@ use crate::high_level_api::{ generate_keys, ClientKey, ConfigBuilder, FheBool, FheUint256, FheUint8, PublicKey, ServerKey, }; use crate::integer::U256; +use crate::shortint::{ClassicPBSParameters, PBSParameters}; use crate::{ set_server_key, CompactPublicKey, CompressedPublicKey, CompressedServerKey, FheUint32, Tag, }; use std::fmt::Debug; +pub(crate) fn setup_cpu(params: Option>) -> ClientKey { + let config = params + .map_or_else(ConfigBuilder::default, |p| { + ConfigBuilder::with_custom_parameters(p.into()) + }) + .build(); + + let client_key = ClientKey::generate(config); + let csks = crate::CompressedServerKey::new(&client_key); + let server_key = csks.decompress(); + + set_server_key(server_key); + + client_key +} + +pub(crate) fn setup_default_cpu() -> ClientKey { + setup_cpu(Option::::None) +} + fn assert_that_public_key_encryption_is_decrypted_by_client_key( clear: ClearType, pks: &PublicKey, diff --git a/tfhe/src/strings/backward_compatibility/mod.rs b/tfhe/src/strings/backward_compatibility/mod.rs new file mode 100644 index 0000000000..544c86f4fd --- /dev/null +++ b/tfhe/src/strings/backward_compatibility/mod.rs @@ -0,0 +1,12 @@ +use crate::strings::ciphertext::{FheAsciiChar, FheString}; +use tfhe_versionable::VersionsDispatch; + +#[derive(VersionsDispatch)] +pub enum FheAsciiCharVersions { + V0(FheAsciiChar), +} + +#[derive(VersionsDispatch)] +pub enum FheStringVersions { + V0(FheString), +} diff --git a/tfhe/src/strings/ciphertext.rs b/tfhe/src/strings/ciphertext.rs index c74dae6035..a14df46005 100644 --- a/tfhe/src/strings/ciphertext.rs +++ b/tfhe/src/strings/ciphertext.rs @@ -5,20 +5,25 @@ use crate::integer::{ ServerKey as IntegerServerKey, }; use crate::shortint::MessageModulus; +use crate::strings::backward_compatibility::{FheAsciiCharVersions, FheStringVersions}; use crate::strings::client_key::EncU16; use crate::strings::N; use rayon::iter::{IndexedParallelIterator, ParallelIterator}; use rayon::slice::ParallelSlice; +use serde::{Deserialize, Serialize}; use std::borrow::Borrow; +use tfhe_versionable::Versionize; /// Represents a encrypted ASCII character. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Versionize)] +#[versionize(FheAsciiCharVersions)] pub struct FheAsciiChar { pub enc_char: RadixCiphertext, } /// Represents a encrypted string made up of [`FheAsciiChar`]s. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, Versionize)] +#[versionize(FheStringVersions)] pub struct FheString { pub enc_string: Vec, pub padded: bool, @@ -71,6 +76,18 @@ pub enum GenericPatternRef<'a> { Enc(&'a FheString), } +impl<'a> From<&'a ClearString> for GenericPatternRef<'a> { + fn from(value: &'a ClearString) -> Self { + Self::Clear(value) + } +} + +impl<'a> From<&'a FheString> for GenericPatternRef<'a> { + fn from(value: &'a FheString) -> Self { + Self::Enc(value) + } +} + impl GenericPatternRef<'_> { pub fn to_owned(self) -> GenericPattern { match self { diff --git a/tfhe/src/strings/client_key.rs b/tfhe/src/strings/client_key.rs index 2fdb2dfbb5..86c160449e 100644 --- a/tfhe/src/strings/client_key.rs +++ b/tfhe/src/strings/client_key.rs @@ -30,6 +30,9 @@ pub struct EncU16 { } impl EncU16 { + pub(crate) fn new(value: RadixCiphertext, max: Option) -> Self { + Self { cipher: value, max } + } pub fn cipher(&self) -> &RadixCiphertext { &self.cipher } diff --git a/tfhe/src/strings/mod.rs b/tfhe/src/strings/mod.rs index 9a1079d499..31cea1191e 100644 --- a/tfhe/src/strings/mod.rs +++ b/tfhe/src/strings/mod.rs @@ -2,6 +2,7 @@ pub mod ciphertext; pub mod client_key; pub mod server_key; +mod backward_compatibility; mod char_iter; #[cfg(test)] mod test_functions; @@ -11,4 +12,4 @@ mod test_functions; const N: usize = 32; pub use client_key::ClientKey; -pub use server_key::ServerKey; +pub use server_key::{ServerKey, ServerKeyRef}; diff --git a/tfhe/src/strings/server_key/comp.rs b/tfhe/src/strings/server_key/comp.rs index 371284fd0f..ead2f6f1bb 100644 --- a/tfhe/src/strings/server_key/comp.rs +++ b/tfhe/src/strings/server_key/comp.rs @@ -1,6 +1,7 @@ use crate::integer::{BooleanBlock, ServerKey as IntegerServerKey}; -use crate::strings::ciphertext::{FheString, GenericPatternRef}; +use crate::strings::ciphertext::{FheString, GenericPattern, GenericPatternRef}; use crate::strings::server_key::{FheStringIsEmpty, ServerKey}; +use crate::ClearString; use std::borrow::Borrow; impl + Sync> ServerKey { @@ -294,4 +295,47 @@ impl + Sync> ServerKey { sk.ge_parallelized(&lhs_uint, &rhs_uint) } + + /// 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 `GenericPatternRef::Clear` for + /// a clear string or `GenericPatternRef::Enc` for an encrypted string. + /// + /// # Examples + /// + /// ```rust + /// use tfhe::integer::{ClientKey, ServerKey}; + /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64; + /// use tfhe::strings::ciphertext::{FheString, GenericPattern}; + /// + /// let ck = ClientKey::new(PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64); + /// let sk = ServerKey::new_radix_server_key(&ck); + /// let ck = tfhe::strings::ClientKey::new(ck); + /// let sk = tfhe::strings::ServerKey::new(sk); + /// 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.as_ref()); + /// let are_equal = ck.inner().decrypt_bool(&result); + /// + /// assert!(are_equal); + /// ``` + pub fn eq_ignore_case(&self, lhs: &FheString, rhs: GenericPatternRef<'_>) -> BooleanBlock { + let (lhs, rhs) = rayon::join( + || self.to_lowercase(lhs), + || match rhs { + GenericPatternRef::Clear(rhs) => { + GenericPattern::Clear(ClearString::new(rhs.str().to_lowercase())) + } + GenericPatternRef::Enc(rhs) => GenericPattern::Enc(self.to_lowercase(rhs)), + }, + ); + + self.eq(&lhs, rhs.as_ref()) + } } diff --git a/tfhe/src/strings/server_key/mod.rs b/tfhe/src/strings/server_key/mod.rs index 79f9ed0cc8..7b0f395679 100644 --- a/tfhe/src/strings/server_key/mod.rs +++ b/tfhe/src/strings/server_key/mod.rs @@ -21,6 +21,8 @@ where inner: T, } +pub type ServerKeyRef<'a> = ServerKey<&'a IntegerServerKey>; + impl ServerKey where T: Borrow + Sync, diff --git a/tfhe/src/strings/server_key/no_patterns.rs b/tfhe/src/strings/server_key/no_patterns.rs index 5ed3872b31..be85353000 100644 --- a/tfhe/src/strings/server_key/no_patterns.rs +++ b/tfhe/src/strings/server_key/no_patterns.rs @@ -1,7 +1,5 @@ -use crate::integer::{BooleanBlock, ServerKey as IntegerServerKey}; -use crate::strings::ciphertext::{ - ClearString, FheString, GenericPattern, GenericPatternRef, UIntArg, -}; +use crate::integer::ServerKey as IntegerServerKey; +use crate::strings::ciphertext::{FheString, UIntArg}; use crate::strings::server_key::{FheStringIsEmpty, FheStringLen, ServerKey}; use rayon::prelude::*; use std::borrow::Borrow; @@ -243,49 +241,6 @@ impl + Sync> ServerKey { 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 `GenericPatternRef::Clear` for - /// a clear string or `GenericPatternRef::Enc` for an encrypted string. - /// - /// # Examples - /// - /// ```rust - /// use tfhe::integer::{ClientKey, ServerKey}; - /// use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64; - /// use tfhe::strings::ciphertext::{FheString, GenericPattern}; - /// - /// let ck = ClientKey::new(PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64); - /// let sk = ServerKey::new_radix_server_key(&ck); - /// let ck = tfhe::strings::ClientKey::new(ck); - /// let sk = tfhe::strings::ServerKey::new(sk); - /// 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.as_ref()); - /// let are_equal = ck.inner().decrypt_bool(&result); - /// - /// assert!(are_equal); - /// ``` - pub fn eq_ignore_case(&self, lhs: &FheString, rhs: GenericPatternRef<'_>) -> BooleanBlock { - let (lhs, rhs) = rayon::join( - || self.to_lowercase(lhs), - || match rhs { - GenericPatternRef::Clear(rhs) => { - GenericPattern::Clear(ClearString::new(rhs.str().to_lowercase())) - } - GenericPatternRef::Enc(rhs) => GenericPattern::Enc(self.to_lowercase(rhs)), - }, - ); - - self.eq(&lhs, rhs.as_ref()) - } - /// Concatenates two encrypted strings and returns the result as a new encrypted string. /// /// This function is equivalent to using the `+` operator on standard strings.