Skip to content

Instantly share code, notes, and snippets.

@FlorentCLMichel
Last active May 4, 2022 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FlorentCLMichel/9fbaf668b030e263d3d9614e585bb9ed to your computer and use it in GitHub Desktop.
Save FlorentCLMichel/9fbaf668b030e263d3d9614e585bb9ed to your computer and use it in GitHub Desktop.
/// encrypt the string `plaintext` into a vector of ciphertexts
pub fn str_to_encrypted_seq(client_key: &ClientKey, plaintext: &str)
-> Vec<Ciphertext>
{
// convert the string to a sequence of bytes
let plaintext_bytes = plaintext.as_bytes();
// create a vector of ciphertexts
let mut result = Vec::<Ciphertext>::new();
// loop over the bytes of the plaintext
for x in plaintext_bytes {
// loop over the bits of the current byte
for i in 0..N_BITS_PER_CHAR {
// if the bit is 1, push an encryption of true to the results
// otherwise, push an encryption of false
if *x & (1 << i) != 0 {
result.push(client_key.encrypt(true))
} else {
result.push(client_key.encrypt(false))
}
}
}
result
}
/// a custom error for FHE operations
#[derive(Debug, Clone)]
pub struct FHEError {
message: String
}
impl FHEError {
/// creates a new [`FHEError`]
pub fn new(message: String) -> FHEError {
FHEError { message }
}
}
// implement the `Display` trait to be able to print the error
impl std::fmt::Display for FHEError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "FHEError: {}", self.message)
}
}
// implement the `Error` trait
impl std::error::Error for FHEError {}
// convert Concrete's [`CryptoAPIError`] to an `FHEError`
impl std::convert::From<CryptoAPIError> for FHEError {
fn from (err: CryptoAPIError) -> Self {
FHEError::new(format!("CryptoAPIError: {:}", err))
}
}
pub use concrete_boolean::{ gen_keys,
server_key::ServerKey,
ciphertext::Ciphertext,
client_key::ClientKey };
pub use concrete::CryptoAPIError;
pub const N_BITS_PER_CHAR: usize = 8;
pub use concrete_boolean::{ gen_keys,
server_key::ServerKey,
ciphertext::Ciphertext,
client_key::ClientKey };
pub use concrete::CryptoAPIError;
/// Return a ciphertext that decrypts to `true` if `a` and `b` encrypt the same bit and `false`
/// otherwise.
pub fn bits_are_equal(server_key: &ServerKey, a: &Ciphertext, b: &Ciphertext)
-> Ciphertext
{
server_key.xnor(&a, &b)
}
/// If `a` is not empty and `a` and `b` have the same length, return `Ok(c)` where `c` is ciphertext
/// that decrypts to `true` if `a` and `b` encrypt the same sequence of bits and `false`otherwise.
/// Return an `FHEError` if `a` is empty or if `a` and `b` have different lengths.
pub fn are_equal(server_key: &ServerKey, a: &[Ciphertext], b: &[Ciphertext])
-> Result<Ciphertext, FHEError>
{
// check that a is not empty
if a.len() == 0 {
return Err(FHEError::new(
"Error checking the equality between two elements: the first element is empty"
.to_string(),
));
}
// check that the two inputs have the same size
if a.len() != b.len() {
return Err(FHEError::new(format!(
"Error checking the equality between two elements: the elements of different lengths ({} and {})",
a.len(), b.len()
)));
}
// check the equality of the first elements
let mut are_equal = bits_are_equal(server_key, &a[0], &b[0]);
// check the equality of the other elements
for i in 1..a.len() {
are_equal = server_key.and(&are_equal, &bits_are_equal(server_key, &a[i], &b[i]));
}
Ok(are_equal)
}
/// encrypt an str
pub fn str_to_encrypted_seq(client_key: &ClientKey, plaintext: &str)
-> Vec<Ciphertext>
{
let plaintext_bytes = plaintext.as_bytes();
let mut result = Vec::<Ciphertext>::new();
for x in plaintext_bytes {
for i in 0..8 {
if *x & (1 << i) != 0 {
result.push(client_key.encrypt(true))
} else {
result.push(client_key.encrypt(false))
}
}
}
result
}
/// search an encrypted word in a set of encrypted words
pub fn search(server_key: &ServerKey, word: &[Ciphertext], list: &[Vec<Ciphertext>])
-> Result<Ciphertext, FHEError>
{
// get an encryption of `false`
let mut result_encrypted = server_key.not(&bits_are_equal(server_key, &word[0], &word[0]));
// loop over the words in the list
for word_from_list in list {
// If the word has the right length, check it against `word`.
// Otherwise, do nothing.
if word_from_list.len() == word.len() {
result_encrypted = server_key.or(&result_encrypted,
&are_equal(server_key, &word, &word_from_list)?);
}
}
Ok(result_encrypted)
}
/// split a string on spaces and pad the words with ` `
pub fn split_and_pad_str(text: &str, length: usize)
-> Vec<String>
{
text.split(' ').map(|x| { format!("{:*<1$}", x, length) }).collect()
}
/// pad `word` with ` ` up to length `length`
pub fn pad_word(word: &str, length: usize)
-> String
{
format!("{:*<1$}", word, length)
}
/// A custom error for FHE operations
#[derive(Debug, Clone)]
pub struct FHEError {
message: String
}
impl FHEError {
pub fn new(message: String) -> FHEError {
FHEError { message }
}
}
impl std::fmt::Display for FHEError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "FHEError: {}", self.message)
}
}
impl std::error::Error for FHEError {}
impl std::convert::From<CryptoAPIError> for FHEError {
fn from (err: CryptoAPIError) -> Self {
FHEError::new(format!("CryptoAPIError: {:}", err))
}
}
fn main() {
// define the client and server keys
let (client_key, server_key) = gen_keys();
// words to be searched for
let word_1 = "like";
let word_2 = "hate";
// text to search the words in
let text = "I like making FHE easy!";
// encrypt the words and text
let word_1_enc = str_to_encrypted_seq(&client_key, &word_1);
let word_2_enc = str_to_encrypted_seq(&client_key, &word_2);
let text_enc = str_to_encrypted_seq(&client_key, &text);
// search for the words in the text
let word_1_found_enc = search_word(&server_key, &word_1_enc, &text_enc).unwrap();
let word_2_found_enc = search_word(&server_key, &word_2_enc, &text_enc).unwrap();
// decrypt the results
let words_found = [client_key.decrypt(&word_1_found_enc),
client_key.decrypt(&word_2_found_enc)];
// print the result
for (i,word) in [word_1, word_2].iter().enumerate() {
if words_found[i] {
println!("‘{}’ found!", word);
} else {
println!("‘{}’ not found", word);
}
}
println!("");
}
use test_concrete::*;
fn main() {
// maximum length of a word
let max_length = 6;
// words to be searched for
let words = ["like", "hate"];
// text to search the words in
let text = "I like making FHE easy";
// split text into words and pad them
let list_words = split_and_pad_str(text, max_length);
// define the client and server keys
let (client_key, server_key) = gen_keys();
// encrypt the words and list
let words_enc = words.iter().map(|x| str_to_encrypted_seq(&client_key,
&pad_word(x, max_length)))
.collect::<Vec<Vec<Ciphertext>>>();
let list_enc: Vec<Vec<Ciphertext>> =
list_words.iter().map(|word| str_to_encrypted_seq(&client_key, &word)).collect();
// search for the words in the list
let words_found_enc = words_enc.iter()
.map(|x| search(&server_key, x, &list_enc).unwrap())
.collect::<Vec<Ciphertext>>();
// decrypt the results
let words_found = words_found_enc.iter()
.map(|x| client_key.decrypt(x))
.collect::<Vec<bool>>();
// print the result
for (i,word) in words.iter().enumerate() {
if words_found[i] {
println!("‘{}’ found!", word);
} else {
println!("‘{}’ not found", word);
}
}
println!("");
}
‘like’ found!
‘hate’ not found
/// If `a` is not empty and `b` is no smaller than `a`, return `Ok(c)` where `c` is a cipher that
/// decrypts to `true` if the plaintext of `a` is in the plaintext of `b` and to `false` otherwise.
/// Return an `FHEError` if `a` is empty or if `b` is smaller than `a`.
pub fn search_word(server_key: &ServerKey, a: &[Ciphertext], b: &[Ciphertext])
-> Result<Ciphertext, FHEError>
{
// check that a is not empty
if a.len() == 0 {
return Err(FHEError::new(
"Error checking the equality between two elements: the first element is empty"
.to_string(),
));
}
// if b is smaller than a, return an encryption of false
if b.len() < a.len() {
return Ok(server_key.and(&a[0], &server_key.not(&a[0])));
}
// check if the start of b is a
let mut is_found = are_equal(server_key, a, &b[..a.len()])?;
// check the next positions
for i in 1..=(b.len()-a.len())/N_BITS_PER_CHAR {
is_found = server_key.or(&is_found,
&are_equal(server_key, a, &b[i*N_BITS_PER_CHAR..a.len()+i*N_BITS_PER_CHAR])?);
}
Ok(is_found)
}
/// Return a ciphertext that decrypts to `true` if `a` and `b` encrypt the same bit and `false`
/// otherwise.
pub fn bits_are_equal(server_key: &ServerKey, a: &Ciphertext, b: &Ciphertext)
-> Ciphertext
{
server_key.xnor(&a, &b)
}
/// If `a` is not empty and `a` and `b` have the same length, return `Ok(c)` where `c` is ciphertext
/// that decrypts to `true` if `a` and `b` encrypt the same sequence of bits and `false`otherwise.
/// Return an `FHEError` if `a` is empty or if `a` and `b` have different lengths.
pub fn are_equal(server_key: &ServerKey, a: &[Ciphertext], b: &[Ciphertext])
-> Result<Ciphertext, FHEError>
{
// check that a is not empty
if a.len() == 0 {
return Err(FHEError::new(
"Error checking the equality between two elements: the first element is empty"
.to_string(),
));
}
// check that the two inputs have the same size
if a.len() != b.len() {
return Err(FHEError::new(format!(
"Error checking the equality between two elements: the elements have different lengths ({} and {})",
a.len(), b.len()
)));
}
// check the equality of the first elements
let mut are_equal = bits_are_equal(server_key, &a[0], &b[0]);
// check the equality of the other elements
for i in 1..a.len() {
are_equal = server_key.and(&are_equal, &bits_are_equal(server_key, &a[i], &b[i]));
}
Ok(are_equal)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment