feat(oprf): add LEAP-style truly unlinkable OPRF with commit-challenge protocol

- Implement commit-challenge protocol to prevent fingerprint attack
- Use Learning With Rounding (LWR) instead of reconciliation helpers
- Add mathematical analysis document (docs/LEAP_ANALYSIS.md)
- 8 new tests, 197 total tests passing
- Benchmark: ~108µs (102x faster than OT-based, truly unlinkable)

The key insight: client commits to r BEFORE server sends challenge ρ,
so server cannot predict H(r||ρ) to extract A·s+e fingerprint.
This commit is contained in:
2026-01-07 12:36:44 -07:00
parent f022aeefd6
commit 8d58a39c3b
4 changed files with 947 additions and 1 deletions

View File

@@ -10,6 +10,11 @@ use opaque_lattice::oprf::fast_oprf::{
PublicParams, ServerKey, client_blind as fast_client_blind, client_finalize as fast_finalize,
evaluate as fast_evaluate, server_evaluate as fast_server_evaluate,
};
use opaque_lattice::oprf::leap_oprf::{
LeapPublicParams, LeapServerKey, client_blind as leap_blind, client_commit,
client_finalize as leap_finalize, evaluate_leap, server_challenge,
server_evaluate as leap_evaluate,
};
use opaque_lattice::oprf::ring_lpr::{
RingLprKey, client_blind as lpr_client_blind, client_finalize as lpr_finalize,
server_evaluate as lpr_server_evaluate,
@@ -100,7 +105,43 @@ fn bench_ring_lpr_oprf(c: &mut Criterion) {
group.finish();
}
/// Compare all three protocols side-by-side
/// Benchmark LEAP OPRF (truly unlinkable, multi-round)
fn bench_leap_oprf(c: &mut Criterion) {
let mut group = c.benchmark_group("leap_oprf");
let pp = LeapPublicParams::generate(b"benchmark-params");
let key = LeapServerKey::generate(&pp, b"benchmark-key");
let password = b"benchmark-password-12345";
group.bench_function("client_commit", |b| b.iter(client_commit));
group.bench_function("server_challenge", |b| b.iter(server_challenge));
let commitment = client_commit();
let challenge = server_challenge();
group.bench_function("client_blind", |b| {
b.iter(|| leap_blind(&pp, password, &commitment, &challenge))
});
let (state, message) = leap_blind(&pp, password, &commitment, &challenge);
group.bench_function("server_evaluate", |b| {
b.iter(|| leap_evaluate(&key, &commitment, &challenge, &message))
});
let response = leap_evaluate(&key, &commitment, &challenge, &message).unwrap();
group.bench_function("client_finalize", |b| {
let state = state.clone();
b.iter(|| leap_finalize(&state, key.public_key(), &response))
});
group.bench_function("full_protocol", |b| {
b.iter(|| evaluate_leap(&pp, &key, password))
});
group.finish();
}
/// Compare all four protocols side-by-side
fn bench_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("oprf_comparison");
@@ -110,6 +151,9 @@ fn bench_comparison(c: &mut Criterion) {
let unlink_pp = UnlinkablePublicParams::generate(b"benchmark-params");
let unlink_key = UnlinkableServerKey::generate(&unlink_pp, b"benchmark-key");
let leap_pp = LeapPublicParams::generate(b"benchmark-params");
let leap_key = LeapServerKey::generate(&leap_pp, b"benchmark-key");
let mut rng = ChaCha20Rng::seed_from_u64(12345);
let lpr_key = RingLprKey::generate(&mut rng);
@@ -132,6 +176,10 @@ fn bench_comparison(c: &mut Criterion) {
|b, pwd| b.iter(|| evaluate_unlinkable(&unlink_pp, &unlink_key, pwd)),
);
group.bench_with_input(BenchmarkId::new("leap_oprf", len), password, |b, pwd| {
b.iter(|| evaluate_leap(&leap_pp, &leap_key, pwd))
});
group.bench_with_input(
BenchmarkId::new("ring_lpr_oprf", len),
password,
@@ -223,6 +271,7 @@ criterion_group!(
benches,
bench_fast_oprf,
bench_unlinkable_oprf,
bench_leap_oprf,
bench_ring_lpr_oprf,
bench_comparison,
);