Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion tfhe-benchmark/benches/high_level_api/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use benchmark::utilities::{
hlapi_throughput_num_ops, write_to_json, BenchmarkType, BitSizesSet, EnvConfig, OperatorType,
};
use criterion::{black_box, Criterion, Throughput};
use oprf::oprf_any_range2;
use rand::prelude::*;
use rayon::prelude::*;
use std::marker::PhantomData;
use std::ops::*;
use tfhe::core_crypto::prelude::Numeric;
Expand All @@ -16,7 +18,7 @@ use tfhe::{
KVStore,
};

use rayon::prelude::*;
mod oprf;

fn bench_fhe_type<FheType>(
c: &mut Criterion,
Expand Down Expand Up @@ -481,5 +483,7 @@ fn main() {
}
}

oprf_any_range2();

c.final_summary();
}
44 changes: 44 additions & 0 deletions tfhe-benchmark/benches/high_level_api/oprf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use benchmark::params_aliases::BENCH_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
use criterion::{black_box, criterion_group, Criterion};
use std::num::NonZeroU64;
use tfhe::{set_server_key, ClientKey, ConfigBuilder, FheUint64, RangeForRandom, Seed, ServerKey};

pub fn oprf_any_range(c: &mut Criterion) {
let bench_name = "hlapi::oprf_any_range";

let mut bench_group = c.benchmark_group(bench_name);
bench_group
.sample_size(15)
.measurement_time(std::time::Duration::from_secs(30));

let param = BENCH_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;

let config = ConfigBuilder::with_custom_parameters(param).build();
let cks = ClientKey::generate(config);
let sks = ServerKey::new(&cks);

rayon::broadcast(|_| set_server_key(sks.clone()));
set_server_key(sks);

for excluded_upper_bound in [3, 52] {
let range = RangeForRandom::new_from_excluded_upper_bound(
NonZeroU64::new(excluded_upper_bound).unwrap(),
);

let bench_id_oprf = format!("{bench_name}::bound_{excluded_upper_bound}");

bench_group.bench_function(&bench_id_oprf, |b| {
b.iter(|| {
_ = black_box(FheUint64::generate_oblivious_pseudo_random_custom_range(
Seed(0),
&range,
None,
));
})
});
}

bench_group.finish()
}

criterion_group!(oprf_any_range2, oprf_any_range);
1 change: 1 addition & 0 deletions tfhe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ rand_distr = "0.4.3"
criterion = "0.5.1"
doc-comment = "0.3.3"
serde_json = "1.0.94"
num-bigint = "0.4.6"
# clap has to be pinned as its minimum supported rust version
# changes often between minor releases, which breaks our CI
clap = { version = "=4.5.30", features = ["derive"] }
Expand Down
42 changes: 33 additions & 9 deletions tfhe/docs/fhe-computation/advanced-features/encrypted-prf.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@

This document explains the mechanism and steps to generate an oblivious encrypted random value using only server keys.

The goal is to give to the server the possibility to generate a random value, which will be obtained in an encrypted format and will remain unknown to the server. The implementation is based on [this article](https://eprint.iacr.org/2024/665).
The goal is to give to the server the possibility to generate a random value, which will be obtained in an encrypted format and will remain unknown to the server.

This is possible through two methods on `FheUint` and `FheInt`:
The main method for this is `FheUint::generate_oblivious_pseudo_random_custom_range` which returns an integer in the given range.
Currently the range can only be in the form `[0, excluded_upper_bound[` with any `excluded_upper_bound` in `[1, 2^64[`
It follows a distribution close to the uniform.

This function guarantees the norm-1 distance (defined as ∆(P,Q) := 1/2 Sum[ω∈Ω] |P(ω) − Q(ω)|)
between the actual distribution and the target uniform distribution will be below the `max_distance` argument (which must be in ]0, 1[).
The higher the distance, the more dissimilar the actual distribution is from the target uniform distribution.

The default value for `max_distance` is `2^-128` if `None` is provided.

Higher values allow better performance but must be considered carefully in the context of their target application as it may have serious unintended consequences.

If the range is a power of 2, the distribution is uniform (for any `max_distance`) and the cost is smaller.


For powers of 2 specifically there are two methods on `FheUint` and `FheInt` (based on [this article](https://eprint.iacr.org/2024/665)):
- `generate_oblivious_pseudo_random` which return an integer taken uniformly in the full integer range (`[0; 2^N[` for a `FheUintN` and `[-2^(N-1); 2^(N-1)[` for a `FheIntN`).
- `generate_oblivious_pseudo_random_bounded` which return an integer taken uniformly in `[0; 2^random_bits_count[`. For a `FheUintN`, we must have `random_bits_count <= N`. For a `FheIntN`, we must have `random_bits_count <= N - 1`.

Both methods functions take a seed `Seed` as input, which could be any `u128` value.
They both rely on the use of the usual server key.

These method functions take a seed `Seed` as input, which could be any `u128` value.
They rely on the use of the usual server key.
The output is reproducible, i.e., the function is deterministic from the inputs: assuming the same hardware, seed and server key, this function outputs the same random encrypted value.


Expand All @@ -18,31 +34,39 @@ Here is an example of the usage:

```rust
use tfhe::prelude::FheDecrypt;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheInt8, Seed};
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheInt8, RangeForRandom, Seed};
use std::num::NonZeroU64;

pub fn main() {
let config = ConfigBuilder::default().build();
let (client_key, server_key) = generate_keys(config);

set_server_key(server_key);

let excluded_upper_bound = NonZeroU64::new(3).unwrap();
let range = RangeForRandom::new_from_excluded_upper_bound(excluded_upper_bound);

// in [0, excluded_upper_bound[ = {0, 1, 2}
let ct_res = FheUint8::generate_oblivious_pseudo_random_custom_range(Seed(0), &range, None);
let dec_result: u8 = ct_res.decrypt(&client_key);

let random_bits_count = 3;

// in [0, 2^8[
let ct_res = FheUint8::generate_oblivious_pseudo_random(Seed(0));

let dec_result: u8 = ct_res.decrypt(&client_key);

// in [0, 2^random_bits_count[ = [0, 8[
let ct_res = FheUint8::generate_oblivious_pseudo_random_bounded(Seed(0), random_bits_count);

let dec_result: u8 = ct_res.decrypt(&client_key);
assert!(dec_result < (1 << random_bits_count));

// in [-2^7, 2^7[
let ct_res = FheInt8::generate_oblivious_pseudo_random(Seed(0));

let dec_result: i8 = ct_res.decrypt(&client_key);

// in [0, 2^random_bits_count[ = [0, 8[
let ct_res = FheInt8::generate_oblivious_pseudo_random_bounded(Seed(0), random_bits_count);

let dec_result: i8 = ct_res.decrypt(&client_key);
assert!(dec_result < (1 << random_bits_count));
}
Expand Down
6 changes: 4 additions & 2 deletions tfhe/src/core_crypto/commons/math/random/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,10 +540,12 @@ pub fn sup_diff(cumulative_bins: &[u64], theoretical_cdf: &[f64]) -> f64 {
.iter()
.copied()
.zip_eq(theoretical_cdf.iter().copied())
.map(|(x, theoretical_cdf)| {
.enumerate()
.map(|(i, (x, theoretical_cdf))| {
let empirical_cdf = x as f64 / number_of_samples as f64;

if theoretical_cdf == 1.0 {
if i == cumulative_bins.len() - 1 {
assert_eq!(theoretical_cdf, 1.0);
assert_eq!(empirical_cdf, 1.0);
}

Expand Down
Loading
Loading