feat(oprf): add split-blinding unlinkable OPRF (partial unlinkability)

- Implement split-blinding protocol with C, C_r dual evaluation
- Add 7 security proof tests for unlinkability properties
- Add benchmarks: ~101µs (109x faster than OT-based)
- Note: Server can compute C - C_r fingerprint (documented limitation)
This commit is contained in:
2026-01-07 12:29:15 -07:00
parent 9be4bcaf7d
commit f022aeefd6
4 changed files with 899 additions and 3 deletions

View File

@@ -14,6 +14,10 @@ use opaque_lattice::oprf::ring_lpr::{
RingLprKey, client_blind as lpr_client_blind, client_finalize as lpr_finalize,
server_evaluate as lpr_server_evaluate,
};
use opaque_lattice::oprf::unlinkable_oprf::{
UnlinkablePublicParams, UnlinkableServerKey, client_blind_unlinkable,
client_finalize_unlinkable, evaluate_unlinkable, server_evaluate_unlinkable,
};
/// Benchmark Fast OPRF (OT-free) - full protocol
fn bench_fast_oprf(c: &mut Criterion) {
@@ -96,15 +100,16 @@ fn bench_ring_lpr_oprf(c: &mut Criterion) {
group.finish();
}
/// Compare both protocols side-by-side
/// Compare all three protocols side-by-side
fn bench_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("oprf_comparison");
// Fast OPRF setup
let pp = PublicParams::generate(b"benchmark-params");
let fast_key = ServerKey::generate(&pp, b"benchmark-key");
// Ring-LPR setup
let unlink_pp = UnlinkablePublicParams::generate(b"benchmark-params");
let unlink_key = UnlinkableServerKey::generate(&unlink_pp, b"benchmark-key");
let mut rng = ChaCha20Rng::seed_from_u64(12345);
let lpr_key = RingLprKey::generate(&mut rng);
@@ -121,6 +126,12 @@ fn bench_comparison(c: &mut Criterion) {
b.iter(|| fast_evaluate(&pp, &fast_key, pwd))
});
group.bench_with_input(
BenchmarkId::new("unlinkable_oprf", len),
password,
|b, pwd| b.iter(|| evaluate_unlinkable(&unlink_pp, &unlink_key, pwd)),
);
group.bench_with_input(
BenchmarkId::new("ring_lpr_oprf", len),
password,
@@ -138,6 +149,36 @@ fn bench_comparison(c: &mut Criterion) {
group.finish();
}
/// Benchmark Unlinkable OPRF - full protocol
fn bench_unlinkable_oprf(c: &mut Criterion) {
let mut group = c.benchmark_group("unlinkable_oprf");
let pp = UnlinkablePublicParams::generate(b"benchmark-params");
let key = UnlinkableServerKey::generate(&pp, b"benchmark-key");
let password = b"benchmark-password-12345";
group.bench_function("client_blind", |b| {
b.iter(|| client_blind_unlinkable(&pp, password))
});
let (state, blinded) = client_blind_unlinkable(&pp, password);
group.bench_function("server_evaluate", |b| {
b.iter(|| server_evaluate_unlinkable(&key, &blinded))
});
let response = server_evaluate_unlinkable(&key, &blinded);
group.bench_function("client_finalize", |b| {
let state = state.clone();
b.iter(|| client_finalize_unlinkable(&state, key.public_key(), &response))
});
group.bench_function("full_protocol", |b| {
b.iter(|| evaluate_unlinkable(&pp, &key, password))
});
group.finish();
}
/// Benchmark message sizes
fn bench_message_sizes(c: &mut Criterion) {
println!("\n=== Message Size Comparison ===\n");
@@ -181,6 +222,7 @@ fn bench_message_sizes(c: &mut Criterion) {
criterion_group!(
benches,
bench_fast_oprf,
bench_unlinkable_oprf,
bench_ring_lpr_oprf,
bench_comparison,
);