chore: test vector generation tool for core algorithms

This commit is contained in:
Nicolas Sarlin
2025-11-17 14:29:10 +01:00
committed by Nicolas Sarlin
parent f45b7a9fdc
commit fc78450245
38 changed files with 670 additions and 7 deletions

View File

@@ -15,6 +15,7 @@ members = [
"utils/param_dedup",
"tests",
"mockups/tfhe-hpu-mockup",
"apps/test-vectors",
]
exclude = [

View File

@@ -25,6 +25,7 @@ BENCH_CUSTOM_COMMAND:=
NODE_VERSION=22.6
BACKWARD_COMPAT_DATA_DIR=utils/tfhe-backward-compat-data
BACKWARD_COMPAT_DATA_GEN_VERSION:=$(TFHE_VERSION)
TEST_VECTORS_DIR=apps/test-vectors
CURRENT_TFHE_VERSION:=$(shell grep '^version[[:space:]]*=' tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
WASM_PACK_VERSION="0.13.1"
WASM_BINDGEN_VERSION:=$(shell cargo tree --target wasm32-unknown-unknown -e all --prefix none | grep "wasm-bindgen v" | head -n 1 | cut -d 'v' -f2)
@@ -525,11 +526,16 @@ clippy_backward_compat_data: install_rs_check_toolchain # the toolchain is selec
echo "Cannot run clippy for backward compat crate on non x86 platform for now."; \
fi
.PHONY: clippy_test_vectors # Run clippy lints on the test vectors app
clippy_test_vectors: install_rs_check_toolchain
cd apps/test-vectors; RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
-p tfhe-test-vectors -- --no-deps -D warnings
.PHONY: clippy_all # Run all clippy targets
clippy_all: clippy_rustdoc clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets \
clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core clippy_tfhe_csprng clippy_zk_pok clippy_trivium \
clippy_versionable clippy_tfhe_lints clippy_ws_tests clippy_bench clippy_param_dedup \
clippy_backward_compat_data
clippy_test_vectors clippy_backward_compat_data
.PHONY: clippy_fast # Run main clippy targets
clippy_fast: clippy_rustdoc clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks \
@@ -1184,6 +1190,17 @@ test_backward_compatibility_ci: install_rs_build_toolchain
.PHONY: test_backward_compatibility # Same as test_backward_compatibility_ci but tries to clone the data repo first if needed
test_backward_compatibility: pull_backward_compat_data test_backward_compatibility_ci
# Generate the test vectors and update the hash file
.PHONY: gen_test_vectors
gen_test_vectors:
./scripts/test_vectors.sh generate apps/test-vectors
# Generate the test vectors and check that the content matches the hash file
# `comm` is used to compare the checksums, and will also notify of any added file
.PHONY: check_test_vectors
check_test_vectors:
./scripts/test_vectors.sh check apps/test-vectors
.PHONY: doc # Build rust doc
doc: install_rs_check_toolchain
@# Even though we are not in docs.rs, this allows to "just" build the doc
@@ -1723,6 +1740,10 @@ pull_backward_compat_data:
pull_hpu_files:
./scripts/pull_lfs_data.sh backends/tfhe-hpu-backend/
.PHONY: pull_test_vectors # Pull the data files needed for backward compatibility tests
pull_test_vectors:
./scripts/pull_lfs_data.sh $(TEST_VECTORS_DIR)
#
# Real use case examples
#
@@ -1774,6 +1795,8 @@ pcc_batch_2:
$(call run_recipe_with_details,clippy)
$(call run_recipe_with_details,clippy_all_targets)
$(call run_recipe_with_details,check_fmt_js)
$(call run_recipe_with_details,clippy_test_vectors)
$(call run_recipe_with_details,check_test_vectors)
.PHONY: pcc_batch_3 # duration: 6'50''
pcc_batch_3:

View File

@@ -0,0 +1,11 @@
[package]
name = "tfhe-test-vectors"
version = "0.1.0"
edition = "2024"
rust-version.workspace = true
[dependencies]
ciborium = "0.2"
serde = { version = "1.0", features = ["derive"] }
tfhe = { path = "../../tfhe", features = ["experimental-force_fft_algo_dif4"] }
tfhe-csprng = { path = "../../tfhe-csprng" }

View File

@@ -0,0 +1,11 @@
# Test vectors for TFHE
This folder contains test vectors for the core TFHE-rs algorithms.
The test vectors are located in `data`, and are generated using the sample program in `src/main.rs`.
To re-generate the test vectors, assuming you have [rustup](https://rust-lang.org/tools/install/) installed on your system, simply run the following command in the current folder:
```
cargo run --release
```
See [the data folder](data/README.md) for more information about the generated values.

View File

@@ -0,0 +1,28 @@
2abec9dc5d399ece68ac227f67f7cafcb9a6acac36ed734fe3d5244021eb1cda data/toy_params/glwe_after_spec_br.cbor
2c70d1d78cc3760733850a353ace2b9c4705e840141b75841739e90e51247e18 data/valid_params_128/small_lwe_secret_key.cbor
36c9080b636475fcacca503ce041bbfeee800fd3e1890dee559ea18defff9fe8 data/toy_params/glwe_after_id_br.cbor
377761beeb4216cf5aa2624a8b64b8259f5a75c32d28e850be8bced3a0cdd6f5 data/toy_params/ksk.cbor
59dba26d457f96478eda130cab5301fce86f23c6a8807de42f2a1e78c4985ca7 data/valid_params_128/lwe_ks.cbor
656f0009c7834c5bcb61621e222047516054b9bc5d0593d474ab8f1c086b67a6 data/valid_params_128/lwe_after_id_pbs.cbor
699580ca92b9c2f9e1f57fb1e312c9e8cb29714f7acdef9d2ba05f798546751f data/toy_params/lwe_sum.cbor
6e54ab41056984595b077baff70236d934308cf5c0c33b4482fbfb129b3756c6 data/valid_params_128/glwe_after_id_br.cbor
70f5e5728822de05b49071efb5ec28551b0f5cc87aa709a455d8e7f04b9c96ee data/toy_params/lwe_after_id_pbs.cbor
7cc6803f5fbc3d5a1bf597f2b979ce17eecd3d6baca12183dea21022a7b65c52 data/toy_params/bsk.cbor
7f3c40a134623b44779a556212477fea26eaed22450f3b6faeb8721d63699972 data/valid_params_128/lwe_sum.cbor
837b3bd3245d4d0534ed255fdef896fb4fa6998a258a14543dfdadd0bfc9b6dd data/toy_params/lwe_prod.cbor
8ee68ed99dd9103fb62b1e2c7c8cf483706ae2071b792d4bd16f9f93f64871f9 data/toy_params/lwe_after_spec_pbs.cbor
99a19c5d6d5f4fd81d9164d0ff96719ef362eabda256bce6a55cba6cb69e42bf data/valid_params_128/glwe_after_spec_br.cbor
aa44aea29efd6d9e4d35a21a625d9cba155672e3f7ed3eddee1e211e62ad146b data/valid_params_128/lwe_ms.cbor
b7a037b9eaa88d6385167579b93e26a0cb6976d9b8967416fd1173e113bda199 data/valid_params_128/large_lwe_secret_key.cbor
c6df98676de04fe54b5ffc2eb30a82ebb706c9d7d5a4e0ed509700fec88761f7 data/toy_params/lwe_ms.cbor
c7d5a864d5616a7d8ad50bbf40416e41e6c9b60c546dc14d4aa8fc40a418baa7 data/toy_params/large_lwe_secret_key.cbor
c806533b325b1009db38be2f9bef5f3b2fad6b77b4c71f2855ccc9d3b4162e98 data/valid_params_128/lwe_b.cbor
c9eb75bd2993639348a679cf48c06e3c38d1a513f48e5b0ce0047cea8cff6bbc data/toy_params/lwe_a.cbor
d6da5baef0e787f6be56e218d8354e26904652602db964844156fdff08350ce6 data/toy_params/lwe_ks.cbor
e44ffa6e5a50a03d32721180a051c8ce62f1791d4853aeaebed0200c183a57cf data/valid_params_128/lwe_after_spec_pbs.cbor
e591ab9af1b6a0aede273f9a3abb65a4c387feb5fa06a6959e9314058ca0f7e5 data/valid_params_128/ksk.cbor
e69d2d2c064fc8c0460b39191ca65338146990349954f5ec5ebd01d93610e7eb data/valid_params_128/lwe_a.cbor
e76c24b2a0c9a842ad13dda35473c2514f9e7d20983b5ea0759c4521a91626d9 data/valid_params_128/lwe_prod.cbor
e9afe7019acba5cda926f13e06df9930571611729d2f2e8ce41956e1f5e1db6f data/valid_params_128/bsk.cbor
eadf2eff35133ffba075df11faecddd6e7af9ddc398011ec4568e5528812b3e2 data/toy_params/lwe_b.cbor
ee9fcf45f1379ca3a7d7bf2b0e7a1cc920ceb496c0217e8604b0b58d2831749e data/toy_params/small_lwe_secret_key.cbor

View File

@@ -0,0 +1,102 @@
# Test vectors for TFHE
These test vectors are generated using [TFHE-rs](https://github.com/zama-ai/tfhe-rs), with the git tag `tfhe-test-vectors-0.1.0`.
They are TFHE-rs objects serialized in the [cbor format](https://cbor.io/). You can deserialize them using any cbor library for the language of your choice. For example, using the [cbor2](https://pypi.org/project/cbor2/) program, run: `cbor2 --pretty toy_params/lwe_a.cbor`.
You will find 2 folders with test vectors for different parameter sets:
- `valid_params_128`: valid classical PBS parameters using a gaussian noise distribution, providing 128bits of security in the IND-CPA model and a bootstrapping probability of failure of 2^{-64}.
- `toy_params`: insecure parameters that yield smaller values
The values are generated for the keyswitch -> bootstrap (KS-PBS) atomic pattern. The cleartext inputs are 2 values, A and B defined below.
All the random values are generated from a fixed seed, that can be found in the `RAND_SEED` constant below. The PRNG used is the one based on the AES block cipher in counter mode, from tfhe `tfhe-csprng` crate.
The programmable bootstrap is applied twice, with 2 different lut, the identity lut and a specific one (currently a x2 operation)
## Vectors
The following values are generated:
### Keys
| name | description | TFHE-rs type |
|------------------------|---------------------------------------------------------------------------------------|-----------------------------|
| `large_lwe_secret_key` | Encryption secret key, before the KS and after the PBS | `LweSecretKey<Vec<u64>>` |
| `small_lwe_secret_key` | Secret key encrypting ciphertexts between the KS and the PBS | `LweSecretKey<Vec<u64>>` |
| `ksk` | The keyswitching key to convert a ct from the large key to the small one | `LweKeyswitchKey<Vec<u64>>` |
| `bsk` | the bootstrapping key to perform a programmable bootstrap on the keyswitched ciphertext | `LweBootstrapKey<Vec<u64>>` |
### Ciphertexts
| name | description | TFHE-rs type | Cleartext |
|----------------------|--------------------------------------------------------------------------------------------------------------|----------------------------|--------------|
| `lwe_a` | Lwe encryption of A | `LweCiphertext<Vec<u64>>` | `A` |
| `lwe_b` | Lwe encryption of B | `LweCiphertext<Vec<u64>>` | `B` |
| `lwe_sum` | Lwe encryption of A plus lwe encryption of B | `LweCiphertext<Vec<u64>>` | `A+B` |
| `lwe_prod` | Lwe encryption of A times cleartext B | `LweCiphertext<Vec<u64>>` | `A*B` |
| `lwe_ms` | The lwe ciphertext after the modswitch part of the PBS ([note](#non-native-encoding)) | `LweCiphertext<Vec<u64>>` | `A` |
| `lwe_ks` | The lwe ciphertext after the keyswitch | `LweCiphertext<Vec<u64>>` | `A` |
| `glwe_after_id_br` | The glwe returned by the application of the identity blind rotation on the mod switched ciphertexts. | `GlweCiphertext<Vec<u64>>` | rot id LUT |
| `lwe_after_id_pbs` | The lwe returned by the application of the sample extract operation on the output of the id blind rotation | `LweCiphertext<Vec<u64>>` | `A` |
| `glwe_after_spec_br` | The glwe returned by the application of the spec blind rotation on the mod switched ciphertexts. | `GlweCiphertext<Vec<u64>>` | rot spec LUT |
| `lwe_after_spec_pbs` | The lwe returned by the application of the sample extract operation on the output of the spec blind rotation | `LweCiphertext<Vec<u64>>` | `spec(A)` |
### Encodings
#### Non native encoding
Warning: TFHE-rs uses a specific encoding for non native (ie: u32, u64) power of two ciphertext modulus. This encoding puts the encoded value in the high bits of the native integer.
For example, the value 37 with a modulus of 64 will be encoded as `0b1001010000000000000000000000000000000000000000000000000000000000`. This matters for the post modswitch lwe ciphertext.
#### Ciphertext modulus
The ciphertext modulus encoding use a specific value for the native modulus: 0. For example, if values are stored on u64 integers, 0 means a ciphertext modulus of 2^64.
## Operations
| name | inputs | outputs |
|-------------------------|-------------------------------------------------------------------|------------------------|
| large secret key gen | PARAMS, RAND_SEED | `large_lwe_secret_key` |
| small secret key gen | PARAMS, RAND_SEED | `small_lwe_secret_key` |
| keyswitch key gen | PARAMS, RAND_SEED, `large_lwe_secret_key`, `small_lwe_secret_key` | `ksk` |
| bootstrap key gen | PARAMS, RAND_SEED, `small_lwe_secret_key`, `large_lwe_secret_key` | `bsk` |
| encryption A | A, `large_lwe_secret_key` | `lwe_a` |
| encryption B | B, `large_lwe_secret_key` | `lwe_b` |
| `E(A)+E(B)` | `lwe_a`, `lwe_b` | `lwe_sum` |
| `E(A)*B` | `lwe_a`, B | `lwe_prod` |
| keyswitch | `lwe_a`, `ksk` | `lwe_ks` |
| modulus switch | `lwe_ks` | `lwe_ms` |
| blind rotation id lut | ID_LUT, `lwe_ms`, `bsk` | `glwe_after_id_br` |
| sample extract id lut | `glwe_after_id_br` | `lwe_after_id_pbs` |
| blind rotation spec lut | SPEC_LUT, `lwe_ms`, `bsk` | `glwe_after_spec_br` |
| sample extract spec lut | `glwe_after_spec_br` | `lwe_after_spec_pbs` |
## Parameters
```rust
const RAND_SEED: u128 = 0x74666865;
const MSG_A: u64 = 4;
const MSG_B: u64 = 3;
const VALID_LWE_DIMENSION: LweDimension = LweDimension(833);
const VALID_GLWE_DIMENSION: GlweDimension = GlweDimension(1);
const VALID_POLYNOMIAL_SIZE: PolynomialSize = PolynomialSize(2048);
const VALID_GAUSSIAN_LWE_NOISE_STDDEV: f64 = 3.6158408373309336e-06;
const VALID_GAUSSIAN_GLWE_NOISE_STDDEV: f64 = 2.845267479601915e-15;
const VALID_PBS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(23);
const VALID_PBS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(1);
const VALID_KS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(3);
const VALID_KS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(5);
const TOY_LWE_DIMENSION: LweDimension = LweDimension(10);
const TOY_GLWE_DIMENSION: GlweDimension = GlweDimension(1);
const TOY_POLYNOMIAL_SIZE: PolynomialSize = PolynomialSize(256);
const TOY_GAUSSIAN_LWE_NOISE_STDDEV: f64 = 0.;
const TOY_GAUSSIAN_GLWE_NOISE_STDDEV: f64 = 0.;
const TOY_PBS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(24);
const TOY_PBS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(1);
const TOY_KS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(37);
const TOY_KS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(1);
const CIPHERTEXT_MODULUS: CiphertextModulus<u64> = CiphertextModulus::new_native();
const MSG_BITS: usize = 4;
const SPEC_LUT: fn(u64) -> u64 = |x| (x * 2) % (1u64 << MSG_BITS);
const ID_LUT: fn(u64) -> u64 = |x| x;
```

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7cc6803f5fbc3d5a1bf597f2b979ce17eecd3d6baca12183dea21022a7b65c52
size 92291

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36c9080b636475fcacca503ce041bbfeee800fd3e1890dee559ea18defff9fe8
size 4679

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2abec9dc5d399ece68ac227f67f7cafcb9a6acac36ed734fe3d5244021eb1cda
size 4679

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:377761beeb4216cf5aa2624a8b64b8259f5a75c32d28e850be8bced3a0cdd6f5
size 25451

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7d5a864d5616a7d8ad50bbf40416e41e6c9b60c546dc14d4aa8fc40a418baa7
size 265

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9eb75bd2993639348a679cf48c06e3c38d1a513f48e5b0ce0047cea8cff6bbc
size 2365

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70f5e5728822de05b49071efb5ec28551b0f5cc87aa709a455d8e7f04b9c96ee
size 2365

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8ee68ed99dd9103fb62b1e2c7c8cf483706ae2071b792d4bd16f9f93f64871f9
size 2365

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eadf2eff35133ffba075df11faecddd6e7af9ddc398011ec4568e5528812b3e2
size 2365

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d6da5baef0e787f6be56e218d8354e26904652602db964844156fdff08350ce6
size 149

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6df98676de04fe54b5ffc2eb30a82ebb706c9d7d5a4e0ed509700fec88761f7
size 143

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:837b3bd3245d4d0534ed255fdef896fb4fa6998a258a14543dfdadd0bfc9b6dd
size 2365

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:699580ca92b9c2f9e1f57fb1e312c9e8cb29714f7acdef9d2ba05f798546751f
size 2365

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee9fcf45f1379ca3a7d7bf2b0e7a1cc920ceb496c0217e8604b0b58d2831749e
size 17

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9afe7019acba5cda926f13e06df9930571611729d2f2e8ce41956e1f5e1db6f
size 61415556

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6e54ab41056984595b077baff70236d934308cf5c0c33b4482fbfb129b3756c6
size 36935

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99a19c5d6d5f4fd81d9164d0ff96719ef362eabda256bce6a55cba6cb69e42bf
size 36935

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e591ab9af1b6a0aede273f9a3abb65a4c387feb5fa06a6959e9314058ca0f7e5
size 76861550

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b7a037b9eaa88d6385167579b93e26a0cb6976d9b8967416fd1173e113bda199
size 2057

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e69d2d2c064fc8c0460b39191ca65338146990349954f5ec5ebd01d93610e7eb
size 18493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:656f0009c7834c5bcb61621e222047516054b9bc5d0593d474ab8f1c086b67a6
size 18493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e44ffa6e5a50a03d32721180a051c8ce62f1791d4853aeaebed0200c183a57cf
size 18493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c806533b325b1009db38be2f9bef5f3b2fad6b77b4c71f2855ccc9d3b4162e98
size 18493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59dba26d457f96478eda130cab5301fce86f23c6a8807de42f2a1e78c4985ca7
size 7558

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa44aea29efd6d9e4d35a21a625d9cba155672e3f7ed3eddee1e211e62ad146b
size 7552

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e76c24b2a0c9a842ad13dda35473c2514f9e7d20983b5ea0759c4521a91626d9
size 18493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f3c40a134623b44779a556212477fea26eaed22450f3b6faeb8721d63699972
size 18493

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c70d1d78cc3760733850a353ace2b9c4705e840141b75841739e90e51247e18
size 842

View File

@@ -0,0 +1,344 @@
use serde::Serialize;
use std::fs::{File, create_dir_all, read_dir, remove_dir_all, remove_file};
use std::path::{Path, PathBuf};
use tfhe::core_crypto::commons::generators::DeterministicSeeder;
use tfhe::core_crypto::commons::math::random::Seed;
use tfhe::core_crypto::prelude::*;
use tfhe_csprng::generators::SoftwareRandomGenerator;
// If you modify the content of these parameters, don't forget to also update `data/README.md`
const RAND_SEED: u128 = 0x74666865;
const MSG_A: u64 = 4;
const MSG_B: u64 = 3;
const VALID_LWE_DIMENSION: LweDimension = LweDimension(833);
const VALID_GLWE_DIMENSION: GlweDimension = GlweDimension(1);
const VALID_POLYNOMIAL_SIZE: PolynomialSize = PolynomialSize(2048);
const VALID_GAUSSIAN_LWE_NOISE_STDDEV: f64 = 3.6158408373309336e-06;
const VALID_GAUSSIAN_GLWE_NOISE_STDDEV: f64 = 2.845267479601915e-15;
const VALID_PBS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(23);
const VALID_PBS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(1);
const VALID_KS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(3);
const VALID_KS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(5);
const TOY_LWE_DIMENSION: LweDimension = LweDimension(10);
const TOY_GLWE_DIMENSION: GlweDimension = GlweDimension(1);
const TOY_POLYNOMIAL_SIZE: PolynomialSize = PolynomialSize(256);
const TOY_GAUSSIAN_LWE_NOISE_STDDEV: f64 = 0.;
const TOY_GAUSSIAN_GLWE_NOISE_STDDEV: f64 = 0.;
const TOY_PBS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(24);
const TOY_PBS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(1);
const TOY_KS_DECOMPOSITION_BASE_LOG: DecompositionBaseLog = DecompositionBaseLog(37);
const TOY_KS_DECOMPOSITION_LEVEL_COUNT: DecompositionLevelCount = DecompositionLevelCount(1);
const ENCODING: Encoding = Encoding {
ciphertext_modulus: CiphertextModulus::new_native(),
msg_bits: 4,
};
const SPEC_LUT: fn(u64) -> u64 = |x| (x * 2) & (1u64 << ENCODING.msg_bits);
const ID_LUT: fn(u64) -> u64 = |x| x;
const DATA_DIR: &str = "./data";
struct Encoding {
ciphertext_modulus: CiphertextModulus<u64>,
msg_bits: usize,
}
impl Encoding {
fn log_delta(&self) -> usize {
self.ciphertext_modulus.into_modulus_log().0 - self.msg_bits - 1
}
const fn msg_modulus(&self) -> usize {
1 << self.msg_bits
}
fn encode(&self, msg: u64) -> Plaintext<u64> {
Plaintext(msg << self.log_delta())
}
fn decode(&self, plaintext: Plaintext<u64>) -> u64 {
let decomposer = SignedDecomposer::new(
DecompositionBaseLog(self.msg_bits + 1),
DecompositionLevelCount(1),
);
let decoded = decomposer.decode_plaintext(plaintext);
decoded.0
}
fn encode_lut(
&self,
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
f: impl Fn(u64) -> u64,
) -> GlweCiphertext<Vec<u64>> {
generate_programmable_bootstrap_glwe_lut(
polynomial_size,
glwe_dimension.to_glwe_size(),
self.msg_modulus(),
self.ciphertext_modulus,
1 << self.log_delta(),
f,
)
}
}
fn modswitched_to_lwe(
modswitched: &LazyStandardModulusSwitchedLweCiphertext<u64, usize, Vec<u64>>,
) -> LweCiphertextOwned<usize> {
let cont = modswitched
.mask()
.chain(std::iter::once(modswitched.body()))
// The coefficients are converted to use the power of two encoding
.map(|coeff| coeff << (usize::BITS as usize - modswitched.log_modulus().0))
.collect();
LweCiphertext::from_container(
cont,
CiphertextModulus::new(1 << modswitched.log_modulus().0),
)
}
fn store_data<Data: Serialize, P: AsRef<Path>>(path: P, data: &Data, name: &str) {
let mut path = path.as_ref().to_path_buf();
path.push(format!("{}.cbor", name));
let mut file = File::create(path).unwrap();
ciborium::ser::into_writer(data, &mut file).unwrap();
}
#[allow(clippy::too_many_arguments)]
fn generate_test_vectors<P: AsRef<Path>>(
path: P,
lwe_dimension: LweDimension,
glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
lwe_noise_stddev: f64,
glwe_noise_stddev: f64,
pbs_decomp_base_log: DecompositionBaseLog,
pbs_decomp_level_count: DecompositionLevelCount,
ks_decomp_base_log: DecompositionBaseLog,
ks_decomp_level_count: DecompositionLevelCount,
encoding: Encoding,
) {
let path = path.as_ref();
create_dir_all(path).unwrap();
let mut secret_generator =
SecretRandomGenerator::<SoftwareRandomGenerator>::new(Seed(RAND_SEED));
let mut encryption_generator = EncryptionRandomGenerator::<SoftwareRandomGenerator>::new(
Seed(RAND_SEED),
&mut DeterministicSeeder::<SoftwareRandomGenerator>::new(Seed(RAND_SEED)),
);
let glwe_secret_key: GlweSecretKey<Vec<u64>> =
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
let large_lwe_secret_key = glwe_secret_key.as_lwe_secret_key();
store_data(path, &large_lwe_secret_key, "large_lwe_secret_key");
let small_lwe_secret_key: LweSecretKey<Vec<u64>> =
LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_generator);
store_data(path, &small_lwe_secret_key, "small_lwe_secret_key");
let lwe_noise_distribution = Gaussian::from_standard_dev(StandardDev(lwe_noise_stddev), 0.);
let glwe_noise_distribution = Gaussian::from_standard_dev(StandardDev(glwe_noise_stddev), 0.);
let plaintext_a = encoding.encode(MSG_A);
let lwe_a = allocate_and_encrypt_new_lwe_ciphertext(
&large_lwe_secret_key,
plaintext_a,
glwe_noise_distribution,
encoding.ciphertext_modulus,
&mut encryption_generator,
);
store_data(path, &lwe_a, "lwe_a");
let plaintext_b = encoding.encode(MSG_B);
let lwe_b = allocate_and_encrypt_new_lwe_ciphertext(
&large_lwe_secret_key,
plaintext_b,
glwe_noise_distribution,
encoding.ciphertext_modulus,
&mut encryption_generator,
);
store_data(path, &lwe_b, "lwe_b");
let mut lwe_sum = LweCiphertext::new(
0u64,
glwe_dimension
.to_equivalent_lwe_dimension(polynomial_size)
.to_lwe_size(),
encoding.ciphertext_modulus,
);
lwe_ciphertext_add(&mut lwe_sum, &lwe_a, &lwe_b);
let decrypted_sum = decrypt_lwe_ciphertext(&large_lwe_secret_key, &lwe_sum);
let res = encoding.decode(decrypted_sum);
assert_eq!(res, MSG_A + MSG_B);
store_data(path, &lwe_sum, "lwe_sum");
let mut lwe_prod = LweCiphertext::new(
0u64,
glwe_dimension
.to_equivalent_lwe_dimension(polynomial_size)
.to_lwe_size(),
encoding.ciphertext_modulus,
);
lwe_ciphertext_cleartext_mul(&mut lwe_prod, &lwe_a, Cleartext(MSG_B));
let decrypted_prod = decrypt_lwe_ciphertext(&large_lwe_secret_key, &lwe_prod);
let res = encoding.decode(decrypted_prod);
assert_eq!(res, MSG_A * MSG_B);
store_data(path, &lwe_prod, "lwe_prod");
let ksk = allocate_and_generate_new_lwe_keyswitch_key(
&large_lwe_secret_key,
&small_lwe_secret_key,
ks_decomp_base_log,
ks_decomp_level_count,
lwe_noise_distribution,
encoding.ciphertext_modulus,
&mut encryption_generator,
);
store_data(path, &ksk, "ksk");
let mut lwe_ks = LweCiphertext::new(
0u64,
lwe_dimension.to_lwe_size(),
encoding.ciphertext_modulus,
);
keyswitch_lwe_ciphertext(&ksk, &lwe_a, &mut lwe_ks);
let decrypted_ks = decrypt_lwe_ciphertext(&small_lwe_secret_key, &lwe_ks);
let res = encoding.decode(decrypted_ks);
assert_eq!(res, MSG_A);
store_data(path, &lwe_ks, "lwe_ks");
let bsk = par_allocate_and_generate_new_lwe_bootstrap_key(
&small_lwe_secret_key,
&glwe_secret_key,
pbs_decomp_base_log,
pbs_decomp_level_count,
glwe_noise_distribution,
encoding.ciphertext_modulus,
&mut encryption_generator,
);
store_data(path, &bsk, "bsk");
let mut fourier_bsk = FourierLweBootstrapKey::new(
bsk.input_lwe_dimension(),
bsk.glwe_size(),
bsk.polynomial_size(),
bsk.decomposition_base_log(),
bsk.decomposition_level_count(),
);
par_convert_standard_lwe_bootstrap_key_to_fourier(&bsk, &mut fourier_bsk);
let lwe_in_ms =
LweCiphertext::from_container(lwe_ks.as_ref().to_vec(), lwe_ks.ciphertext_modulus());
let log_modulus = polynomial_size.to_blind_rotation_input_modulus_log();
let modswitched = lwe_ciphertext_modulus_switch(lwe_in_ms, log_modulus);
let lwe_ms = modswitched_to_lwe(&modswitched);
store_data(path, &lwe_ms, "lwe_ms");
let mut id_lut = encoding.encode_lut(glwe_dimension, polynomial_size, ID_LUT);
blind_rotate_assign(&modswitched, &mut id_lut, &fourier_bsk);
store_data(path, &id_lut, "glwe_after_id_br");
let mut lwe_pbs_id = LweCiphertext::new(
0u64,
glwe_dimension
.to_equivalent_lwe_dimension(polynomial_size)
.to_lwe_size(),
encoding.ciphertext_modulus,
);
extract_lwe_sample_from_glwe_ciphertext(&id_lut, &mut lwe_pbs_id, MonomialDegree(0));
let decrypted_pbs_id = decrypt_lwe_ciphertext(&large_lwe_secret_key, &lwe_pbs_id);
let res = encoding.decode(decrypted_pbs_id);
assert_eq!(res, MSG_A);
store_data(path, &lwe_pbs_id, "lwe_after_id_pbs");
let mut spec_lut = encoding.encode_lut(glwe_dimension, polynomial_size, SPEC_LUT);
blind_rotate_assign(&modswitched, &mut spec_lut, &fourier_bsk);
store_data(path, &spec_lut, "glwe_after_spec_br");
let mut lwe_pbs_spec = LweCiphertext::new(
0u64,
glwe_dimension
.to_equivalent_lwe_dimension(polynomial_size)
.to_lwe_size(),
encoding.ciphertext_modulus,
);
extract_lwe_sample_from_glwe_ciphertext(&spec_lut, &mut lwe_pbs_spec, MonomialDegree(0));
let decrypted_pbs_spec = decrypt_lwe_ciphertext(&large_lwe_secret_key, &lwe_pbs_spec);
let res = encoding.decode(decrypted_pbs_spec);
assert_eq!(res, SPEC_LUT(MSG_A));
store_data(path, &lwe_pbs_spec, "lwe_after_spec_pbs");
}
fn rm_dir_except_readme<P: AsRef<Path>>(dir: P) {
let dir = dir.as_ref();
for entry_result in read_dir(dir).unwrap() {
let entry = entry_result.unwrap();
let path = entry.path();
// Skip the README.md file at the root.
if entry.file_name() == "README.md" {
continue;
}
if path.is_dir() {
remove_dir_all(&path).unwrap();
} else {
remove_file(&path).unwrap();
}
}
}
fn main() {
rm_dir_except_readme(DATA_DIR);
generate_test_vectors(
PathBuf::from(DATA_DIR).join("valid_params_128"),
VALID_LWE_DIMENSION,
VALID_GLWE_DIMENSION,
VALID_POLYNOMIAL_SIZE,
VALID_GAUSSIAN_LWE_NOISE_STDDEV,
VALID_GAUSSIAN_GLWE_NOISE_STDDEV,
VALID_PBS_DECOMPOSITION_BASE_LOG,
VALID_PBS_DECOMPOSITION_LEVEL_COUNT,
VALID_KS_DECOMPOSITION_BASE_LOG,
VALID_KS_DECOMPOSITION_LEVEL_COUNT,
ENCODING,
);
generate_test_vectors(
PathBuf::from(DATA_DIR).join("toy_params"),
TOY_LWE_DIMENSION,
TOY_GLWE_DIMENSION,
TOY_POLYNOMIAL_SIZE,
TOY_GAUSSIAN_LWE_NOISE_STDDEV,
TOY_GAUSSIAN_GLWE_NOISE_STDDEV,
TOY_PBS_DECOMPOSITION_BASE_LOG,
TOY_PBS_DECOMPOSITION_LEVEL_COUNT,
TOY_KS_DECOMPOSITION_BASE_LOG,
TOY_KS_DECOMPOSITION_LEVEL_COUNT,
ENCODING,
);
}

View File

@@ -3,14 +3,14 @@
set -e
if [ $# -lt 1 ]; then
echo "invalid arguments, usage:\n"
echo "$0 <data_path>"
exit 1
echo "invalid arguments, usage:\n"
echo "$0 <data_path>"
exit 1
fi
if ! git lfs env 2>/dev/null >/dev/null; then
echo "git lfs is not installed, please install it and try again"
exit 1
echo "git lfs is not installed, please install it and try again"
exit 1
fi
git lfs pull --include="$1/*" --exclude=""

59
scripts/test_vectors.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# This script generates or checks the SHA256 for the test vectors in `apps/test-vectors`
set -e
MODE="$1"
TARGET_DIR="$2"
CHECKSUM_FILE="checksums.sha256"
if [ -z "$MODE" ] || [ -z "$TARGET_DIR" ]; then
echo "Usage: $0 <mode> <target dir>"
echo "Modes:"
echo " generate Generate the vectors and update the checksum file"
echo " check Generate the vectors and compare their content with the checksum file"
exit 1
fi
if [ ! -d "$TARGET_DIR" ]; then
echo "Error: Directory '$TARGET_DIR' not found."
exit 1
fi
checksum () {
find data -name '*.cbor' -type f -exec sha256sum {} + | sort
}
# Generate the test vectors
echo "Generating test-vectors in $TARGET_DIR..."
cd $TARGET_DIR
cargo run --release
if [ "$MODE" == "generate" ]; then
echo "Generating hashes in $TARGET_DIR/$CHECKSUM_FILE..."
checksum > "$CHECKSUM_FILE"
echo "Done."
elif [ "$MODE" == "check" ]; then
echo "Checking vectors integrity against $TARGET_DIR/$CHECKSUM_FILE..."
if [ ! -f "$CHECKSUM_FILE" ]; then
echo "Error: Checksum file $CHECKSUM_FILE not found."
echo "Run 'generate' mode first."
exit 1
fi
diffs=$(comm -3 checksums.sha256 <(checksum))
if [ -n "$diffs" ]; then
echo "Error: Checksum file and generated vectors do not match."
echo $diffs
exit 1
fi
echo "Done."
else
echo "Error: Invalid mode '$MODE'."
echo "Mode must be 'generate' or 'check'."
exit 1
fi

View File

@@ -3,7 +3,7 @@ use std::collections::HashSet;
use std::io::{Error, ErrorKind};
// TODO use .gitignore or git to resolve ignored files
const DIR_TO_IGNORE: [&str; 2] = [".git", "target"];
const DIR_TO_IGNORE: [&str; 3] = [".git", "target", "apps/test-vectors"];
const FILES_TO_IGNORE: [&str; 9] = [
// This contains fragments of code that are unrelated to TFHE-rs