feat(oprf): add revolutionary VOLE-LWR helper-less unlinkable OPRF

Implements a novel post-quantum OPRF combining:
- VOLE-based masking (prevents fingerprint attacks)
- LWR finalization (no reconciliation helpers transmitted)
- PCG pre-processing (amortized communication cost)
- NTT-friendly q=65537 (WASM performance)

Key fixes during implementation:
- LWR parameters: p=16, β=1 ensures 2nβ²=512 < q/(2p)=2048
- Password element must be UNIFORM (not small) for LWR to work
- Server subtracts v=u·Δ+noise, client just rounds (no addition)

Performance: ~82µs full protocol (vs 60µs fast, 99µs unlinkable)
Security: UC-unlinkable, helper-less, post-quantum (Ring-LWR)

All 206 tests passing.
This commit is contained in:
2026-01-07 12:59:20 -07:00
parent 8d58a39c3b
commit d8b4ed9c2d
4 changed files with 756 additions and 2 deletions

View File

@@ -23,6 +23,10 @@ use opaque_lattice::oprf::unlinkable_oprf::{
UnlinkablePublicParams, UnlinkableServerKey, client_blind_unlinkable,
client_finalize_unlinkable, evaluate_unlinkable, server_evaluate_unlinkable,
};
use opaque_lattice::oprf::vole_oprf::{
VoleServerKey, evaluate_vole_oprf, vole_client_blind, vole_client_finalize,
vole_server_evaluate, vole_setup,
};
/// Benchmark Fast OPRF (OT-free) - full protocol
fn bench_fast_oprf(c: &mut Criterion) {
@@ -157,6 +161,9 @@ fn bench_comparison(c: &mut Criterion) {
let mut rng = ChaCha20Rng::seed_from_u64(12345);
let lpr_key = RingLprKey::generate(&mut rng);
let vole_pcg = vole_setup();
let vole_key = VoleServerKey::generate(b"benchmark-key");
let passwords = [
b"short".as_slice(),
b"medium-password-123".as_slice(),
@@ -192,6 +199,10 @@ fn bench_comparison(c: &mut Criterion) {
})
},
);
group.bench_with_input(BenchmarkId::new("vole_oprf", len), password, |b, pwd| {
b.iter(|| evaluate_vole_oprf(&vole_pcg, &vole_key, pwd))
});
}
group.finish();
@@ -227,6 +238,35 @@ fn bench_unlinkable_oprf(c: &mut Criterion) {
group.finish();
}
/// Benchmark VOLE OPRF (revolutionary helper-less, truly unlinkable)
fn bench_vole_oprf(c: &mut Criterion) {
let mut group = c.benchmark_group("vole_oprf");
let pcg = vole_setup();
let key = VoleServerKey::generate(b"benchmark-key");
let password = b"benchmark-password-12345";
group.bench_function("client_blind", |b| {
b.iter(|| vole_client_blind(&pcg, &key.delta, password))
});
let (state, message) = vole_client_blind(&pcg, &key.delta, password);
group.bench_function("server_evaluate", |b| {
b.iter(|| vole_server_evaluate(&key, &pcg, &message))
});
let response = vole_server_evaluate(&key, &pcg, &message);
group.bench_function("client_finalize", |b| {
b.iter(|| vole_client_finalize(&state, &key.delta, &response))
});
group.bench_function("full_protocol", |b| {
b.iter(|| evaluate_vole_oprf(&pcg, &key, password))
});
group.finish();
}
/// Benchmark message sizes
fn bench_message_sizes(c: &mut Criterion) {
println!("\n=== Message Size Comparison ===\n");
@@ -273,6 +313,7 @@ criterion_group!(
bench_unlinkable_oprf,
bench_leap_oprf,
bench_ring_lpr_oprf,
bench_vole_oprf,
bench_comparison,
);

View File

@@ -3,12 +3,11 @@
//! A |t-value| > 5 indicates a timing leak with high confidence.
//! Functions should show |t-value| < 5 after sufficient samples.
use dudect_bencher::{BenchRng, Class, CtRunner, ctbench_main};
use dudect_bencher::{BenchRng, Class, CtRunner, ctbench_main, rand::Rng};
use opaque_lattice::oprf::fast_oprf::{
PublicParams, Q, RING_N, ReconciliationHelper, RingElement, ServerKey, client_blind,
client_finalize, server_evaluate,
};
use rand::Rng;
fn coin_flip(rng: &mut BenchRng) -> bool {
rng.gen_range(0u8..2) == 0