FheLatinString (Integers)

In this tutorial, we are going to build a data type that represents a Latin string in FHE while implementing the to_lower and to_upper functions.

The allowed characters in a Latin string are:

  • Uppercase letters: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

  • Lowercase letters: a b c d e f g h i j k l m n o p q r s t u v w x y z

For the code point of the letters, we will use the ascii codes. In ascii:

  • The uppercase letters are in the range [65, 90]

  • The lowercase letters are in the range [97, 122]

lower_case = upper_case + 32 <=> upper_case = lower_case - 32

For this type, we will use the FheUint8 type.


Types and methods

Our type will hold the encrypted characters as a Vec<FheUint8>, as well as the encrypted constant 32 to implement our functions that change the case.

In the FheLatinString::encrypt function, we have to make a bit of data validation:

  • The input string can only contain ascii letters (no digit, no special characters)

  • The input string cannot mix lower and upper case letters

These two points are to work around a limitation of FHE, which is that we cannot create branches, meaning our function cannot use conditional statements. For example, we can not check if the 'char' is a letter and uppercase to modify it to lowercase, like in the example below.

fn to_lower(string: &String) -> String {
    let mut result = String::with_capacity(string.len());
    for char in string.chars() {
        if char.is_uppercase() {
            result.extend(char.to_lowercase().to_string().chars())
        }
    }
    result
}

With these preconditions checked, implementing to_lower and to_upper is rather simple.

As we will be using the FheUint8 type, the integers feature must be activated:

# Cargo.toml

[dependencies]
# ...
concrete = { version = "0.2.0", features = ["integers"]}
use concrete::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
use concrete::prelude::*;

struct FheLatinString{
    bytes: Vec<FheUint8>,
    // Constant used to switch lower case <=> upper case
    cst: FheUint8,
}

impl FheLatinString {
    fn encrypt(string: &str, client_key: &ClientKey) -> Self {
        assert!(
            string.chars().all(|char| char.is_ascii_alphabetic()),
            "The input string must only contain ascii letters"
        );

        let has_mixed_case = string.as_bytes().windows(2).any(|window| {
            let first = char::from(*window.first().unwrap());
            let second = char::from(*window.last().unwrap());

            (first.is_ascii_lowercase() && second.is_ascii_uppercase())
                || (first.is_ascii_uppercase() && second.is_ascii_lowercase())
        });

        assert!(
            !has_mixed_case,
            "The input string cannot mix lower case and upper case letters"
        );

        let fhe_bytes = string
            .bytes()
            .map(|b| FheUint8::encrypt(b, client_key))
            .collect::<Vec<FheUint8>>();
        let cst = FheUint8::encrypt(32, client_key);

        Self {
            bytes: fhe_bytes,
            cst,
        }
    }

    fn decrypt(&self, client_key: &ClientKey) -> String {
        let ascii_bytes = self
            .bytes
            .iter()
            .map(|fhe_b| fhe_b.decrypt(client_key))
            .collect::<Vec<u8>>();
        String::from_utf8(ascii_bytes).unwrap()
    }

    fn to_upper(&self) -> Self {
        Self {
            bytes: self
                .bytes
                .iter()
                .map(|b| b - &self.cst)
                .collect::<Vec<FheUint8>>(),
            cst: self.cst.clone(),
        }
    }

    fn to_lower(&self) -> Self {
        Self {
            bytes: self
                .bytes
                .iter()
                .map(|b| b + &self.cst)
                .collect::<Vec<FheUint8>>(),
            cst: self.cst.clone(),
        }
    }
}


fn main() {
    let config = ConfigBuilder::all_disabled()
        .enable_default_uint8()
        .build();

    let (client_key, server_key) = generate_keys(config);

    set_server_key(server_key);

    let my_string = FheLatinString::encrypt("zama", &client_key);
    let verif_string = my_string.decrypt(&client_key);
    println!("{}", verif_string);

    let my_string_upper = my_string.to_upper();
    let verif_string = my_string_upper.decrypt(&client_key);
    println!("{}", verif_string);
    assert_eq!(verif_string, "ZAMA");

    let my_string_lower = my_string_upper.to_lower();
    let verif_string = my_string_lower.decrypt(&client_key);
    println!("{}", verif_string);
    assert_eq!(verif_string, "zama");
}

Last updated