Compare commits

..

4 Commits

Author SHA1 Message Date
Arthur Meyre
99ded5fc52 chore: bump ntt requirement which should have been 0.7.1 already 2026-04-24 10:43:43 +02:00
David Testé
5fd090db7c chore(docs): update leading-trailing zeros results 2026-04-23 17:11:51 +02:00
Arthur Meyre
61e8dadb19 chore: bump version to 1.6.1 2026-04-23 15:37:15 +02:00
Arthur Meyre
d12584086a chore(hl): export two missing (Compressed)ReRandomizationKey types 2026-04-23 15:37:15 +02:00
29 changed files with 33 additions and 1396 deletions

View File

@@ -27,7 +27,7 @@ members = [
"utils/wasm-par-mq/web_tests",
]
exclude = ["utils/tfhe-lints", "apps/trivium", "apps/princev2"]
exclude = ["utils/tfhe-lints", "apps/trivium"]
[workspace.package]
rust-version = "1.91.1"

View File

@@ -288,7 +288,6 @@ fmt_internal: install_rs_check_toolchain
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt $(FMT_CHECK)
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" -Z unstable-options -C utils/tfhe-lints fmt $(FMT_CHECK)
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" -Z unstable-options -C apps/trivium fmt $(FMT_CHECK)
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" -Z unstable-options -C apps/princev2 fmt $(FMT_CHECK)
for crate in `ls -1 $(BACKWARD_COMPAT_DATA_DIR)/crates/ | grep generate_`; do \
echo "fmt $$crate"; \
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" -Z unstable-options -C $(BACKWARD_COMPAT_DATA_DIR)/crates/$$crate fmt $(FMT_CHECK); \
@@ -514,11 +513,6 @@ clippy_trivium: install_rs_check_toolchain
cd apps/trivium; RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
-p tfhe-trivium -- --no-deps -D warnings
.PHONY: clippy_princev2 # Run clippy lints on PRINCEv2 app
clippy_princev2: install_rs_check_toolchain
cd apps/princev2; RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \
-p tfhe-princev2 -- --no-deps -D warnings
.PHONY: clippy_ws_tests # Run clippy on the workspace level tests
clippy_ws_tests: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --tests \
@@ -613,7 +607,7 @@ clippy_test_vectors: install_rs_check_toolchain
# MAKE SURE TO ALSO ADD IT TO A PCC BATCH BELOW
.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_zk_pok_wasm clippy_trivium clippy_princev2 \
clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core clippy_tfhe_csprng clippy_zk_pok clippy_zk_pok_wasm clippy_trivium \
clippy_versionable clippy_safe_serialize clippy_tfhe_lints clippy_ws_tests clippy_bench clippy_param_dedup \
clippy_test_vectors clippy_backward_compat_data clippy_wasm_par_mq
@@ -2293,7 +2287,6 @@ pcc_batch_6:
$(call run_recipe_with_details,clippy_zk_pok)
$(call run_recipe_with_details,clippy_zk_pok_wasm)
$(call run_recipe_with_details,clippy_trivium)
$(call run_recipe_with_details,clippy_princev2)
$(call run_recipe_with_details,clippy_versionable)
$(call run_recipe_with_details,clippy_safe_serialize)
$(call run_recipe_with_details,clippy_param_dedup)

View File

@@ -1,23 +0,0 @@
[package]
name = "tfhe-princev2"
version = "0.1.0"
edition = "2021"
[dependencies]
rayon = "1.11.0"
tfhe = { path = "../../tfhe", features = ["shortint"] }
[dev-dependencies]
criterion = "0.5.1"
[features]
verbose-timings = []
[profile.release]
lto = "fat"
opt-level = 3
codegen-units = 1
[[bench]]
name = "princev2"
harness = false

View File

@@ -1,79 +0,0 @@
# FHE implementation of PRINCEv2 using TFHE-rs
This crate implements homomorphic encryption and decryption of the PRINCEv2 block cipher [BEK+20] using TFHE-rs's shortint API. It takes FHE ciphertexts representing the plaintext (resp. ciphertext) block and the two halves of the PRINCEv2 key and produces FHE ciphertexts of the encrypted (resp. decrypted) block.
Inputs and outputs encrypt 64-bit integers that are represented as vectors of 2-bit nibbles, most significant nibble first, stacked in the lower part of the FHE message space of each ciphertext.
The cipher itself (a succession of S-box, Linear, Permutation, Xor layers) is evaluated under FHE using the `shortint` API, systematically operating on 4-bit lookup tables. More details on the FHE design can be found in [BJ26, Section 6].
## References
PRINCEv2 is specified in:
> [BEK+20] Dusan Božilov, Maria Eichlseder, Miroslav Kneževic, Baptiste Lambin, Gregor Leander, Thorben Moos, Ventzislav Nikov, Shahram Rasoolzadeh, Yosuke Todo, and Friedrich Wiemer. *PRINCEv2: More security for (almost) no overhead.* In Selected Areas in Cryptography (SAC 2020), volume 12804 of LNCS, pp.483--511, Springer, 2020. DOI:10.1007/978-3-030-81652-0_19.
Test vectors are those of Appendix B of the paper.
More details on the FHE implementation design can be found in Section 6 of:
> [BJ26] Olivier Bernard and Marc Joye. *Hash function constructions from lightweight block ciphers for fully homomorphic encryption*. Cryptology ePrint Archive, ePrint:2026/309, 2026.
## Layout
- `src/u64_conv.rs` — plaintext-side conversions between `u64` and the 32-element 2-bit-nibble vectors used on the FHE side; it exposes `u64_to_vec_u2` and `vec_u2_to_u64` as part of the encoding contract for the underlying plaintexts of the inputs and outputs.
- `src/permute.rs` — generic permutation helper over ciphertext arrays.
- `src/pv2_lut.rs` — precomputed S-box, inverse S-box, M-layer and round-constant lookup tables.
- `src/pv2_cipher.rs` — the homomorphic round functions and the public `pv2_encrypt` / `pv2_decrypt` entry points.
- `tests/pv2_kat.rs` — known-answer tests against the paper vectors.
- `benches/princev2.rs` — benchmarks for a full call of `pv2_encrypt` (`pv2_decrypt` has exactly the same performance characteristics).
## Usage
```rust,no_run
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128;
use tfhe::shortint::prelude::*;
use tfhe_princev2::{pv2_encrypt, u64_to_vec_u2, vec_u2_to_u64};
let (s_key, ev_key) = tfhe::shortint::gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
let encode = |x: u64| -> [Ciphertext; 32] {
let nibbles = u64_to_vec_u2(x);
let v: Vec<_> = nibbles.into_iter().map(|n| s_key.encrypt(n as u64)).collect();
v.try_into().unwrap()
};
let ct_m = encode(0x0123456789abcdef);
let ct_k0 = encode(0x0123456789abcdef);
let ct_k1 = encode(0xfedcba9876543210);
let mut ct_out: [Ciphertext; 32] = std::array::from_fn(|_| ev_key.create_trivial(0));
pv2_encrypt(&ev_key, &mut ct_out, &ct_m, &ct_k0, &ct_k1);
let out_nibbles: [u8; 32] =
std::array::from_fn(|i| s_key.decrypt_message_and_carry(&ct_out[i]) as u8);
assert_eq!(vec_u2_to_u64(out_nibbles), 0x603cd95fa72a8704);
```
## Running tests
```bash
RAYON_NUM_THREADS=64 cargo test --release --test pv2_kat -- --test-threads=1
```
Each KAT should take approximately 5 seconds (resp. 800ms) on 8 cores (resp. 64 cores) on an Amazon AWS hpc7a.96xlarge machine. There are currently 10 KATs (5 for encryption and same for decryption). Optimal timings depend on the hardware but will be structurally better using a number of threads which is a power of 2 up to 64; the best possible latency is obtained through 64 individual threads.
## Optional verbose timings
```bash
RAYON_NUM_THREADS=64 cargo test --release --test pv2_kat --features verbose-timings -- --test-threads=1 --nocapture
```
This times each internal round function call and emits one `eprintln!` per such call.
## Running benchmarks
```bash
RAYON_NUM_THREADS=64 cargo bench --bench princev2
```
Timings obtained on up to 64 cores of an `Amazon AWS hpc7a.96xlarge` machine can also be found in [BJ26, Table 6.1].

View File

@@ -1,86 +0,0 @@
//! Benchmarks for homomorphic PRINCEv2 encryption (and decryption)
//!
//! Times one full call of `pv2_encrypt`, i.e., transciphering one block of 64 bits.
//! Note that decryption `pv2_decrypt` follows exactly the same logic as encryption with different
//! constants, hence it is not benched separately.
use criterion::{criterion_group, criterion_main, Criterion};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128;
use tfhe::shortint::prelude::*;
use tfhe_princev2::{pv2_encrypt, u64_to_vec_u2, vec_u2_to_u64};
// [NB] We don't expect pv2_decrypt() to behave differently from pv2_encrypt()
criterion_group!(pv2_benches, bench_pv2_encrypt);
criterion_main!(pv2_benches);
// KAT structure for Pv2 cipher
struct Pv2Kat {
name: &'static str,
ptxt: u64,
k0: u64,
k1: u64,
ctxt: u64,
}
static PV2_KAT_LN2: Pv2Kat = Pv2Kat {
// ptxt, k0, k1 are the first three u64 words of ln(2) fractional part.
// ctxt was computed with the Sagemaths reference implementation and cross-checked here.
name: "PRINCEv2 KAT from ln(2)",
ptxt: 0xb17217f7d1cf79ab,
k0: 0xc9e3b39803f2f6af,
k1: 0x40f343267298b62d,
ctxt: 0x40ac916b4598216d,
};
/// Encrypt a u64 as 32 ciphertexts, each holding a 2-bit nibble in the low bits of the FHE message
/// space. Most significant bits of the input are at index 0 in the output
fn encrypt_u64_as_vec_u2l(s_key: &ClientKey, x: u64) -> [Ciphertext; 32] {
let x_u2: [u8; 32] = u64_to_vec_u2(x);
let ct: Vec<Ciphertext> = x_u2
.into_iter()
.map(|u2| s_key.encrypt(u2 as u64))
.collect();
ct.try_into().unwrap()
}
/// Reverse of function encrypt_u64_as_vec_u2l()
fn decrypt_vec_u2l_as_u64(s_key: &ClientKey, v: &[Ciphertext; 32]) -> u64 {
let x_u2: [u8; 32] = std::array::from_fn(|n| s_key.decrypt_message_and_carry(&v[n]) as u8);
let x: u64 = vec_u2_to_u64(x_u2);
x
}
/// Run benches for PRINCEv2 transciphering.
fn bench_pv2_encrypt(c: &mut Criterion) {
let (s_key, ev_key): (ClientKey, ServerKey) = // Params: Need 4-bits msg + nu >= 4
tfhe::shortint::gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
// Encryptions of inputs (k0,k1,m)
let ct_k0: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, PV2_KAT_LN2.k0);
let ct_k1: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, PV2_KAT_LN2.k1);
let ct_m: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, PV2_KAT_LN2.ptxt);
// PRINCEv2 Enc in FHE
let mut ct_out: [Ciphertext; 32] = std::array::from_fn(|_| ev_key.create_trivial(0)); // [NB] shortint::create_trivial() vs boolean::trivial_encrypt()
c.bench_function("PRINCEv2 Trans-Encryption of one message block", |b| {
b.iter(|| {
pv2_encrypt(&ev_key, &mut ct_out, &ct_m, &ct_k0, &ct_k1);
});
});
// Testing the (de-)encrypted result
let pt_out: u64 = decrypt_vec_u2l_as_u64(&s_key, &ct_out);
assert_eq!(
pt_out,
PV2_KAT_LN2.ctxt,
"{} failed: ptxt={:#018x}, k0={:#018x}, k1={:#018x}, expected={:#018x}, got={:#018x}",
PV2_KAT_LN2.name,
PV2_KAT_LN2.ptxt,
PV2_KAT_LN2.k0,
PV2_KAT_LN2.k1,
PV2_KAT_LN2.ctxt,
pt_out
);
}

View File

@@ -1,9 +0,0 @@
// Pure Rust Helpers
mod u64_conv;
pub use u64_conv::{u64_to_vec_u2, vec_u2_to_u64}; // For tests, part of the encoding contract for pv2_encrypt()/pv2_decrypt()
mod permute;
// Cipher internals: pre-computed constants, s-box and perms
mod pv2_cipher;
mod pv2_lut;
pub use pv2_cipher::{pv2_decrypt, pv2_encrypt};

View File

@@ -1,68 +0,0 @@
/*
* Apply Permutations on arrays
* --------------------------------------------------------------------------------------------- */
pub fn apply_perm_assign<T>(list: &mut [T], order: &[usize]) {
assert_eq!(list.len(), order.len());
let n: usize = order.len();
let mut done: Vec<bool> = vec![false; n];
for i in 0..n {
// Do not cycle multiple times
if done[i] {
continue;
}
let mut from = i;
let mut to = order[i];
// Cycle always of length < n
while to != i {
list.swap(from, to);
done[from] = true;
from = to;
to = order[to];
}
done[from] = true;
}
}
#[test]
fn test_apply_perm_assign() {
let perm: [usize; 16] = [
0x0, 0x5, 0xa, 0xf, 0x4, 0x9, 0xe, 0x3, 0x8, 0xd, 0x2, 0x7, 0xc, 0x1, 0x6, 0xb,
];
let mut list: [u8; 16] = [
0x8, 0x3, 0x2, 0xb, 0xd, 0x4, 0x6, 0xf, 0x1, 0x0, 0x8, 0xe, 0x7, 0x8, 0x5, 0x7,
];
let exp_res: [u8; 16] = [
0x8, 0x4, 0x8, 0x7, 0xd, 0x0, 0x5, 0xb, 0x1, 0x8, 0x2, 0xf, 0x7, 0x3, 0x6, 0xe,
];
apply_perm_assign(&mut list, &perm);
assert_eq!(exp_res, list);
#[rustfmt::skip]
let perm: [usize; 64] = [ // FHE_MP_PERM_FW
0x00, 0x05, 0x0a, 0x0f, 0x12, 0x17, 0x18, 0x1d, 0x21, 0x26, 0x2b, 0x2c, 0x31, 0x36, 0x3b, 0x3c,
0x13, 0x14, 0x19, 0x1e, 0x22, 0x27, 0x28, 0x2d, 0x32, 0x37, 0x38, 0x3d, 0x01, 0x06, 0x0b, 0x0c,
0x23, 0x24, 0x29, 0x2e, 0x33, 0x34, 0x39, 0x3e, 0x02, 0x07, 0x08, 0x0d, 0x10, 0x15, 0x1a, 0x1f,
0x30, 0x35, 0x3a, 0x3f, 0x03, 0x04, 0x09, 0x0e, 0x11, 0x16, 0x1b, 0x1c, 0x20, 0x25, 0x2a, 0x2f,
];
#[rustfmt::skip]
let mut list: [u8; 64] = [ // Random
0xc2, 0x0d, 0x97, 0xd6, 0xb0, 0x79, 0x1b, 0x43, 0xcd, 0x03, 0x33, 0xfe, 0x4b, 0x1c, 0x7f, 0xa9,
0xc0, 0xc2, 0xa7, 0x17, 0x88, 0xbf, 0xa6, 0x49, 0x5d, 0xcd, 0x11, 0xee, 0xdc, 0xc4, 0x17, 0x90,
0x12, 0x7e, 0x0d, 0xb0, 0x1f, 0x58, 0xf5, 0xf4, 0x9f, 0xcc, 0xdd, 0xca, 0x49, 0x5a, 0x0e, 0xd2,
0xf6, 0x37, 0xe9, 0x40, 0x6d, 0x56, 0x79, 0x53, 0xd6, 0x63, 0x6f, 0x8a, 0xf5, 0xaa, 0x5b, 0x08,
];
#[rustfmt::skip]
let exp_res: [u8; 64] = [
0xc2, 0x79, 0x33, 0xa9, 0xa7, 0x49, 0x5d, 0xc4, 0x7e, 0xf5, 0xca, 0x49, 0x37, 0x79, 0x8a, 0xf5,
0x17, 0x88, 0xcd, 0x17, 0x0d, 0xf4, 0x9f, 0x5a, 0xe9, 0x53, 0xd6, 0xaa, 0x0d, 0x1b, 0xfe, 0x4b,
0xb0, 0x1f, 0xcc, 0x0e, 0x40, 0x6d, 0x63, 0x5b, 0x97, 0x43, 0xcd, 0x1c, 0xc0, 0xbf, 0x11, 0x90,
0xf6, 0x56, 0x6f, 0x08, 0xd6, 0xb0, 0x03, 0x7f, 0xc2, 0xa6, 0xee, 0xdc, 0x12, 0x58, 0xdd, 0xd2,
];
apply_perm_assign(&mut list, &perm);
assert_eq!(exp_res, list);
}

View File

@@ -1,537 +0,0 @@
use rayon::prelude::*;
use tfhe::shortint::prelude::*;
use crate::permute; // permute/shuffle/swap arrays
use crate::pv2_lut; // fhe luts and constants for prince v2
/* Macro to monitor individual functions timings (feature related: "verbose-timings") */
macro_rules! monitor {
($fn:ident($( $a:expr ), *)) => {
#[cfg(feature = "verbose-timings")]
let t0 = std::time::Instant::now();
$fn($( $a), *);
#[cfg(feature = "verbose-timings")]
eprintln!("{}:\t{:.4?}", stringify!($fn), t0.elapsed())
}
}
/* out_u4 = (in_u2q xor ct_k) as vec_u4
* [Parallel:(32)/32/(16)] XOR stage -> u4 */
fn pv2_xor_to_u4(
ev_key: &ServerKey,
out_u4: &mut [Ciphertext; 16],
in_u2q: &[Ciphertext; 32],
ct_k: &[Ciphertext; 32],
) {
// xor alternatively to pair of high/low bits
let zlut_xor_fw = [
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_FW[0][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_FW[1][x as usize] as u64),
];
/* "Bivariate" xor ------------------------------------------------------------------
* Sum in_u2q + ct_k, apply xor LUT to high or low bit */
/* [Sequential]
let mut ct_hl: [Ciphertext; 32] = std::array::from_fn(|n| ev_key.unchecked_add(&in_u2q[n], &ct_k[n]));
for n in 0..32 {
ev_key.apply_lookup_table_assign(&mut ct_hl[n], &zlut_xor_fw[n & 0x1]);
}
// */
//* [Parallel:32]
let ct_hl: [Ciphertext; 32] = (0..32)
.into_par_iter()
.map(|n| {
let both_n: Ciphertext = ev_key.unchecked_add(&in_u2q[n], &ct_k[n]);
ev_key.apply_lookup_table(&both_n, &zlut_xor_fw[n & 0x1]) // Combined version faster?
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
// */
// [Parallel:16] Sum by pairs
/* (*out_u4) = (0..16).into_par_iter().map(|w| {
ev_key.unchecked_add(&ct_hl[2*w], &ct_hl[2*w+1])
}).collect::<Vec<_>>().try_into().unwrap();*/
for w in 0..16 {
out_u4[w] = ev_key.unchecked_add(&ct_hl[2 * w], &ct_hl[2 * w + 1]);
}
}
/* out_b = (in_u2q xor ct_k) as vec_b
* [Parallel:(32)/64] -> drifted bits */
fn pv2_xor_to_b(
ev_key: &ServerKey,
out_b: &mut [Ciphertext; 64],
in_u2q: &[Ciphertext; 32],
ct_k: &[Ciphertext; 32],
) {
let zlut_xor_bh = [
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BH[0][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BH[1][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BH[2][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BH[3][x as usize] as u64),
];
let zlut_xor_bl = [
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BL[0][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BL[1][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BL[2][x as usize] as u64),
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_BL[3][x as usize] as u64),
];
// [Parallel:32] Sum in_u2q + ct_k --> could stay as iter? and assign in the following loop
let ct_hl: [Ciphertext; 32] =
std::array::from_fn(|n| ev_key.unchecked_add(&in_u2q[n], &ct_k[n]));
// Apply xor (incl. bit_extract) luts on each nibble
/* [Sequential]
for w in 0..16 {
let b_pos = w & 0x3; // w mod 4 (b_pos:0123_b, so b_pos=0 is for (b << 3))
out_b[4*w] = ev_key.apply_lookup_table(&ct_hl[2*w], &zlut_xor_bh[b_pos]);
out_b[4*w+1] = ev_key.apply_lookup_table(&ct_hl[2*w], &zlut_xor_bl[b_pos]);
out_b[4*w+2] = ev_key.apply_lookup_table(&ct_hl[2*w+1], &zlut_xor_bh[b_pos]);
out_b[4*w+3] = ev_key.apply_lookup_table(&ct_hl[2*w+1], &zlut_xor_bl[b_pos]);
} // */
//* [Parallel:64] Apply xor (incl. bit_extract) luts on each nibble
(*out_b) = (0..64)
.into_par_iter()
.map(|idx| {
let n: usize = idx >> 1; // 2*w or 2*w+1
let w: usize = idx >> 2;
let b_pos: usize = w & 0x3;
let zlut_bhl_pos = if (idx & 0x1) == 1 {
&zlut_xor_bl[b_pos]
} else {
&zlut_xor_bh[b_pos]
};
ev_key.apply_lookup_table(&ct_hl[n], zlut_bhl_pos)
})
.collect::<Vec<_>>()
.try_into()
.unwrap(); // */
}
// [Parallel:(32)/32]
fn pv2_xor_to_u2(
ev_key: &ServerKey,
out_u2: &mut [Ciphertext; 32],
in_u2q: &[Ciphertext; 32],
ct_k: &[Ciphertext; 32],
) {
let zlut_xor =
ev_key.generate_lookup_table(|x: u64| pv2_lut::PV2_XOR_TO_LOW[x as usize] as u64);
/* [Sequential]
for n in 0..32 {
out_u2[n] = ev_key.unchecked_add(&in_u2q[n], &ct_k[n]);
ev_key.apply_lookup_table_assign(&mut out_u2[n], &zlut_xor);
} // */
// [Parallel:32] Apply xor luts on each nibble
//* [Parallel:32]
(*out_u2) = (0..32)
.into_par_iter()
.map(|n| {
let both_n: Ciphertext = ev_key.unchecked_add(&in_u2q[n], &ct_k[n]);
ev_key.apply_lookup_table(&both_n, &zlut_xor)
})
.collect::<Vec<_>>()
.try_into()
.unwrap(); // */
}
// [Parallel:64/(32/16)/64/(32)] Fw Round
// Forward round receives full 4-bit nibbles (16) and returns 2-bit nibbles (32) packed on high bits
fn pv2_fw_round(
ev_key: &ServerKey,
out_u2q: &mut [Ciphertext; 32], // out: 2-bits (high)
in_u4: &[Ciphertext; 16], // in: 4-bits (full)
zlut: &[[u8; 16]; 16],
) {
/* S-Boxes ------------------------------------------------------------------------------------
* . each 4-bit nibbles requires 4 applications of (same LUT + Bit extraction)
* . extracted bits for word w go at position 3-w mod 4 (w=0 --> b000, w=1 --> 0b00, etc) */
/* [Sequential]
let mut ct_tmp: [Ciphertext; 64] = std::array::from_fn(|_| ev_key.create_trivial(0));
for w in 0..16 {
for b in 0..4 { // use apply_many_lookup_tables ?
let zlut_b = ev_key.generate_lookup_table(
|x:u64| (((zlut[w][x as usize] >> (3-b)) & 0x1) << ((3-w) % 4)) as u64
);
ct_tmp[b + 4*w] = ev_key.apply_lookup_table(&in_u4[w], &zlut_b);
}
} // */
/* [Sequential::array]
let mut ct_tmp: [Ciphertext; 64] = std::array::from_fn(|idx| {
let w: usize = idx >> 2;
let b: usize = idx & 0x3;
let zlut_b = ev_key.generate_lookup_table(
|x:u64| (((zlut[w][x as usize] >> (3-b)) & 0x1) << ((3-w) % 4)) as u64
);
ev_key.apply_lookup_table(&in_u4[w], &zlut_b) // ct_tmp[idx]
}); // */
//* [Parallel:64]
let ct_tmp: [Ciphertext; 64] = (0..64)
.into_par_iter()
.map(|idx| {
// idx = 4*w + b
let w: usize = idx >> 2;
let b: usize = idx & 0x3;
let zlut_b = ev_key.generate_lookup_table(|x: u64| {
// [Nb] w=0..15
(((zlut[w][x as usize] >> (3 - b)) & 0x1) << (3 - (w % 4))) as u64
});
ev_key.apply_lookup_table(&in_u4[w], &zlut_b)
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
// */
/* Bridging Sbox --> MLayer ----------------------------------------------------------
* So as to obtain 4-bit enc nibbles with: 048c, 159d, etc */
// TODO(?): [Parallel:32/16]
for w in 0..16 {
// this uses u2q for some u4 ahead of time (as temporary holder)
let oo: usize = 16 * (w / 4) + (w % 4);
out_u2q[w] = ev_key.unchecked_add(&ct_tmp[oo], &ct_tmp[oo + 4]);
out_u2q[w + 1] = ev_key.unchecked_add(&ct_tmp[oo + 8], &ct_tmp[oo + 12]);
out_u2q[w] = ev_key.unchecked_add(&out_u2q[w], &out_u2q[w + 1]);
}
/* M-layer: Apply exor matrices ------------------------------------------------------ */
/* [Sequential]
for w in 0..16 {
for b in 0..4 {
let zlut_ex = ev_key.generate_lookup_table(
|x:u64| pv2_lut::PV2_EXOR_FW[w % 2][b][x as usize] as u64
);
ct_tmp[b + 4*w] = ev_key.apply_lookup_table(&out_u2q[w], &zlut_ex);
}
} // */
//* [Parallel:64]
let mut ct_tmp: [Ciphertext; 64] = (0..64)
.into_par_iter()
.map(|idx| {
// idx = 4*w + b
let w: usize = idx >> 2;
let b: usize = idx & 0x3;
let zlut_ex = ev_key
.generate_lookup_table(|x: u64| pv2_lut::PV2_EXOR_FW[w % 2][b][x as usize] as u64);
ev_key.apply_lookup_table(&out_u2q[w], &zlut_ex)
})
.collect::<Vec<_>>()
.try_into()
.unwrap(); // */
// Apply Fhe Perm permutation + Permutation Layer
// --> Directly assign correctly in above loop? as ct_tmp[INV_FHE_MP_PERM_FW[b + 4*w]] = ...
permute::apply_perm_assign(&mut ct_tmp, &pv2_lut::FHE_MP_PERM_FW);
/* Bridging M-Layer --> Xor --------------------------------------------------------- */
// [Parallel:32] Combine pairs
for n in 0..32 {
out_u2q[n] = ev_key.unchecked_add(&ct_tmp[2 * n], &ct_tmp[2 * n + 1]);
}
}
// [Parallel:64/(32)/64/(32/16)/64/(32)/32/(32/16)/32]
fn pv2_mid_round(
ev_key: &ServerKey,
out_u2q: &mut [Ciphertext; 32], // out: 2-bits (high)
in_u4: &[Ciphertext; 16], // in: 4-bits (full)
ct_k_fst: &[Ciphertext; 32],
ct_k_scd: &[Ciphertext; 32],
zlut_fst: &[[u8; 16]; 16],
zlut_scd: &[[u8; 16]; 16],
) {
/* S-Boxes ------------------------------------------------------------------------------------
* /!\ output for xor */
/* [Sequential]
for w in 0..16 {
for b in 0..2 {
let zlut_u2q = ev_key.generate_lookup_table(
|x:u64| (((pv2_lut::PV2_5_S_M[w][x as usize] >> (2-2*b)) & 0x3) << 2) as u64
);
out_u2q[b + 2*w] = ev_key.apply_lookup_table(&in_u4[w], &zlut_u2q);
}
} // */
//* [Parallel:64]
(*out_u2q) = (0..32)
.into_par_iter()
.map(|n| {
let w: usize = n >> 1;
let b: usize = n & 0x1;
let zlut_u2q = ev_key.generate_lookup_table(|x: u64| {
(((zlut_fst[w][x as usize] >> (2 - 2 * b)) & 0x3) << 2) as u64
});
ev_key.apply_lookup_table(&in_u4[w], &zlut_u2q)
})
.collect::<Vec<_>>()
.try_into()
.unwrap(); // */
/* XOR K0 [Parallel:(32)/64] --------------------------------------------------------- */
let mut ct_tmp_b: [Ciphertext; 64] = std::array::from_fn(|_| ev_key.create_trivial(0));
pv2_xor_to_b(ev_key, &mut ct_tmp_b, out_u2q, ct_k_fst);
/* Bridging to M-Layer --------------------------------------------------------------- */
// [Parallel:32/16] Comb sum (048c,...)
let mut ct_tmp_u4: [Ciphertext; 16] = std::array::from_fn(|_| ev_key.create_trivial(0));
for w in 0..16 {
// mm, use u2q for some u4 ahead of time
let oo: usize = 16 * (w / 4) + (w % 4);
out_u2q[w] = ev_key.unchecked_add(&ct_tmp_b[oo], &ct_tmp_b[oo + 4]);
out_u2q[w + 1] = ev_key.unchecked_add(&ct_tmp_b[oo + 8], &ct_tmp_b[oo + 12]);
ct_tmp_u4[w] = ev_key.unchecked_add(&out_u2q[w], &out_u2q[w + 1]);
}
/* M-layer: Apply exor matrices ------------------------------------------------------ */
/* [Sequential]
for w in 0..16 {
for b in 0..4 {
let zlut_ex = ev_key.generate_lookup_table(
|x:u64| pv2_lut::PV2_EXOR_FW[w % 2][b][x as usize] as u64
);
ct_tmp_b[b + 4*w] = ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_ex);
}
} // */
//* [Parallel:64]
ct_tmp_b = (0..64)
.into_par_iter()
.map(|idx| {
// idx = 4*w + b
let w: usize = idx >> 2;
let b: usize = idx & 0x3;
let zlut_ex = ev_key
.generate_lookup_table(|x: u64| pv2_lut::PV2_EXOR_FW[w % 2][b][x as usize] as u64);
ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_ex)
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
// */
// Apply Fhe Perm permutation
permute::apply_perm_assign(&mut ct_tmp_b, &pv2_lut::FHE_M_PERM);
/* Bridging M-Layer --> Xor --------------------------------------------------------- */
// [Parallel:32] Combine pairs
for n in 0..32 {
out_u2q[n] = ev_key.unchecked_add(&ct_tmp_b[2 * n], &ct_tmp_b[2 * n + 1]);
}
/* XOR k1 [Parallel:(32)/32/(16)] --------------------------------------------------- */
pv2_xor_to_u4(ev_key, &mut ct_tmp_u4, out_u2q, ct_k_scd);
/* S-Boxes ------------------------------------------------------------------------------------
* . output 2,2 bits on position (32)00 */
/* [Sequential]
for w in 0..16 {
for b in 0..2 {
let zlut_u2q = ev_key.generate_lookup_table(
|x:u64| (((pv2_lut::PV2_0_IS_0[w][x as usize] >> (2-2*b)) & 0x3) << 2) as u64
);
out_u2q[b + 2*w] = ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_u2q);
}
}
// */
//* [Parallel:32]
(*out_u2q) = (0..32)
.into_par_iter()
.map(|n| {
let w: usize = n >> 1;
let b: usize = n & 0x1;
let zlut_u2q = ev_key.generate_lookup_table(|x: u64| {
(((zlut_scd[w][x as usize] >> (2 - 2 * b)) & 0x3) << 2) as u64
});
ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_u2q)
})
.collect::<Vec<_>>()
.try_into()
.unwrap(); // */
}
// [Parallel:(32/16)/64/(32/16)/32]
fn pv2_bw_round(
ev_key: &ServerKey,
out_u2q: &mut [Ciphertext; 32], // out: 2-bits (high)
in_b: &[Ciphertext; 64], // in: 1-bits (<< w%4 = 333322221111000033...)
zlut: &[[u8; 16]; 16],
) {
let mut ct_tmp_u4: [Ciphertext; 16] = std::array::from_fn(|_| ev_key.create_trivial(0)); // ...
// iPerm + M-Layer
// [Parallel:32/16] Combined iPerm + comb sum (048c,etc)
for w in 0..16 {
let idx: [usize; 4] =
std::array::from_fn(|b| (w & 0x3) + 4 * pv2_lut::IPERM[4 * (w >> 2) + b]);
out_u2q[2 * w] = ev_key.unchecked_add(&in_b[idx[0]], &in_b[idx[1]]);
out_u2q[2 * w + 1] = ev_key.unchecked_add(&in_b[idx[2]], &in_b[idx[3]]);
ct_tmp_u4[w] = ev_key.unchecked_add(&out_u2q[2 * w], &out_u2q[2 * w + 1]);
}
/* M-layer: Apply exor matrices ------------------------------------------------------ */
/* [Sequential]
let mut ct_tmp_b: [Ciphertext; 64] = std::array::from_fn(|_| ev_key.create_trivial(0));
for w in 0..16 {
for b in 0..4 {
let zlut_ex = ev_key.generate_lookup_table(
|x:u64| pv2_lut::PV2_EXOR_BW[w % 4][b][x as usize] as u64
);
ct_tmp_b[b + 4*w] = ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_ex);
}
} // */
//* [Parallel:64]
let mut ct_tmp_b: [Ciphertext; 64] = (0..64)
.into_par_iter()
.map(|idx| {
// idx = 4*w + b
let w: usize = idx >> 2;
let b: usize = idx & 0x3;
let zlut_ex = ev_key
.generate_lookup_table(|x: u64| pv2_lut::PV2_EXOR_BW[w % 4][b][x as usize] as u64);
ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_ex)
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
// */
// FHE Perm permutation
permute::apply_perm_assign(&mut ct_tmp_b, &pv2_lut::FHE_M_PERM);
/* Bridging MLayer --> SBox ----------------------------------------------------------- */
// [Parallel:32/16] Combine to u4 = sum[4*i:4*i+4] for i in range(16)
for w in 0..16 {
out_u2q[2 * w] = ev_key.unchecked_add(&ct_tmp_b[4 * w], &ct_tmp_b[4 * w + 1]);
out_u2q[2 * w + 1] = ev_key.unchecked_add(&ct_tmp_b[4 * w + 2], &ct_tmp_b[4 * w + 3]);
ct_tmp_u4[w] = ev_key.unchecked_add(&out_u2q[2 * w], &out_u2q[2 * w + 1]);
}
/* S-Boxes ------------------------------------------------------------------------------------
* . output 2,2 bits on position (32)00 */
/* [Sequential]
for w in 0..16 {
for b in 0..2 {
let zlut_u2q = ev_key.generate_lookup_table(
|x:u64| (((zlut[w][x as usize] >> (2-2*b)) & 0x3) << 2) as u64
);
out_u2q[b + 2*w] = ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_u2q);
}
}
// */
//* [Parallel:32]
(*out_u2q) = (0..32)
.into_par_iter()
.map(|n| {
let w: usize = n >> 1;
let b: usize = n & 0x1;
let zlut_u2q = ev_key.generate_lookup_table(|x: u64| {
(((zlut[w][x as usize] >> (2 - 2 * b)) & 0x3) << 2) as u64
});
ev_key.apply_lookup_table(&ct_tmp_u4[w], &zlut_u2q)
})
.collect::<Vec<_>>()
.try_into()
.unwrap(); // */
}
/* Encryption -----------------------------------------------------------------------------------
* (Whitening + Fw Rounds + Mid Round + Bw Rounds + Whitening)
*/
#[rustfmt::skip] // [skip] Each of 22 monitor! calls get split on 5 lines which destroys readability
pub fn pv2_encrypt(
ev_key: &ServerKey,
ct_enc: &mut [Ciphertext; 32],
ct_m: &[Ciphertext; 32],
ct_k0: &[Ciphertext; 32],
ct_k1: &[Ciphertext; 32],
) {
// Work buffers: u4, u2q, b (depending on the inner nibbles format, u2q = u2 <<2)
let mut ct_u4: [Ciphertext; 16] = std::array::from_fn(|_| ev_key.create_trivial(0));
let mut ct_b: [Ciphertext; 64] = std::array::from_fn(|_| ev_key.create_trivial(0));
// [Parallel] + Init: ct_m << 2
let mut ct_u2q: [Ciphertext; 32] =
std::array::from_fn(|n| ev_key.unchecked_scalar_mul(&ct_m[n], 4));
// Whitening
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k0));
// Forward rounds
//*
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_0_S_0));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k1));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_1_S_2));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k0));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_0_S_0));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k1));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_3_S_4));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k0));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_0_S_0));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k1)); // */
// Middle round
//*
monitor!(pv2_mid_round(ev_key, &mut ct_u2q, &ct_u4,
ct_k0, ct_k1, &pv2_lut::PV2_5_S_M, &pv2_lut::PV2_0_IS_0)); // */
// Backward rounds
//*
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k0));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_6_IS_7));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k1));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_0_IS_0));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k0));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_8_IS_9));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k1));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_0_IS_0));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k0));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_A_IS_B));
// Last Xor to u2l
monitor!(pv2_xor_to_u2(ev_key, ct_enc, &ct_u2q, ct_k1)); // */
}
/* Decryption -----------------------------------------------------------------------------------
* Inverse of pv2_encrypt().
*/
#[rustfmt::skip] // [skip] Each of 22 monitor! calls get split on 5 lines which destroys readability
pub fn pv2_decrypt(
ev_key: &ServerKey,
ct_dec: &mut [Ciphertext; 32],
ct_c: &[Ciphertext; 32],
ct_k0: &[Ciphertext; 32],
ct_k1: &[Ciphertext; 32],
) {
// Work buffers: u4, u2q, b (depending on the inner nibbles format, u2q = u2 <<2)
let mut ct_u4: [Ciphertext; 16] = std::array::from_fn(|_| ev_key.create_trivial(0));
let mut ct_b: [Ciphertext; 64] = std::array::from_fn(|_| ev_key.create_trivial(0));
// [Parallel] + Init: ct_m << 2
let mut ct_u2q: [Ciphertext; 32] =
std::array::from_fn(|n| ev_key.unchecked_scalar_mul(&ct_c[n], 4));
// Whitening
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k1));
// Forward rounds
//*
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_B_S_A));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k0));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_0_S_0));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k1));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_9_S_8));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k0));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_0_S_0));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k1));
monitor!(pv2_fw_round(ev_key, &mut ct_u2q, &ct_u4, &pv2_lut::PV2_7_S_6));
monitor!(pv2_xor_to_u4(ev_key, &mut ct_u4, &ct_u2q, ct_k0)); // */
// Middle round
//*
monitor!(pv2_mid_round(ev_key, &mut ct_u2q, &ct_u4,
ct_k1, ct_k0, &pv2_lut::PV2_0_S_0, &pv2_lut::PV2_M_IS_5)); // */
// Backward rounds
//*
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k1));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_0_IS_0));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k0));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_4_IS_3));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k1));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_0_IS_0));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k0));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_2_IS_1));
monitor!(pv2_xor_to_b(ev_key, &mut ct_b, &ct_u2q, ct_k1));
monitor!(pv2_bw_round(ev_key, &mut ct_u2q, &ct_b, &pv2_lut::PV2_0_IS_0));
// Last Xor to u2l
monitor!(pv2_xor_to_u2(ev_key, ct_dec, &ct_u2q, ct_k0)); // */
}

View File

@@ -1,331 +0,0 @@
/*
* Prince v2 constant definitions and Look-up tables for FHE
* --------------------------------------------------------------------------------- */
use crate::u64_conv;
/* Permutations -------------------------------------------------------------------- */
static PERM: [usize; 64 / 4] = [
// Prince permutation layer on nibbles
0x0, 0x5, 0xa, 0xf, 0x4, 0x9, 0xe, 0x3, 0x8, 0xd, 0x2, 0x7, 0xc, 0x1, 0x6, 0xb,
];
pub static IPERM: [usize; 64 / 4] = [
// Prince inverse permutation on nibbles
0x0, 0xd, 0xa, 0x7, 0x4, 0x1, 0xe, 0xb, 0x8, 0x5, 0x2, 0xf, 0xc, 0x9, 0x6, 0x3,
];
// Permutation to apply on 16-bits nibbles after M0 if computed as exor( 0123 )
// ---> bits 0c84,51d9,a62e,fb73
// ---> TODO: put Perm in cycle notation so as to use swaps
// (u16 bits) 0123...def (as an array of bits from msb to lsb)
static FHE_M0_PERM: [usize; 16] = [
0x0, 0x5, 0xa, 0xf, 0x3, 0x4, 0x9, 0xe, 0x2, 0x7, 0x8, 0xd, 0x1, 0x6, 0xb, 0xc,
];
// Permutation to apply on 16-bits nibbles after M1 if computed as exor( 0123 )
// --> bits c840,1d95,62ea,b73f
static FHE_M1_PERM: [usize; 16] = [
0x3, 0x4, 0x9, 0xe, 0x2, 0x7, 0x8, 0xd, 0x1, 0x6, 0xb, 0xc, 0x0, 0x5, 0xa, 0xf,
];
// Combined overall bits permutation: (p0 | p1 | p1 | p0) with indexes 0..63
// FHE_M_PERM = sum(( [_c + _n*16 for _c in _perm] for _n,_perm
// in enumerate([FHE_M0_PERM,FHE_M1_PERM,FHE_M1_PERM,FHE_M0_PERM]) ), []);
pub static FHE_M_PERM: [usize; 64] = {
let mut n: usize = 0;
let mut m_perm: [usize; 64] = [0; 64];
while n < 4 {
let mut p_idx: usize = 0;
while p_idx < 16 {
m_perm[p_idx + n * 16] = n * 16
+ match n {
0 | 3 => FHE_M0_PERM[p_idx],
1 | 2 => FHE_M1_PERM[p_idx],
_ => unreachable!(),
};
p_idx += 1;
}
n += 1;
}
m_perm
};
// Combined with Permutation layer (fw)
// = [ fhe_M_Perm[ 4*Perm[_i >> 2] + (_i & 0x3) ] for _i in range(64) ]
pub static FHE_MP_PERM_FW: [usize; 64] = {
let mut b: usize = 0;
let mut m_perm: [usize; 64] = [0; 64];
while b < 64 {
// Unnatural, but just to see the same structure as above
m_perm[b] = FHE_M_PERM[(PERM[b >> 2] << 2) + (b & 0x3)];
b += 1;
}
m_perm
};
/* Round constants ----------------------------------------------------------------- */
const PRINCE_NRND: usize = 12; // Number of rounds (more precisely, nb of round constants / non-linear layers)
static _RC_ALPHA: u64 = 0xc0ac29b7c97c50dd; // see paper about symmetry of RC
static _RC_BETA: u64 = 0x3f84d5b5b5470917; // see paper about symmetry of RC_V2
#[rustfmt::skip]
static RC_V2: [u64; PRINCE_NRND] = [
0x0000000000000000, 0x13198a2e03707344, 0xa4093822299f31d0, 0x082efa98ec4e6c89,
0x452821e638d01377, 0xbe5466cf34e90c6c, 0x7ef84f78fd955cb1, 0x7aacf4538d971a60,
0xc882d32f25323c54, 0x9b8ded979cd838c7, 0xd3b5a399ca0c2399, 0x3f84d5b5b5470917,
];
#[rustfmt::skip]
static RC_V2_IP_IM: [u64; PRINCE_NRND] = [ // iM . iP (RC) [from sage script]
0x0000000000000000, 0x90ecdeb7cb7fc1ce, 0x81b2cb20a82a2928, 0x480cdfa91d749037,
0xcb1a13467044d772, 0x9e8995b07a988c08, 0xe70338c395311a6a, 0x60dc22bf6e681c08,
0x318672daf2dd0655, 0x2a74fad9b606e252, 0xe96673c424d657ac, 0xabc631f91e2ccb7a,
];
static RC_BETA_IM: u64 = 0x42f93b79daa0eea5; // iM (RC_BETA) [from sage script]
// Decomposed versions
static ZRC_V2: [[u8; 64 / 4]; PRINCE_NRND] = array_u64_to_vec_u4(RC_V2);
static ZRC_V2_IP_IM: [[u8; 64 / 4]; PRINCE_NRND] = array_u64_to_vec_u4(RC_V2_IP_IM);
static ZRC_BETA_IM: [u8; 64 / 4] = u64_conv::u64_to_vec_u4(RC_BETA_IM);
// Emulating map on const for u64_to_vec_u4
pub const fn array_u64_to_vec_u4<const N: usize>(tab: [u64; N]) -> [[u8; 64 / 4]; N] {
let mut i: usize = 0;
let mut mat: [[u8; 64 / 4]; N] = [[0; 64 / 4]; N];
while i < N {
// for loop not allowed in const fn
mat[i] = u64_conv::u64_to_vec_u4(tab[i]);
i += 1;
}
mat
}
/* (inv)SBox and derivatives ------------------------------------------------------- */
static PV2_S: [u8; 1 << 4] = [
// Forward SBox
0xb, 0xf, 0x3, 0x2, 0xa, 0xc, 0x9, 0x1, 0x6, 0x7, 0x8, 0x0, 0xe, 0x5, 0xd, 0x4,
];
static PV2_IS: [u8; 1 << 4] = [
// Backward SBox
0xb, 0x7, 0x3, 0x2, 0xf, 0xd, 0x8, 0x9, 0xa, 0x6, 0x4, 0x0, 0x5, 0xe, 0xc, 0x1,
];
// Combined RC+Sboxes for Encryption
pub static PV2_0_S_0: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, [0_u8; 16], [0_u8; 16]); // Not ideal
pub static PV2_1_S_2: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, ZRC_V2[1], ZRC_V2_IP_IM[2]);
pub static PV2_3_S_4: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, ZRC_V2[3], ZRC_V2_IP_IM[4]);
pub static PV2_5_S_M: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, ZRC_V2[5], ZRC_BETA_IM);
pub static PV2_0_IS_0: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_IS, [0_u8; 16], [0_u8; 16]); // Not ideal
pub static PV2_6_IS_7: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_IS, ZRC_V2_IP_IM[6], ZRC_V2[7]);
pub static PV2_8_IS_9: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_IS, ZRC_V2_IP_IM[8], ZRC_V2[9]);
pub static PV2_A_IS_B: [[u8; 1 << 4]; 64 / 4] =
build_zlut_xsy(PV2_IS, ZRC_V2_IP_IM[10], ZRC_V2[11]);
// Additional RC+Sboxes for Decryption
pub static PV2_B_S_A: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, ZRC_V2[11], ZRC_V2_IP_IM[10]);
pub static PV2_9_S_8: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, ZRC_V2[9], ZRC_V2_IP_IM[8]);
pub static PV2_7_S_6: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_S, ZRC_V2[7], ZRC_V2_IP_IM[6]);
pub static PV2_M_IS_5: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_IS, ZRC_BETA_IM, ZRC_V2[5]);
pub static PV2_4_IS_3: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_IS, ZRC_V2_IP_IM[4], ZRC_V2[3]);
pub static PV2_2_IS_1: [[u8; 1 << 4]; 64 / 4] = build_zlut_xsy(PV2_IS, ZRC_V2_IP_IM[2], ZRC_V2[1]);
// Build special LUTs: SBox( x ^ inner ) ^ outer, depending on word index
const fn build_zlut_xsy(
sbox: [u8; 1 << 4],
xor_inner: [u8; 64 / 4],
xor_outer: [u8; 64 / 4],
) -> [[u8; 1 << 4]; 64 / 4] {
let mut zlut_xsy: [[u8; 1 << 4]; 64 / 4] = [[0; 1 << 4]; 64 / 4];
let mut w: usize = 0;
while w < 64 / 4 {
// for loop not allowed in const fn
let mut x: usize = 0;
while x < (1 << 4) {
zlut_xsy[w][x] = sbox[((x as u8) ^ xor_inner[w]) as usize] ^ xor_outer[w];
x += 1;
}
w += 1;
}
zlut_xsy
}
#[test]
fn test_build_xsy() {
#[rustfmt::skip]
let zlut_1s2: [[u8;16]; 16] = [
[0x7, 0x3, 0xa, 0xb, 0x4, 0x2, 0x9, 0x1, 0xf, 0xe, 0x8, 0x0, 0xd, 0x6, 0xc, 0x5],
[0x3, 0x2, 0xe, 0xa, 0x0, 0x8, 0xd, 0xb, 0x1, 0x9, 0x6, 0x7, 0x5, 0xc, 0x4, 0xf],
[0x4, 0x0, 0x9, 0x8, 0x7, 0x1, 0xa, 0x2, 0xc, 0xd, 0xb, 0x3, 0xe, 0x5, 0xf, 0x6],
[0x5, 0x4, 0x2, 0xa, 0x7, 0xc, 0x6, 0xf, 0xd, 0x9, 0x0, 0x1, 0xe, 0x8, 0x3, 0xb],
[0xa, 0xb, 0x4, 0xc, 0x2, 0x9, 0x1, 0x8, 0x7, 0x3, 0xf, 0xe, 0x6, 0x0, 0x5, 0xd],
[0x3, 0xb, 0xd, 0xc, 0x6, 0xf, 0x5, 0xe, 0x8, 0x9, 0x0, 0x4, 0x2, 0xa, 0x1, 0x7],
[0x1, 0x0, 0x9, 0xd, 0xb, 0x3, 0x8, 0xe, 0xa, 0x2, 0x4, 0x5, 0xf, 0x6, 0xc, 0x7],
[0xd, 0x4, 0xe, 0x5, 0x8, 0x0, 0x6, 0x7, 0x9, 0x1, 0xa, 0xc, 0x3, 0x2, 0xb, 0xf],
[0x1, 0x5, 0x9, 0x8, 0x0, 0x6, 0x3, 0xb, 0xc, 0xd, 0x2, 0xa, 0x4, 0xf, 0x7, 0xe],
[0xa, 0xb, 0x7, 0x3, 0x9, 0x1, 0x4, 0x2, 0x8, 0x0, 0xf, 0xe, 0xc, 0x5, 0xd, 0x6],
[0x3, 0xb, 0xe, 0x8, 0x0, 0x1, 0xd, 0x9, 0x6, 0xf, 0x7, 0xc, 0x2, 0xa, 0x5, 0x4],
[0x1, 0x5, 0x9, 0x8, 0x0, 0x6, 0x3, 0xb, 0xc, 0xd, 0x2, 0xa, 0x4, 0xf, 0x7, 0xe],
[0x3, 0xb, 0xe, 0x8, 0x0, 0x1, 0xd, 0x9, 0x6, 0xf, 0x7, 0xc, 0x2, 0xa, 0x5, 0x4],
[0xb, 0xa, 0x6, 0x2, 0x8, 0x0, 0x5, 0x3, 0x9, 0x1, 0xe, 0xf, 0xd, 0x4, 0xc, 0x7],
[0x8, 0xe, 0xb, 0x3, 0x9, 0xd, 0x1, 0x0, 0xc, 0x7, 0xf, 0x6, 0x4, 0x5, 0xa, 0x2],
[0x2, 0x4, 0x1, 0x9, 0x3, 0x7, 0xb, 0xa, 0x6, 0xd, 0x5, 0xc, 0xe, 0xf, 0x0, 0x8],
];
assert_eq!(zlut_1s2, PV2_1_S_2);
#[rustfmt::skip]
let zlut_5sm: [[u8;16]; 16] = [
[0x4, 0xc, 0x3, 0x2, 0x0, 0x9, 0x1, 0xa, 0x6, 0x7, 0xb, 0xf, 0x5, 0xd, 0x8, 0xe],
[0xf, 0x6, 0xc, 0x7, 0xa, 0x2, 0x4, 0x5, 0xb, 0x3, 0x8, 0xe, 0x1, 0x0, 0x9, 0xd],
[0x3, 0x5, 0xe, 0x6, 0x0, 0x4, 0xd, 0xc, 0xa, 0x1, 0xb, 0x2, 0x8, 0x9, 0xf, 0x7],
[0x3, 0x5, 0x0, 0x8, 0x2, 0x6, 0xa, 0xb, 0x7, 0xc, 0x4, 0xd, 0xf, 0xe, 0x1, 0x9],
[0xa, 0x2, 0x9, 0xf, 0x0, 0x1, 0x8, 0xc, 0xe, 0x7, 0xd, 0x6, 0xb, 0x3, 0x5, 0x4],
[0x2, 0xa, 0x1, 0x7, 0x8, 0x9, 0x0, 0x4, 0x6, 0xf, 0x5, 0xe, 0x3, 0xb, 0xd, 0xc],
[0x9, 0x2, 0xa, 0x3, 0x1, 0x0, 0xf, 0x7, 0xd, 0xb, 0xe, 0x6, 0xc, 0x8, 0x4, 0x5],
[0xd, 0x4, 0xc, 0x7, 0x9, 0x1, 0xe, 0xf, 0x8, 0x0, 0x5, 0x3, 0xb, 0xa, 0x6, 0x2],
[0xf, 0xe, 0x2, 0x6, 0xc, 0x4, 0x1, 0x7, 0xd, 0x5, 0xa, 0xb, 0x9, 0x0, 0x8, 0x3],
[0x0, 0x6, 0x3, 0xb, 0x1, 0x5, 0x9, 0x8, 0x4, 0xf, 0x7, 0xe, 0xc, 0xd, 0x2, 0xa],
[0x7, 0xe, 0x4, 0xf, 0x2, 0xa, 0xc, 0xd, 0x3, 0xb, 0x0, 0x6, 0x9, 0x8, 0x1, 0x5],
[0x7, 0x6, 0x0, 0x8, 0x5, 0xe, 0x4, 0xd, 0xf, 0xb, 0x2, 0x3, 0xc, 0xa, 0x1, 0x9],
[0x5, 0x1, 0xd, 0xc, 0x4, 0x2, 0x7, 0xf, 0x8, 0x9, 0x6, 0xe, 0x0, 0xb, 0x3, 0xa],
[0x0, 0xb, 0x3, 0xa, 0x8, 0x9, 0x6, 0xe, 0x4, 0x2, 0x7, 0xf, 0x5, 0x1, 0xd, 0xc],
[0x3, 0xb, 0x0, 0x6, 0x9, 0x8, 0x1, 0x5, 0x7, 0xe, 0x4, 0xf, 0x2, 0xa, 0xc, 0xd],
[0xb, 0x0, 0x8, 0x1, 0x3, 0x2, 0xd, 0x5, 0xf, 0x9, 0xc, 0x4, 0xe, 0xa, 0x6, 0x7],
];
assert_eq!(zlut_5sm, PV2_5_S_M);
#[rustfmt::skip]
let zlut_6is7: [[u8;16]; 16] = [
[0xb, 0x6, 0x2, 0x9, 0x3, 0x7, 0xd, 0x1, 0xf, 0xe, 0x8, 0xa, 0x4, 0x5, 0xc, 0x0],
[0x3, 0x2, 0x7, 0x5, 0x8, 0x9, 0xd, 0x1, 0xb, 0x6, 0x4, 0xf, 0xa, 0xe, 0xc, 0x0],
[0x1, 0xd, 0x9, 0x8, 0x5, 0x7, 0x2, 0x3, 0x0, 0xc, 0xe, 0xa, 0xf, 0x4, 0x6, 0xb],
[0xe, 0xf, 0xb, 0x7, 0x5, 0x4, 0x1, 0x3, 0xc, 0x8, 0xa, 0x6, 0xd, 0x0, 0x2, 0x9],
[0xd, 0xc, 0x8, 0x4, 0x6, 0x7, 0x2, 0x0, 0xf, 0xb, 0x9, 0x5, 0xe, 0x3, 0x1, 0xa],
[0xe, 0x2, 0x0, 0x4, 0x1, 0xa, 0x8, 0x5, 0xf, 0x3, 0x7, 0x6, 0xb, 0x9, 0xc, 0xd],
[0x0, 0xb, 0x9, 0x4, 0xf, 0x3, 0x1, 0x5, 0xa, 0x8, 0xd, 0xc, 0xe, 0x2, 0x6, 0x7],
[0x1, 0x0, 0x4, 0x8, 0xa, 0xb, 0xe, 0xc, 0x3, 0x7, 0x5, 0x9, 0x2, 0xf, 0xd, 0x6],
[0xe, 0x2, 0x8, 0xc, 0x6, 0xd, 0x9, 0x4, 0xf, 0x3, 0xa, 0xb, 0x5, 0x7, 0x1, 0x0],
[0x0, 0x2, 0x4, 0x5, 0xa, 0x6, 0xf, 0xe, 0x3, 0x8, 0xc, 0x1, 0xb, 0x7, 0xd, 0x9],
[0xb, 0xa, 0xe, 0x2, 0x0, 0x1, 0x4, 0x6, 0x9, 0xd, 0xf, 0x3, 0x8, 0x5, 0x7, 0xc],
[0x0, 0xc, 0x5, 0x4, 0xa, 0x8, 0xe, 0xf, 0x1, 0xd, 0x7, 0x3, 0x9, 0x2, 0x6, 0xb],
[0x6, 0xa, 0x3, 0x2, 0xc, 0xe, 0x8, 0x9, 0x7, 0xb, 0x1, 0x5, 0xf, 0x4, 0x0, 0xd],
[0xe, 0xa, 0x0, 0xc, 0x6, 0xb, 0xf, 0x4, 0x9, 0x8, 0x1, 0xd, 0x2, 0x3, 0x5, 0x7],
[0xe, 0xf, 0x9, 0xb, 0x5, 0x4, 0xd, 0x1, 0xa, 0x7, 0x3, 0x8, 0x2, 0x6, 0xc, 0x0],
[0x4, 0x0, 0xa, 0x6, 0xc, 0x1, 0x5, 0xe, 0x3, 0x2, 0xb, 0x7, 0x8, 0x9, 0xf, 0xd],
];
assert_eq!(zlut_6is7, PV2_6_IS_7);
}
/* LUTs for M-layer (exors) -------------------------------------------------------- */
// Bits (msb) 0123 (lsb) in u4
static PV2_EXOR_TO_0: [[u8; 1 << 4]; 4] = build_zlut_exor(0);
static PV2_EXOR_TO_1: [[u8; 1 << 4]; 4] = build_zlut_exor(1);
static PV2_EXOR_TO_2: [[u8; 1 << 4]; 4] = build_zlut_exor(2);
static PV2_EXOR_TO_3: [[u8; 1 << 4]; 4] = build_zlut_exor(3);
#[rustfmt::skip]
pub static PV2_EXOR_FW: [&[[u8; 1 << 4]; 4]; 2] = [
&PV2_EXOR_TO_3, &PV2_EXOR_TO_2,
];
#[rustfmt::skip]
pub static PV2_EXOR_BW: [&[[u8; 1 << 4]; 4]; 4] = [
&PV2_EXOR_TO_3, &PV2_EXOR_TO_2, &PV2_EXOR_TO_1, &PV2_EXOR_TO_0,
];
// e-xor(b) = xor of all bits except b. [Ex: e-xor(1010,0) = 0, e-xor(1010,1) = 1.]
// Bits (msb) 0123 (lsb) in u4 [more convenient for describing the M-layer?]
const fn u4_exor(x: u8, b: u8) -> u8 {
assert!(b < 4 && x < 16);
let ex_mask: u8 = 0xf - (1 << (3 - b));
let mut c: u8 = x & ex_mask;
c = (c & 0x3) ^ (c >> 2);
c = (c & 0x1) ^ (c >> 1);
c
}
#[test]
fn test_u4_exor() {
let x: u8 = 0b1010;
assert_eq!(u4_exor(x, 0), 1);
assert_eq!(u4_exor(x, 1), 0);
assert_eq!(u4_exor(x, 2), 1);
assert_eq!(u4_exor(x, 3), 0);
let x: u8 = 0b1110;
assert_eq!(u4_exor(x, 0), 0);
assert_eq!(u4_exor(x, 1), 0);
assert_eq!(u4_exor(x, 2), 0);
assert_eq!(u4_exor(x, 3), 1);
}
// e-xor(b) = xor of all bits except b. [Ex: e-xor(1010,0) = 0, e-xor(1010,1) = 1.]
// Bits (msb) 0123 (lsb) in u4 [more convenient for describing the M-layer?]
const fn build_zlut_exor(to_b: u8) -> [[u8; 1 << 4]; 4] {
let mut zlut_exor_to_b: [[u8; 1 << 4]; 4] = [[0; 1 << 4]; 4];
let mut b: usize = 0;
while b < 4 {
// for loop not allowed in const fn
let mut x: usize = 0;
while x < (1 << 4) {
zlut_exor_to_b[b][x] = u4_exor(x as u8, b as u8) << to_b;
x += 1;
}
b += 1;
}
zlut_exor_to_b
}
#[test]
fn test_build_exor() {
let zlut_exor_to_1: [[u8; 16]; 4] = [
[0, 2, 2, 0, 2, 0, 0, 2, 0, 2, 2, 0, 2, 0, 0, 2],
[0, 2, 2, 0, 0, 2, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2],
[0, 2, 0, 2, 2, 0, 2, 0, 2, 0, 2, 0, 0, 2, 0, 2],
[0, 0, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 2, 2],
];
assert_eq!(zlut_exor_to_1, PV2_EXOR_TO_1);
}
/* LUTs for xoring to high / low bits ---------------------------------------------- */
// [Nb] Probably overkill in this case
pub static PV2_XOR_TO_LOW: [u8; 1 << 4] = [
// (01) ^ (23): (x & 3) ^^ (x >> 2) for x in range(16)
0x0, 0x1, 0x2, 0x3, 0x1, 0x0, 0x3, 0x2, 0x2, 0x3, 0x0, 0x1, 0x3, 0x2, 0x1, 0x0,
];
static PV2_XOR_TO_HIGH: [u8; 1 << 4] = [
// <above> << 2
0x0, 0x4, 0x8, 0xc, 0x4, 0x0, 0xc, 0x8, 0x8, 0xc, 0x0, 0x4, 0xc, 0x8, 0x4, 0x0,
];
pub static PV2_XOR_FW: [&[u8; 1 << 4]; 2] = [&PV2_XOR_TO_HIGH, &PV2_XOR_TO_LOW];
// xoring + extract bits (low/high of 2 output bits)
pub static PV2_XOR_BL: [[u8; 1 << 4]; 4] = build_zlut_xor_bhl(0);
pub static PV2_XOR_BH: [[u8; 1 << 4]; 4] = build_zlut_xor_bhl(1);
const fn build_zlut_xor_bhl(hl: u8) -> [[u8; 1 << 4]; 4] {
// [(((01) ^ (23) >> hl) << (3 - b)
let mut zlut_xor_hl_to_b: [[u8; 1 << 4]; 4] = [[0; 1 << 4]; 4];
let mut b: usize = 0;
while b < 4 {
// for loop not allowed in const fn
let mut x: usize = 0;
while x < (1 << 4) {
zlut_xor_hl_to_b[b][x] = ((PV2_XOR_TO_LOW[x] >> hl) & 0x1) << (3 - b);
x += 1;
}
b += 1;
}
zlut_xor_hl_to_b
}
#[test]
fn test_build_xor_bhl() {
let zlut_xor_bh: [[u8; 16]; 4] = [
[0, 0, 8, 8, 0, 0, 8, 8, 8, 8, 0, 0, 8, 8, 0, 0],
[0, 0, 4, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 0],
[0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0],
[0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0],
];
assert_eq!(zlut_xor_bh, PV2_XOR_BH);
}

View File

@@ -1,90 +0,0 @@
/*
* Some bit manipulation converting u64 to vectors of 2/4-bit nibbles
* ----------------------------------------------------------------------------------------------- */
// u64 -> [u4; 16], res[0] = 4 MSB bits of u64
pub const fn u64_to_vec_u4(u: u64) -> [u8; 64 / 4] {
let mut i: usize = 0;
let mut v: [u8; 64 / 4] = [0; 64 / 4];
// "for" loop is unusable inside const
while i < 64 / 4 {
v[64 / 4 - i - 1] = ((u >> (4 * i)) & 0xf) as u8;
i += 1;
}
v
}
#[allow(dead_code)] // kept for symmetry with u64_to_vec_u4(); might be useful to convert back decomposed constants
pub const fn vec_u4_to_u64(v: [u8; 64 / 4]) -> u64 {
let mut i: usize = 0;
let mut u: u64 = 0;
// "for" loop is unusable inside const
while i < 64 / 4 {
u += (v[i] as u64) << (60 - 4 * i);
i += 1;
}
u
}
#[test]
fn test_u64_conv_vec_u4() {
let u: u64 = 0x3f84d5b5b5470917;
let u_dec: [u8; 16] = [
0x3, 0xf, 0x8, 0x4, 0xd, 0x5, 0xb, 0x5, 0xb, 0x5, 0x4, 0x7, 0x0, 0x9, 0x1, 0x7,
];
assert_eq!(u_dec, u64_to_vec_u4(u));
assert_eq!(u, vec_u4_to_u64(u_dec));
let u: u64 = 0x0ac6f9cd6e6f275d;
let u_dec: [u8; 16] = [
0x0, 0xa, 0xc, 0x6, 0xf, 0x9, 0xc, 0xd, 0x6, 0xe, 0x6, 0xf, 0x2, 0x7, 0x5, 0xd,
];
assert_eq!(u_dec, u64_to_vec_u4(u));
assert_eq!(u, vec_u4_to_u64(u_dec));
}
// u64 -> [u2; 32], res[0] = 2 MSB bits of u64
pub const fn u64_to_vec_u2(u: u64) -> [u8; 64 / 2] {
let mut i: usize = 0;
let mut v: [u8; 64 / 2] = [0; 64 / 2];
while i < 64 / 2 {
// for loop unusable inside const
v[64 / 2 - i - 1] = ((u >> (2 * i)) & 0x3) as u8;
i += 1;
}
v
}
pub const fn vec_u2_to_u64(v: [u8; 64 / 2]) -> u64 {
let mut i: usize = 0;
let mut u: u64 = 0;
while i < 64 / 2 {
// for loop unusable inside const
u += (v[i] as u64) << (62 - 2 * i);
i += 1;
}
u
}
#[test]
fn test_u64_conv_vec_u2() {
let u: u64 = 0x603cd95fa72a8704;
#[rustfmt::skip]
let u_dec: [u8; 32] = [
0x1, 0x2, 0x0, 0x0, 0x0, 0x3, 0x3, 0x0, 0x3, 0x1, 0x2, 0x1, 0x1, 0x1, 0x3, 0x3,
0x2, 0x2, 0x1, 0x3, 0x0, 0x2, 0x2, 0x2, 0x2, 0x0, 0x1, 0x3, 0x0, 0x0, 0x1, 0x0];
assert_eq!(u_dec, u64_to_vec_u2(u));
assert_eq!(u, vec_u2_to_u64(u_dec));
let u: u64 = 0xee873b2ec447944d;
#[rustfmt::skip]
let u_dec: [u8; 32] = [
0x3, 0x2, 0x3, 0x2, 0x2, 0x0, 0x1, 0x3, 0x0, 0x3, 0x2, 0x3, 0x0, 0x2, 0x3, 0x2,
0x3, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0x3, 0x2, 0x1, 0x1, 0x0, 0x1, 0x0, 0x3, 0x1];
assert_eq!(u_dec, u64_to_vec_u2(u));
assert_eq!(u, vec_u2_to_u64(u_dec));
}

View File

@@ -1,132 +0,0 @@
//! Known-answer tests against the PRINCEv2 paper test vectors.
//!
//! These tests run a full homomorphic PRINCEv2 encryption/decryption and assert that the decrypted
//! ciphertext matches the values from PRINCEv2 specifications [BEK+20, Appendix B].
//!
//! [BEK+20] Dusan Božilov, Maria Eichlseder, Miroslav Kneževic, Baptiste Lambin, Gregor Leander,
//! Thorben Moos, Ventzislav Nikov, Shahram Rasoolzadeh, Yosuke Todo, and Friedrich Wiemer.
//! PRINCEv2: More security for (almost) no overhead. In Selected Areas in Cryptography (SAC 2020),
//! volume 12804 of LNCS, pp.483--511, Springer, 2020. DOI:10.1007/978-3-030-81652-0_19.
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128;
use tfhe::shortint::prelude::*;
use tfhe_princev2::{pv2_decrypt, pv2_encrypt, u64_to_vec_u2, vec_u2_to_u64};
// KAT structure for Pv2 cipher
struct Pv2Kat {
name: &'static str,
ptxt: u64,
k0: u64,
k1: u64,
ctxt: u64,
}
/// Test vectors from [BEK+20, Appendix B]
static PV2_KATS_TABLE: [Pv2Kat; 5] = [
Pv2Kat {
name: "PRINCEv2 KAT #1",
ptxt: 0x0000000000000000,
k0: 0x0000000000000000,
k1: 0x0000000000000000,
ctxt: 0x0125fc7359441690,
},
Pv2Kat {
name: "PRINCEv2 KAT #2",
ptxt: 0xffffffffffffffff,
k0: 0x0000000000000000,
k1: 0x0000000000000000,
ctxt: 0x832bd46f108e7857,
},
Pv2Kat {
name: "PRINCEv2 KAT #3",
ptxt: 0x0000000000000000,
k0: 0xffffffffffffffff,
k1: 0x0000000000000000,
ctxt: 0xee873b2ec447944d,
},
Pv2Kat {
name: "PRINCEv2 KAT #4",
ptxt: 0x0000000000000000,
k0: 0x0000000000000000,
k1: 0xffffffffffffffff,
ctxt: 0x0ac6f9cd6e6f275d,
},
Pv2Kat {
name: "PRINCEv2 KAT #5",
ptxt: 0x0123456789abcdef,
k0: 0x0123456789abcdef,
k1: 0xfedcba9876543210,
ctxt: 0x603cd95fa72a8704,
},
];
/// Encrypt a u64 as 32 ciphertexts, each holding a 2-bit nibble in the low bits of the FHE message
/// space. Most significant bits of the input are at index 0 in the output
fn encrypt_u64_as_vec_u2l(s_key: &ClientKey, x: u64) -> [Ciphertext; 32] {
let x_u2: [u8; 32] = u64_to_vec_u2(x);
let ct: Vec<Ciphertext> = x_u2
.into_iter()
.map(|u2| s_key.encrypt(u2 as u64))
.collect();
ct.try_into().unwrap()
}
/// Reverse of function encrypt_u64_as_vec_u2l()
fn decrypt_vec_u2l_as_u64(s_key: &ClientKey, v: &[Ciphertext; 32]) -> u64 {
let x_u2: [u8; 32] = std::array::from_fn(|n| s_key.decrypt_message_and_carry(&v[n]) as u8);
let x: u64 = vec_u2_to_u64(x_u2);
x
}
/// Run KATs homomorphically for PRINCEv2 Encryption.
/// [Note] Takes approximately 21s / KAT on 8 cores.
#[test]
fn pv2_enc_kat() {
let (s_key, ev_key): (ClientKey, ServerKey) = // Params: Need 4-bits msg + nu >= 4
tfhe::shortint::gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
for tkat in &PV2_KATS_TABLE {
// Encryptions of inputs (k0,k1,m)
let ct_k0: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, tkat.k0);
let ct_k1: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, tkat.k1);
let ct_m: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, tkat.ptxt);
// PRINCEv2 Enc in FHE
let mut ct_out: [Ciphertext; 32] = std::array::from_fn(|_| ev_key.create_trivial(0)); // [NB] shortint::create_trivial() vs boolean::trivial_encrypt()
pv2_encrypt(&ev_key, &mut ct_out, &ct_m, &ct_k0, &ct_k1);
// Testing the (de-)encrypted result
let pt_out: u64 = decrypt_vec_u2l_as_u64(&s_key, &ct_out);
assert_eq!(
pt_out, tkat.ctxt,
"{} failed: ptxt={:#018x}, k0={:#018x}, k1={:#018x}, expected={:#018x}, got={:#018x}",
tkat.name, tkat.ptxt, tkat.k0, tkat.k1, tkat.ctxt, pt_out
);
}
}
#[test]
fn pv2_dec_kat() {
let (s_key, ev_key): (ClientKey, ServerKey) = // Params: Need 4-bits msg + nu >= 4
tfhe::shortint::gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M128);
for tkat in &PV2_KATS_TABLE {
// Encryptions of inputs (k0,k1,m)
let ct_k0: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, tkat.k0);
let ct_k1: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, tkat.k1);
let ct_c: [Ciphertext; 32] = encrypt_u64_as_vec_u2l(&s_key, tkat.ctxt);
// PRINCEv2 Dec in FHE
let mut ct_out: [Ciphertext; 32] = std::array::from_fn(|_| ev_key.create_trivial(0)); // [NB] shortint::create_trivial() vs boolean::trivial_encrypt()
pv2_decrypt(&ev_key, &mut ct_out, &ct_c, &ct_k0, &ct_k1);
// Testing the (de-)encrypted result
let pt_out: u64 = decrypt_vec_u2l_as_u64(&s_key, &ct_out);
assert_eq!(
pt_out, tkat.ptxt,
"{} failed: ctxt={:#018x}, k0={:#018x}, k1={:#018x}, expected={:#018x}, got={:#018x}",
tkat.name, tkat.ctxt, tkat.k0, tkat.k1, tkat.ptxt, pt_out
);
}
}

View File

@@ -6,12 +6,11 @@ use std::process::Command;
const DIR_TO_IGNORE: [&str; 1] = ["apps/test-vectors"];
const FILES_TO_IGNORE: [&str; 12] = [
const FILES_TO_IGNORE: [&str; 11] = [
// This contains fragments of code that are unrelated to TFHE-rs
"tfhe/docs/tutorials/sha256-bool.md",
// TODO: This contains code that could be executed as a trivium docstring
"apps/trivium/README.md",
"apps/princev2/README.md",
// TODO: should we test this ?
"utils/tfhe-versionable/README.md",
"utils/wasm-par-mq/README.md",

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "1.6.0"
version = "1.6.1"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -64,7 +64,7 @@ tfhe-fft = { version = "0.10.1", path = "../tfhe-fft", features = [
"serde",
"fft128",
] }
tfhe-ntt = { version = "0.7.0", path = "../tfhe-ntt" }
tfhe-ntt = { version = "0.7.1", path = "../tfhe-ntt" }
pulp = { workspace = true, features = ["default"] }
tfhe-cuda-backend = { version = "0.14.0", path = "../backends/tfhe-cuda-backend", optional = true }
aligned-vec = { workspace = true, features = ["default", "serde"] }

View File

@@ -75,11 +75,11 @@
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="594.0" y="420.0">121 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="678.0" y="420.0">165 ms</text>
<text dominant-baseline="middle" text-anchor="start" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="6" y="460.0">Leading / Trailing zeros/ones</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="342.0" y="460.0">88.4 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="426.0" y="460.0">148 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="510.0" y="460.0">169 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="594.0" y="460.0">222 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="678.0" y="460.0">275 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="342.0" y="460.0">67.2 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="426.0" y="460.0">70.6 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="510.0" y="460.0">89.8 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="594.0" y="460.0">92.6 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="678.0" y="460.0">113 ms</text>
<text dominant-baseline="middle" text-anchor="start" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="6" y="500.0">Log2</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="342.0" y="500.0">110 ms</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="426.0" y="500.0">163 ms</text>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -75,11 +75,11 @@
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="594.0" y="420.0">32.5 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="678.0" y="420.0">14.0 ops/s</text>
<text dominant-baseline="middle" text-anchor="start" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="6" y="460.0">Leading / Trailing zeros/ones</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="342.0" y="460.0">625 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="426.0" y="460.0">247 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="510.0" y="460.0">108 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="594.0" y="460.0">44.1 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="678.0" y="460.0">19.0 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="342.0" y="460.0">824 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="426.0" y="460.0">487 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="510.0" y="460.0">222 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="594.0" y="460.0">119 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="678.0" y="460.0">57.8 ops/s</text>
<text dominant-baseline="middle" text-anchor="start" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="6" y="500.0">Log2</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="342.0" y="500.0">542 ops/s</text>
<text dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="14" font-weight="normal" fill="black" x="426.0" y="500.0">220 ops/s</text>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -74,7 +74,7 @@ To compile and execute GPU TFHE-rs programs, make sure your system has the follo
To use the **TFHE-rs** GPU backend in your project, add the following dependency in your `Cargo.toml`.
```toml
tfhe = { version = "~1.6.0", features = ["boolean", "shortint", "integer", "gpu"] }
tfhe = { version = "~1.6.1", features = ["boolean", "shortint", "integer", "gpu"] }
```
If none of the supported backends is configured in `Cargo.toml`, the CPU backend is used.

View File

@@ -17,7 +17,7 @@ This guide explains how to update your existing program to leverage HPU accelera
To use the **TFHE-rs** HPU backend in your project, add the following dependency in your `Cargo.toml`.
```toml
tfhe = { version = "~1.6.0", features = ["integer", "hpu-v80"] }
tfhe = { version = "~1.6.1", features = ["integer", "hpu-v80"] }
```
{% hint style="success" %}

View File

@@ -16,7 +16,7 @@ You can load serialized data with the `unversionize` function, even in newer ver
[dependencies]
# ...
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
tfhe-versionable = "0.6.0"
bincode = "1.3.3"
```

View File

@@ -161,7 +161,7 @@ In the following example, we use [bincode](https://crates.io/crates/bincode) for
[dependencies]
# ...
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
bincode = "1.3.3"
```

View File

@@ -19,7 +19,7 @@ The following example shows a complete workflow of working with encrypted arrays
# Cargo.toml
[dependencies]
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
```
```rust

View File

@@ -36,7 +36,7 @@ To serialize a `KVStore`, it must first be compressed.
# Cargo.toml
[dependencies]
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
```
```rust

View File

@@ -29,7 +29,7 @@ Here is an example:
# Cargo.toml
[dependencies]
tfhe = { version = "~1.6.0", features = ["integer", "strings"] }
tfhe = { version = "~1.6.1", features = ["integer", "strings"] }
```
```rust

View File

@@ -7,7 +7,7 @@ This document provides instructions to set up **TFHE-rs** in your project.
First, add **TFHE-rs** as a dependency in your `Cargo.toml`.
```toml
tfhe = { version = "~1.6.0", features = ["boolean", "shortint", "integer"] }
tfhe = { version = "~1.6.1", features = ["boolean", "shortint", "integer"] }
```
{% hint style="info" %}
@@ -35,7 +35,7 @@ By default, **TFHE-rs** makes the assumption that hardware AES features are enab
To add support for older CPU, import **TFHE-rs** with the `software-prng` feature in your `Cargo.toml`:
```toml
tfhe = { version = "~1.6.0", features = ["boolean", "shortint", "integer", "software-prng"] }
tfhe = { version = "~1.6.1", features = ["boolean", "shortint", "integer", "software-prng"] }
```
## Hardware acceleration

View File

@@ -59,7 +59,7 @@ edition = "2021"
Then add the following configuration to include **TFHE-rs**:
```toml
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
```
Your updated `Cargo.toml` file should look like this:
@@ -71,7 +71,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
```
If you are on a different platform please refer to the [installation documentation](installation.md) for configuration options of other supported platforms.

View File

@@ -9,7 +9,7 @@ Welcome to this tutorial about `TFHE-rs` `core_crypto` module.
To use `TFHE-rs`, it first has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "~1.6.0" }
tfhe = { version = "~1.6.1" }
```
### Commented code to double a 2-bit message in a leveled fashion and using a PBS with the `core_crypto` module.

View File

@@ -28,7 +28,7 @@ To use the `FheUint8` type, enable the `integer` feature:
# Cargo.toml
[dependencies]
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
```
The `MyFheString::encrypt` function performs data validation to ensure the input string contains only ASCII characters.
@@ -167,7 +167,7 @@ First, add the feature in your `Cargo.toml`
# Cargo.toml
[dependencies]
tfhe = { version = "~1.6.0", features = ["strings"] }
tfhe = { version = "~1.6.1", features = ["strings"] }
```
The `FheAsciiString` type allows to simply do homomorphic case changing of encrypted strings (and much more!):

View File

@@ -17,7 +17,7 @@ This function returns a Boolean (`true` or `false`) so that the total count of `
```toml
# Cargo.toml
tfhe = { version = "~1.6.0", features = ["integer"] }
tfhe = { version = "~1.6.1", features = ["integer"] }
```
First, define the verification function.

View File

@@ -70,8 +70,8 @@ pub use integers::{
pub use keys::CudaServerKey;
pub use keys::{
generate_keys, ClientKey, CompactPublicKey, CompressedCompactPublicKey, CompressedPublicKey,
CompressedReRandomizationKeySwitchingKey, CompressedServerKey, KeySwitchingKey, PublicKey,
ReRandomizationKeySwitchingKey, ServerKey,
CompressedReRandomizationKey, CompressedReRandomizationKeySwitchingKey, CompressedServerKey,
KeySwitchingKey, PublicKey, ReRandomizationKey, ReRandomizationKeySwitchingKey, ServerKey,
};
use strum::FromRepr;

View File

@@ -976,7 +976,7 @@ dependencies = [
[[package]]
name = "tfhe"
version = "1.6.0"
version = "1.6.1"
dependencies = [
"aligned-vec",
"bincode",