Skip to content

Commit

Permalink
Merge pull request #1133 from zama-ai/docs/tfhers
Browse files Browse the repository at this point in the history
Update docs on TFHE-rs
  • Loading branch information
youben11 authored Nov 14, 2024
2 parents 1c0c7db + 3f37458 commit 61e6fad
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 95 deletions.
27 changes: 7 additions & 20 deletions docs/guides/tfhers/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# TFHE-rs Interoperability


{% hint style="warning" %}

This feature is currently in beta version. Please note that the API may change in future Concrete releases.
Expand All @@ -27,28 +26,16 @@ When working with a TFHE-rs integer type in Concrete, you can use the `.encode(.
```python
from concrete.fhe import tfhers

# don't worry about the API, we will have better examples later.
# we just want to show the encoding here
tfhers_params = tfhers.CryptoParams(
lwe_dimension=909,
glwe_dimension=1,
polynomial_size=4096,
pbs_base_log=15,
pbs_level=2,
lwe_noise_distribution=9.743962418842052e-07,
glwe_noise_distribution=2.168404344971009e-19,
encryption_key_choice=tfhers.EncryptionKeyChoice.BIG,
)

# TFHERSInteger using this type will be represented as a vector of 8/2=4 integers
tfhers_type = tfhers.TFHERSIntegerType(
# This will create a TFHE-rs unsigned integer of 8 bits
# using the parameters from the json file
tfhers_type = tfhers.get_type_from_params(
"tfhers_params.json",
is_signed=False,
bit_width=8,
carry_width=3,
msg_width=2,
params=tfhers_params,
precision=8,
)

# Encoding could change depending on the parameters saved in 'tfhers_params.json'
# You should have the same result if message_modulus was equal to 4
assert (tfhers_type.encode(123) == [3, 2, 3, 1]).all()

assert tfhers_type.decode([3, 2, 3, 1]) == 123
Expand Down
31 changes: 13 additions & 18 deletions docs/guides/tfhers/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,51 @@ Concrete already has its serilization functions (e.g. `tfhers_bridge.export_valu

## Ciphertexts

We can deserialize `FheUint8` (and similarly other types) using `bincode`
We should deserialize `FheUint8` using safe serialization functions from TFHE-rs

```rust
use tfhe::FheUint8;
use tfhe::safe_serialization::{safe_deserialize, safe_serialize};

const SERIALIZE_SIZE_LIMIT: u64 = 1_000_000_000;

/// ...

fn load_fheuint8(path: &String) -> FheUint8 {
let path_fheuint: &Path = Path::new(path);
let serialized_fheuint = fs::read(path_fheuint).unwrap();
let mut serialized_data = Cursor::new(serialized_fheuint);
bincode::deserialize_from(&mut serialized_data).unwrap()
let file = fs::File::open(path).unwrap();
safe_deserialize(file, SERIALIZE_SIZE_LIMIT).unwrap()
}
```

To serialize

```rust
fn save_fheuint8(fheuint: FheUint8, path: &String) {
let mut serialized_ct = Vec::new();
bincode::serialize_into(&mut serialized_ct, &fheuint).unwrap();
let path_ct: &Path = Path::new(path);
fs::write(path_ct, serialized_ct).unwrap();
let file = fs::File::create(path).unwrap();
safe_serialize(fheuint, file, SERIALIZE_SIZE_LIMIT).unwrap()
}
```

## Secret Key

We can deserialize `LweSecretKey` using `bincode`
TFHE-rs safe serialization doesn't yet support this key so we should deserialize `LweSecretKey` using `bincode`

```rust
use tfhe::core_crypto::prelude::LweSecretKey;

/// ...

fn load_lwe_sk(path: &String) -> LweSecretKey<Vec<u64>> {
let path_sk: &Path = Path::new(path);
let serialized_lwe_key = fs::read(path_sk).unwrap();
let mut serialized_data = Cursor::new(serialized_lwe_key);
bincode::deserialize_from(&mut serialized_data).unwrap()
let file = fs::File::open(path).unwrap();
bincode::deserialize_from(file).unwrap()
}
```

To serialize

```rust
fn save_lwe_sk(lwe_sk: LweSecretKey<Vec<u64>>, path: &String) {
let mut serialized_lwe_key = Vec::new();
bincode::serialize_into(&mut serialized_lwe_key, &lwe_sk).unwrap();
let path_sk: &Path = Path::new(path);
fs::write(path_sk, serialized_lwe_key).unwrap();
let file = fs::File::create(path).unwrap();
bincode::serialize_into(file, lwe_sk).unwrap()
}
```
27 changes: 9 additions & 18 deletions docs/guides/tfhers/shared-key.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,15 @@ In short, we first determine a suitable set of parameters from TFHE-rs and then
from functools import partial
from concrete.fhe import tfhers

tfhers_params = tfhers.CryptoParams(
lwe_dimension=909,
glwe_dimension=1,
polynomial_size=4096,
pbs_base_log=15,
pbs_level=2,
lwe_noise_distribution=9.743962418842052e-07,
glwe_noise_distribution=2.168404344971009e-19,
encryption_key_choice=tfhers.EncryptionKeyChoice.BIG,
)
# creating a TFHE-rs ciphertext type with crypto and encoding params
tfhers_type = tfhers.TFHERSIntegerType(
# This will create a TFHE-rs unsigned integer of 8 bits
# using the parameters from the json file
tfhers_type = tfhers.get_type_from_params(
# The json file is a serialization of ClassicPBSParameters in TFHE-rs
"tfhers_params.json",
is_signed=False,
bit_width=8,
carry_width=3,
msg_width=2,
params=tfhers_params,
precision=8,
)

# this partial will help us create TFHERSInteger with the given type instead of calling
# tfhers.TFHERSInteger(tfhers_type, value) every time
tfhers_int = partial(tfhers.TFHERSInteger, tfhers_type)
Expand Down Expand Up @@ -146,7 +137,7 @@ let shortint_key =
// Concrete uses this parameters to define the TFHE-rs ciphertext type
tfhe::shortint::prelude::PARAM_MESSAGE_2_CARRY_3_KS_PBS
).unwrap();
let client_key = ClientKey::from_raw_parts(shortint_key.into(), None, None);
let client_key = ClientKey::from_raw_parts(shortint_key.into(), None, None, tfhe::Tag::default());
let server_key = client_key.generate_server_key();
```

Expand All @@ -167,7 +158,7 @@ let config = ConfigBuilder::with_custom_parameters(
.build();

let (client_key, server_key) = generate_keys(config);
let (integer_ck, _, _) = client_key.clone().into_raw_parts();
let (integer_ck, _, _, _) = client_key.clone().into_raw_parts();
let shortint_ck = integer_ck.into_raw_parts();
let (glwe_secret_key, _, _) = shortint_ck.into_raw_parts();
let lwe_secret_key = glwe_secret_key.into_lwe_secret_key();
Expand Down
58 changes: 58 additions & 0 deletions frontends/concrete-python/concrete/fhe/tfhers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
tfhers module to represent, and compute on tfhers integer values.
"""

import json
from math import log2

from .bridge import new_bridge
from .dtypes import (
CryptoParams,
Expand All @@ -18,3 +21,58 @@
)
from .tracing import from_native, to_native
from .values import TFHERSInteger


def get_type_from_params(
path_to_params_json: str, is_signed: bool, precision: int
) -> TFHERSIntegerType:
"""Get a TFHE-rs integer type from TFHE-rs parameters in JSON format.
Args:
path_to_params_json (str): path to the TFHE-rs parameters (JSON format)
is_signed (bool): sign of the result type
precision (int): precision of the result type
Returns:
TFHERSIntegerType: constructed type from the loaded parameters
"""

# Read crypto parameters from TFHE-rs in the json file
with open(path_to_params_json) as f:
crypto_param_dict = json.load(f)

lwe_dim = crypto_param_dict["lwe_dimension"]
glwe_dim = crypto_param_dict["glwe_dimension"]
poly_size = crypto_param_dict["polynomial_size"]
pbs_base_log = crypto_param_dict["pbs_base_log"]
pbs_level = crypto_param_dict["pbs_level"]
msg_width = int(log2(crypto_param_dict["message_modulus"]))
carry_width = int(log2(crypto_param_dict["carry_modulus"]))
lwe_noise_distr = crypto_param_dict["lwe_noise_distribution"]["Gaussian"]["std"]
glwe_noise_distr = crypto_param_dict["glwe_noise_distribution"]["Gaussian"]["std"]
encryption_key_choice = (
EncryptionKeyChoice.BIG
if crypto_param_dict["encryption_key_choice"] == "Big"
else EncryptionKeyChoice.SMALL
)

assert glwe_dim == 1, "glwe dim must be 1"
assert encryption_key_choice == EncryptionKeyChoice.BIG, "encryption_key_choice must be BIG"

tfhers_params = CryptoParams(
lwe_dimension=lwe_dim,
glwe_dimension=glwe_dim,
polynomial_size=poly_size,
pbs_base_log=pbs_base_log,
pbs_level=pbs_level,
lwe_noise_distribution=lwe_noise_distr,
glwe_noise_distribution=glwe_noise_distr,
encryption_key_choice=encryption_key_choice,
)
return TFHERSIntegerType(
is_signed=is_signed,
bit_width=precision,
carry_width=carry_width,
msg_width=msg_width,
params=tfhers_params,
)
6 changes: 6 additions & 0 deletions frontends/concrete-python/examples/tfhers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ python example.py keygen -s $TDIR/tfhers_sk -o $TDIR/concrete_sk -k $TDIR/concre
../../tests/tfhers-utils/target/release/tfhers_utils encrypt-with-key --value 73 --ciphertext $TDIR/tfhers_ct_2 --client-key $TDIR/tfhers_client_key
```

{% hint style="info" %}

If you have tensor inputs, then you can encrypt by passing your flat tensor in `--value`. Concrete will take care of reshaping the values to the corresponding shape. For example `--value=1,2,3,4` can represent a 2 by 2 tensor, or a flat vector of 4 values.

{% endhint %}

## Compute in TFHE-rs

```sh
Expand Down
40 changes: 8 additions & 32 deletions frontends/concrete-python/examples/tfhers/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,18 @@
from concrete import fhe
from concrete.fhe import tfhers

########## Params #####################
LWE_DIM = 909
GLWE_DIM = 1
POLY_SIZE = 4096
PBS_BASE_LOG = 15
PBS_LEVEL = 2
MSG_WIDTH = 2
CARRY_WIDTH = 3
ENCRYPTION_KEY_CHOICE = tfhers.EncryptionKeyChoice.BIG
LWE_NOISE_DISTR = 0
GLWE_NOISE_DISTR = 2.168404344971009e-19
#######################################

assert GLWE_DIM == 1, "glwe dim must be 1"

### Options ###########################
# These parameters were saved by running the tfhers_utils utility:
# tfhers_utils save-params tfhers_params.json
TFHERS_PARAMS_FILE = "tfhers_params.json"
FHEUINT_PRECISION = 8
IS_SIGNED = False
#######################################


tfhers_params = tfhers.CryptoParams(
lwe_dimension=LWE_DIM,
glwe_dimension=GLWE_DIM,
polynomial_size=POLY_SIZE,
pbs_base_log=PBS_BASE_LOG,
pbs_level=PBS_LEVEL,
lwe_noise_distribution=LWE_NOISE_DISTR,
glwe_noise_distribution=GLWE_NOISE_DISTR,
encryption_key_choice=ENCRYPTION_KEY_CHOICE,
)
tfhers_type = tfhers.TFHERSIntegerType(
is_signed=False,
bit_width=FHEUINT_PRECISION,
carry_width=CARRY_WIDTH,
msg_width=MSG_WIDTH,
params=tfhers_params,
tfhers_type = tfhers.get_type_from_params(
TFHERS_PARAMS_FILE,
is_signed=IS_SIGNED,
precision=FHEUINT_PRECISION,
)
tfhers_int = partial(tfhers.TFHERSInteger, tfhers_type)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"lwe_dimension":902,"glwe_dimension":1,"polynomial_size":4096,"lwe_noise_distribution":{"Gaussian":{"std":1.0994794733558207e-6,"mean":0.0}},"glwe_noise_distribution":{"Gaussian":{"std":2.168404344971009e-19,"mean":0.0}},"pbs_base_log":15,"pbs_level":2,"ks_base_log":3,"ks_level":6,"message_modulus":4,"carry_modulus":8,"max_noise_level":10,"log2_p_fail":-64.084,"ciphertext_modulus":{"modulus":0,"scalar_bits":64},"encryption_key_choice":"Big"}
31 changes: 31 additions & 0 deletions frontends/concrete-python/tests/tfhers-utils/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontends/concrete-python/tests/tfhers-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"

bincode = "1.3.3"
serde = "1"
serde_json = "1.0.128"

clap = { version = "4.5.16", features = ["derive"] }

Expand Down
Loading

0 comments on commit 61e6fad

Please sign in to comment.