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 128cea0a97..995859fb2f 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..a624608451 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/mod.rs @@ -0,0 +1,124 @@ +mod comp; +mod contains; +mod find; +mod no_pattern; +mod replace; +mod strip; +mod trim; + +use crate::high_level_api::details::MaybeCloned; +use crate::prelude::{FheDecrypt, FheTryEncrypt, Tagged}; +use crate::strings::ciphertext::FheString; +use crate::{ClientKey, Tag}; +pub use no_pattern::{FheStringIsEmpty, FheStringLen}; + +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), + } + } +} + +pub struct FheAsciiString { + pub(crate) inner: AsciiDevice, + pub(crate) tag: Tag, +} + +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, + } + } + + 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, + ) + } +} + +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..3c5f8c1c92 --- /dev/null +++ b/tfhe/src/high_level_api/strings/ascii/no_pattern.rs @@ -0,0 +1,331 @@ +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::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 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 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..49ea5b772c --- /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 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 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 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 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..09f03a0ac4 --- /dev/null +++ b/tfhe/src/high_level_api/strings/tests/mod.rs @@ -0,0 +1,152 @@ +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); + } + } +} + +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/ciphertext.rs b/tfhe/src/strings/ciphertext.rs index c74dae6035..efea15c7bf 100644 --- a/tfhe/src/strings/ciphertext.rs +++ b/tfhe/src/strings/ciphertext.rs @@ -71,6 +71,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..7bb2c1e149 100644 --- a/tfhe/src/strings/mod.rs +++ b/tfhe/src/strings/mod.rs @@ -11,4 +11,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.