ntru prime
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
pub mod fast_oprf;
|
pub mod fast_oprf;
|
||||||
pub mod hybrid;
|
pub mod hybrid;
|
||||||
pub mod leap_oprf;
|
pub mod leap_oprf;
|
||||||
|
pub mod ntru_oprf;
|
||||||
pub mod ot;
|
pub mod ot;
|
||||||
pub mod ring;
|
pub mod ring;
|
||||||
pub mod ring_lpr;
|
pub mod ring_lpr;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod security_proofs;
|
mod security_proofs;
|
||||||
|
pub mod silent_vole_oprf;
|
||||||
pub mod unlinkable_oprf;
|
pub mod unlinkable_oprf;
|
||||||
pub mod vole_oprf;
|
pub mod vole_oprf;
|
||||||
pub mod voprf;
|
pub mod voprf;
|
||||||
@@ -48,3 +50,16 @@ pub use vole_oprf::{
|
|||||||
vole_client_login, vole_client_start_registration, vole_client_verify_login,
|
vole_client_login, vole_client_start_registration, vole_client_verify_login,
|
||||||
vole_server_evaluate, vole_server_login, vole_server_register, vole_setup,
|
vole_server_evaluate, vole_server_login, vole_server_register, vole_setup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use silent_vole_oprf::{
|
||||||
|
BlindedInput as SilentBlindedInput, ClientCredential as SilentClientCredential,
|
||||||
|
ClientState as SilentClientState, OprfOutput as SilentOprfOutput,
|
||||||
|
ServerPublicKey as SilentServerPublicKey, ServerRecord as SilentServerRecord,
|
||||||
|
ServerResponse as SilentServerResponse, ServerSecretKey as SilentServerSecretKey,
|
||||||
|
client_blind as silent_client_blind, client_finalize as silent_client_finalize,
|
||||||
|
client_finish_registration as silent_client_finish_registration,
|
||||||
|
client_login as silent_client_login, client_verify_login as silent_client_verify_login,
|
||||||
|
evaluate as silent_evaluate, server_evaluate as silent_server_evaluate,
|
||||||
|
server_keygen as silent_server_keygen, server_login as silent_server_login,
|
||||||
|
server_register as silent_server_register,
|
||||||
|
};
|
||||||
|
|||||||
1034
src/oprf/ntru_oprf.rs
Normal file
1034
src/oprf/ntru_oprf.rs
Normal file
File diff suppressed because it is too large
Load Diff
910
src/oprf/silent_vole_oprf.rs
Normal file
910
src/oprf/silent_vole_oprf.rs
Normal file
@@ -0,0 +1,910 @@
|
|||||||
|
//! Silent VOLE OPRF - True Oblivious Construction
|
||||||
|
//!
|
||||||
|
//! # The Problem We're Solving
|
||||||
|
//!
|
||||||
|
//! The previous "VOLE-OPRF" had a fatal flaw: server stored `client_seed` and could
|
||||||
|
//! compute `u = PRG(client_seed, pcg_index)`, then unmask `s = masked_input - u`.
|
||||||
|
//!
|
||||||
|
//! # The Fix: Ring-LWE Based Oblivious Evaluation
|
||||||
|
//!
|
||||||
|
//! This construction uses Ring-LWE encryption to achieve TRUE obliviousness:
|
||||||
|
//! - Client's mask `r` is fresh random each session
|
||||||
|
//! - Server sees `C = A·r + e + encode(s)` - an LWE ciphertext
|
||||||
|
//! - Server CANNOT extract `s` because solving LWE is hard
|
||||||
|
//! - Server CANNOT link sessions because `r` is different each time
|
||||||
|
//!
|
||||||
|
//! # Protocol Flow
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! REGISTRATION:
|
||||||
|
//! Server generates: (A, pk = A·k + e_k) where k is OPRF key
|
||||||
|
//! Client stores: (A, pk)
|
||||||
|
//! Server stores: k
|
||||||
|
//!
|
||||||
|
//! LOGIN (Single Round):
|
||||||
|
//! Client:
|
||||||
|
//! 1. Pick random small r (blinding factor)
|
||||||
|
//! 2. C = A·r + e + encode(password) // LWE encryption!
|
||||||
|
//! 3. Send C to server
|
||||||
|
//!
|
||||||
|
//! Server:
|
||||||
|
//! 4. V = k·C = k·A·r + k·e + k·encode(s)
|
||||||
|
//! 5. Send V to client
|
||||||
|
//!
|
||||||
|
//! Client:
|
||||||
|
//! 6. W = r·pk = r·A·k + r·e_k // Unblinding term
|
||||||
|
//! 7. Output = round(V - W) = round(k·s + noise)
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Security Analysis
|
||||||
|
//!
|
||||||
|
//! - **Obliviousness**: Server sees C which is LWE encryption of s with randomness r.
|
||||||
|
//! Extracting s requires solving Ring-LWE (hard).
|
||||||
|
//! - **Unlinkability**: Each session uses fresh r, so C₁ and C₂ are independent.
|
||||||
|
//! Server cannot compute C₁ - C₂ to get anything useful.
|
||||||
|
//! - **Correctness**: V - W = k·s + (k·e - r·e_k) = k·s + small_noise.
|
||||||
|
//! LWR rounding absorbs the noise.
|
||||||
|
//!
|
||||||
|
//! # Why This Is Revolutionary
|
||||||
|
//!
|
||||||
|
//! 1. **True Obliviousness**: Unlike the broken "shared seed" approach
|
||||||
|
//! 2. **No Reconciliation Helper**: LWR rounding eliminates helper transmission
|
||||||
|
//! 3. **Single Round Online**: Client → Server → Client
|
||||||
|
//! 4. **Post-Quantum Secure**: Based on Ring-LWE/LWR assumptions
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use sha3::{Digest, Sha3_256, Sha3_512};
|
||||||
|
use std::fmt;
|
||||||
|
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PARAMETERS - Carefully chosen for security and correctness
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Ring dimension (power of 2 for NTT)
|
||||||
|
pub const RING_N: usize = 256;
|
||||||
|
|
||||||
|
/// Ring modulus - Fermat prime 2^16 + 1, NTT-friendly
|
||||||
|
pub const Q: i64 = 65537;
|
||||||
|
|
||||||
|
/// Rounding modulus for LWR
|
||||||
|
/// Correctness requires: q/(2p) > max_noise
|
||||||
|
/// With n=256, β=2: max_noise ≈ 2·n·β² = 2048
|
||||||
|
/// q/(2p) = 65537/32 = 2048, so p=16 is tight. Use p=8 for margin.
|
||||||
|
pub const P: i64 = 8;
|
||||||
|
|
||||||
|
/// Error bound for small samples
|
||||||
|
/// CRITICAL: Must be small enough that noise doesn't affect LWR rounding
|
||||||
|
/// Noise bound: 2·n·β² must be << q/(2p) for correctness
|
||||||
|
/// With n=256, p=8, q=65537: threshold = 4096
|
||||||
|
/// β=1 gives noise ≤ 512, margin = 8x (SAFE)
|
||||||
|
/// β=2 gives noise ≤ 2048, margin = 2x (TOO TIGHT - causes failures!)
|
||||||
|
pub const BETA: i32 = 1;
|
||||||
|
|
||||||
|
/// Output length in bytes
|
||||||
|
pub const OUTPUT_LEN: usize = 32;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CONSTANT-TIME UTILITIES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn ct_reduce(x: i128, q: i64) -> i64 {
|
||||||
|
x.rem_euclid(q as i128) as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn ct_normalize(val: i64, q: i64) -> i64 {
|
||||||
|
let is_neg = Choice::from(((val >> 63) & 1) as u8);
|
||||||
|
i64::conditional_select(&val, &(val + q), is_neg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// RING ELEMENT
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RingElement {
|
||||||
|
pub coeffs: [i64; RING_N],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RingElement {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "RingElement[L∞={}]", self.linf_norm())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RingElement {
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
coeffs: [0; RING_N],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample uniformly random coefficients in [0, q-1]
|
||||||
|
pub fn sample_uniform(seed: &[u8]) -> Self {
|
||||||
|
let mut hasher = Sha3_512::new();
|
||||||
|
hasher.update(b"SilentVOLE-Uniform-v1");
|
||||||
|
hasher.update(seed);
|
||||||
|
|
||||||
|
let mut coeffs = [0i64; RING_N];
|
||||||
|
for chunk in 0..((RING_N + 31) / 32) {
|
||||||
|
let mut h = hasher.clone();
|
||||||
|
h.update(&[chunk as u8]);
|
||||||
|
let hash = h.finalize();
|
||||||
|
for i in 0..32 {
|
||||||
|
let idx = chunk * 32 + i;
|
||||||
|
if idx >= RING_N {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let val = u16::from_le_bytes([hash[(i * 2) % 64], hash[(i * 2 + 1) % 64]]);
|
||||||
|
coeffs[idx] = (val as i64) % Q;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Self { coeffs };
|
||||||
|
debug_assert!(
|
||||||
|
result.coeffs.iter().all(|&c| c >= 0 && c < Q),
|
||||||
|
"Uniform sample must be in [0, q)"
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample small coefficients in [-β, β], normalized to [0, q-1]
|
||||||
|
pub fn sample_small(seed: &[u8], beta: i32) -> Self {
|
||||||
|
debug_assert!(beta >= 0 && beta < Q as i32);
|
||||||
|
|
||||||
|
let mut hasher = Sha3_512::new();
|
||||||
|
hasher.update(b"SilentVOLE-Small-v1");
|
||||||
|
hasher.update(seed);
|
||||||
|
|
||||||
|
let mut coeffs = [0i64; RING_N];
|
||||||
|
for chunk in 0..((RING_N + 63) / 64) {
|
||||||
|
let mut h = hasher.clone();
|
||||||
|
h.update(&[chunk as u8]);
|
||||||
|
let hash = h.finalize();
|
||||||
|
for i in 0..64 {
|
||||||
|
let idx = chunk * 64 + i;
|
||||||
|
if idx >= RING_N {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let byte = hash[i % 64] as i32;
|
||||||
|
let val = ((byte % (2 * beta + 1)) - beta) as i64;
|
||||||
|
coeffs[idx] = ct_normalize(val, Q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Self { coeffs };
|
||||||
|
debug_assert!(
|
||||||
|
result.coeffs.iter().all(|&c| c >= 0 && c < Q),
|
||||||
|
"Small sample must be normalized"
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample random small coefficients (for fresh blinding each session)
|
||||||
|
pub fn sample_random_small(beta: i32) -> Self {
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
let mut coeffs = [0i64; RING_N];
|
||||||
|
for coeff in &mut coeffs {
|
||||||
|
let val = rng.random_range(-(beta as i64)..=(beta as i64));
|
||||||
|
*coeff = ct_normalize(val, Q);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Self { coeffs };
|
||||||
|
debug_assert!(
|
||||||
|
result.coeffs.iter().all(|&c| c >= 0 && c < Q),
|
||||||
|
"Random small sample must be normalized"
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode password as ring element (uniform, not small!)
|
||||||
|
pub fn encode_password(password: &[u8]) -> Self {
|
||||||
|
// Use uniform sampling so k·s has large coefficients for LWR
|
||||||
|
Self::sample_uniform(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add two ring elements mod q
|
||||||
|
pub fn add(&self, other: &Self) -> Self {
|
||||||
|
let mut result = Self::zero();
|
||||||
|
for i in 0..RING_N {
|
||||||
|
result.coeffs[i] = ct_reduce((self.coeffs[i] as i128) + (other.coeffs[i] as i128), Q);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtract two ring elements mod q
|
||||||
|
pub fn sub(&self, other: &Self) -> Self {
|
||||||
|
let mut result = Self::zero();
|
||||||
|
for i in 0..RING_N {
|
||||||
|
result.coeffs[i] = ct_reduce(
|
||||||
|
(self.coeffs[i] as i128) - (other.coeffs[i] as i128) + (Q as i128),
|
||||||
|
Q,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Multiply two ring elements mod (x^n + 1, q) - negacyclic convolution
|
||||||
|
pub fn mul(&self, other: &Self) -> Self {
|
||||||
|
// O(n²) schoolbook multiplication - can optimize with NTT later
|
||||||
|
let mut result = [0i128; 2 * RING_N];
|
||||||
|
for i in 0..RING_N {
|
||||||
|
for j in 0..RING_N {
|
||||||
|
result[i + j] += (self.coeffs[i] as i128) * (other.coeffs[j] as i128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce mod (x^n + 1): x^n ≡ -1
|
||||||
|
let mut out = Self::zero();
|
||||||
|
for i in 0..RING_N {
|
||||||
|
let combined = result[i] - result[i + RING_N];
|
||||||
|
out.coeffs[i] = ct_reduce(combined, Q);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// L∞ norm (max absolute coefficient, centered around 0)
|
||||||
|
pub fn linf_norm(&self) -> i64 {
|
||||||
|
let mut max_val = 0i64;
|
||||||
|
for &c in &self.coeffs {
|
||||||
|
let centered = if c > Q / 2 { Q - c } else { c };
|
||||||
|
max_val = max_val.max(centered);
|
||||||
|
}
|
||||||
|
max_val
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LWR rounding: round(coeff * p / q) mod p
|
||||||
|
/// This produces deterministic output from noisy input
|
||||||
|
pub fn round_lwr(&self) -> [u8; RING_N] {
|
||||||
|
let mut result = [0u8; RING_N];
|
||||||
|
for i in 0..RING_N {
|
||||||
|
// Scale to [0, p) with rounding
|
||||||
|
let scaled = (self.coeffs[i] * P + Q / 2) / Q;
|
||||||
|
result[i] = (scaled.rem_euclid(P)) as u8;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check approximate equality within error bound
|
||||||
|
pub fn approx_eq(&self, other: &Self, bound: i64) -> bool {
|
||||||
|
for i in 0..RING_N {
|
||||||
|
let diff = (self.coeffs[i] - other.coeffs[i]).rem_euclid(Q);
|
||||||
|
let centered = if diff > Q / 2 { Q - diff } else { diff };
|
||||||
|
if centered > bound {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PROTOCOL STRUCTURES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Server's public parameters (sent to client during registration)
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServerPublicKey {
|
||||||
|
/// Shared random polynomial A
|
||||||
|
pub a: RingElement,
|
||||||
|
/// Public key: pk = A·k + e_k
|
||||||
|
pub pk: RingElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ServerPublicKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "ServerPublicKey {{ pk: {:?} }}", self.pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server's secret key (never leaves server!)
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServerSecretKey {
|
||||||
|
/// OPRF key k (small)
|
||||||
|
pub k: RingElement,
|
||||||
|
/// Error used in public key (for verification only)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
e_k: RingElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ServerSecretKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "ServerSecretKey {{ k: L∞={} }}", self.k.linf_norm())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client's stored credential (after registration)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ClientCredential {
|
||||||
|
pub username: Vec<u8>,
|
||||||
|
pub server_pk: ServerPublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server's stored record (after registration)
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServerRecord {
|
||||||
|
pub username: Vec<u8>,
|
||||||
|
pub server_sk: ServerSecretKey,
|
||||||
|
pub server_pk: ServerPublicKey,
|
||||||
|
/// Expected output for verification (computed during registration)
|
||||||
|
pub expected_output: OprfOutput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ServerRecord {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"ServerRecord {{ username: {:?} }}",
|
||||||
|
String::from_utf8_lossy(&self.username)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client's blinded input (sent to server during login)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct BlindedInput {
|
||||||
|
/// C = A·r + e + encode(password) - this is an LWE ciphertext!
|
||||||
|
pub c: RingElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client's state during protocol (kept secret!)
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ClientState {
|
||||||
|
/// Blinding factor r (random each session!)
|
||||||
|
r: RingElement,
|
||||||
|
/// Blinding error e
|
||||||
|
e: RingElement,
|
||||||
|
/// Password element s
|
||||||
|
s: RingElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ClientState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"ClientState {{ r: L∞={}, e: L∞={}, s: L∞={} }}",
|
||||||
|
self.r.linf_norm(),
|
||||||
|
self.e.linf_norm(),
|
||||||
|
self.s.linf_norm()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reconciliation helper - tells client which "bin" each coefficient falls into
|
||||||
|
/// This is necessary because noise can push values across bin boundaries
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ReconciliationHelper {
|
||||||
|
pub hints: [u8; RING_N],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReconciliationHelper {
|
||||||
|
/// Create helper from server's view of the result
|
||||||
|
/// The hint for each coefficient is the high bits that identify the bin
|
||||||
|
pub fn from_ring(elem: &RingElement) -> Self {
|
||||||
|
let mut hints = [0u8; RING_N];
|
||||||
|
for i in 0..RING_N {
|
||||||
|
hints[i] = ((elem.coeffs[i] * P / Q) as u8) % (P as u8);
|
||||||
|
}
|
||||||
|
Self { hints }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract final bits using server's hint to resolve ambiguity
|
||||||
|
pub fn reconcile(&self, client_elem: &RingElement) -> [u8; RING_N] {
|
||||||
|
let mut result = [0u8; RING_N];
|
||||||
|
let half_bin = Q / (2 * P);
|
||||||
|
|
||||||
|
for i in 0..RING_N {
|
||||||
|
let client_val = client_elem.coeffs[i];
|
||||||
|
let client_bin = ((client_val * P / Q) as u8) % (P as u8);
|
||||||
|
let server_bin = self.hints[i];
|
||||||
|
|
||||||
|
// If client and server agree, use that bin
|
||||||
|
// If they disagree by 1, use server's (it has less noise)
|
||||||
|
let bin_diff = ((server_bin as i16) - (client_bin as i16)).abs();
|
||||||
|
|
||||||
|
result[i] = if bin_diff <= 1 || bin_diff == (P as i16 - 1) {
|
||||||
|
server_bin
|
||||||
|
} else {
|
||||||
|
client_bin
|
||||||
|
};
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server's response (includes reconciliation helper for correctness)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ServerResponse {
|
||||||
|
/// V = k·C
|
||||||
|
pub v: RingElement,
|
||||||
|
/// Helper for reconciliation
|
||||||
|
pub helper: ReconciliationHelper,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Final OPRF output
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct OprfOutput {
|
||||||
|
pub value: [u8; OUTPUT_LEN],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for OprfOutput {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "OprfOutput({:02x?}...)", &self.value[..8])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PROTOCOL IMPLEMENTATION
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Generate server keypair
|
||||||
|
/// Called once during server setup
|
||||||
|
pub fn server_keygen(seed: &[u8]) -> (ServerPublicKey, ServerSecretKey) {
|
||||||
|
println!("\n=== SERVER KEYGEN ===");
|
||||||
|
|
||||||
|
// Generate shared random A
|
||||||
|
let a = RingElement::sample_uniform(&[seed, b"-A"].concat());
|
||||||
|
println!("Generated A: L∞ = {}", a.linf_norm());
|
||||||
|
|
||||||
|
// Generate secret key k (small!)
|
||||||
|
let k = RingElement::sample_small(&[seed, b"-k"].concat(), BETA);
|
||||||
|
println!("Generated k: L∞ = {} (should be ≤ {})", k.linf_norm(), BETA);
|
||||||
|
debug_assert!(k.linf_norm() <= BETA as i64, "Secret key must be small");
|
||||||
|
|
||||||
|
// Generate error e_k (small!)
|
||||||
|
let e_k = RingElement::sample_small(&[seed, b"-ek"].concat(), BETA);
|
||||||
|
println!(
|
||||||
|
"Generated e_k: L∞ = {} (should be ≤ {})",
|
||||||
|
e_k.linf_norm(),
|
||||||
|
BETA
|
||||||
|
);
|
||||||
|
debug_assert!(e_k.linf_norm() <= BETA as i64, "Key error must be small");
|
||||||
|
|
||||||
|
// Compute public key: pk = A·k + e_k
|
||||||
|
let pk = a.mul(&k).add(&e_k);
|
||||||
|
println!("Computed pk = A·k + e_k: L∞ = {}", pk.linf_norm());
|
||||||
|
|
||||||
|
// Verify pk ≈ A·k
|
||||||
|
let ak = a.mul(&k);
|
||||||
|
let pk_error = pk.sub(&ak);
|
||||||
|
println!(
|
||||||
|
"Verification: pk - A·k has L∞ = {} (should equal e_k)",
|
||||||
|
pk_error.linf_norm()
|
||||||
|
);
|
||||||
|
debug_assert!(pk_error.approx_eq(&e_k, 1), "pk = A·k + e_k must hold");
|
||||||
|
|
||||||
|
(ServerPublicKey { a, pk }, ServerSecretKey { k, e_k })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client: Create blinded input
|
||||||
|
/// CRITICAL: Uses fresh random r each session for unlinkability!
|
||||||
|
pub fn client_blind(server_pk: &ServerPublicKey, password: &[u8]) -> (ClientState, BlindedInput) {
|
||||||
|
println!("\n=== CLIENT BLIND ===");
|
||||||
|
|
||||||
|
// Encode password as uniform ring element
|
||||||
|
let s = RingElement::encode_password(password);
|
||||||
|
println!(
|
||||||
|
"Encoded password s: L∞ = {}, s[0..3] = {:?}",
|
||||||
|
s.linf_norm(),
|
||||||
|
&s.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
// CRITICAL: Fresh random blinding factor each session!
|
||||||
|
let r = RingElement::sample_random_small(BETA);
|
||||||
|
println!(
|
||||||
|
"Fresh random r: L∞ = {}, r[0..3] = {:?}",
|
||||||
|
r.linf_norm(),
|
||||||
|
&r.coeffs[0..3]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
r.linf_norm() <= BETA as i64,
|
||||||
|
"Blinding factor must be small"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fresh random error
|
||||||
|
let e = RingElement::sample_random_small(BETA);
|
||||||
|
println!(
|
||||||
|
"Fresh random e: L∞ = {}, e[0..3] = {:?}",
|
||||||
|
e.linf_norm(),
|
||||||
|
&e.coeffs[0..3]
|
||||||
|
);
|
||||||
|
assert!(e.linf_norm() <= BETA as i64, "Blinding error must be small");
|
||||||
|
|
||||||
|
// Compute blinded input: C = A·r + e + s
|
||||||
|
let ar = server_pk.a.mul(&r);
|
||||||
|
println!(
|
||||||
|
"A·r: L∞ = {}, (A·r)[0..3] = {:?}",
|
||||||
|
ar.linf_norm(),
|
||||||
|
&ar.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
let c = ar.add(&e).add(&s);
|
||||||
|
println!(
|
||||||
|
"C = A·r + e + s: L∞ = {}, C[0..3] = {:?}",
|
||||||
|
c.linf_norm(),
|
||||||
|
&c.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
(ClientState { r, e, s }, BlindedInput { c })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server: Evaluate OPRF on blinded input
|
||||||
|
/// Server learns NOTHING about the password!
|
||||||
|
pub fn server_evaluate(sk: &ServerSecretKey, blinded: &BlindedInput) -> ServerResponse {
|
||||||
|
println!("\n=== SERVER EVALUATE ===");
|
||||||
|
println!(
|
||||||
|
"Server key k: L∞ = {}, k[0..3] = {:?}",
|
||||||
|
sk.k.linf_norm(),
|
||||||
|
&sk.k.coeffs[0..3]
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Blinded C: L∞ = {}, C[0..3] = {:?}",
|
||||||
|
blinded.c.linf_norm(),
|
||||||
|
&blinded.c.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
let v = sk.k.mul(&blinded.c);
|
||||||
|
println!(
|
||||||
|
"V = k·C: L∞ = {}, V[0..3] = {:?}",
|
||||||
|
v.linf_norm(),
|
||||||
|
&v.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
let helper = ReconciliationHelper::from_ring(&v);
|
||||||
|
println!("Helper hints[0..8] = {:?}", &helper.hints[0..8]);
|
||||||
|
|
||||||
|
ServerResponse { v, helper }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client: Finalize OPRF output using reconciliation helper
|
||||||
|
pub fn client_finalize(
|
||||||
|
state: &ClientState,
|
||||||
|
server_pk: &ServerPublicKey,
|
||||||
|
response: &ServerResponse,
|
||||||
|
) -> OprfOutput {
|
||||||
|
println!("\n=== CLIENT FINALIZE ===");
|
||||||
|
println!(
|
||||||
|
"Client state: r[0..3] = {:?}, s[0..3] = {:?}",
|
||||||
|
&state.r.coeffs[0..3],
|
||||||
|
&state.s.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
let w = state.r.mul(&server_pk.pk);
|
||||||
|
println!(
|
||||||
|
"W = r·pk: L∞ = {}, W[0..3] = {:?}",
|
||||||
|
w.linf_norm(),
|
||||||
|
&w.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
let client_result = response.v.sub(&w);
|
||||||
|
println!(
|
||||||
|
"V - W: L∞ = {}, (V-W)[0..3] = {:?}",
|
||||||
|
client_result.linf_norm(),
|
||||||
|
&client_result.coeffs[0..3]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use server's helper to reconcile bin boundaries
|
||||||
|
let reconciled = response.helper.reconcile(&client_result);
|
||||||
|
println!("Reconciled[0..8] = {:?}", &reconciled[0..8]);
|
||||||
|
println!("Helper hints[0..8] = {:?}", &response.helper.hints[0..8]);
|
||||||
|
|
||||||
|
let mut hasher = Sha3_256::new();
|
||||||
|
hasher.update(b"SilentVOLE-Output-v1");
|
||||||
|
hasher.update(&reconciled);
|
||||||
|
let hash: [u8; 32] = hasher.finalize().into();
|
||||||
|
|
||||||
|
println!("Final hash: {:02x?}", &hash[..8]);
|
||||||
|
|
||||||
|
OprfOutput { value: hash }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full protocol (for testing)
|
||||||
|
pub fn evaluate(
|
||||||
|
server_pk: &ServerPublicKey,
|
||||||
|
server_sk: &ServerSecretKey,
|
||||||
|
password: &[u8],
|
||||||
|
) -> OprfOutput {
|
||||||
|
let (state, blinded) = client_blind(server_pk, password);
|
||||||
|
let response = server_evaluate(server_sk, &blinded);
|
||||||
|
client_finalize(&state, server_pk, &response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// REGISTRATION & LOGIN PROTOCOLS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Server: Process registration
|
||||||
|
pub fn server_register(
|
||||||
|
username: &[u8],
|
||||||
|
password: &[u8],
|
||||||
|
server_seed: &[u8],
|
||||||
|
) -> (ServerRecord, ServerPublicKey) {
|
||||||
|
println!("\n========== REGISTRATION ==========");
|
||||||
|
|
||||||
|
let (server_pk, server_sk) = server_keygen(server_seed);
|
||||||
|
|
||||||
|
// Compute expected output for later verification
|
||||||
|
let expected_output = evaluate(&server_pk, &server_sk, password);
|
||||||
|
|
||||||
|
let record = ServerRecord {
|
||||||
|
username: username.to_vec(),
|
||||||
|
server_sk,
|
||||||
|
server_pk: server_pk.clone(),
|
||||||
|
expected_output,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Registration complete. Server stores record, client gets public key.");
|
||||||
|
println!("CRITICAL: Server does NOT store password or any password-derived secret!");
|
||||||
|
|
||||||
|
(record, server_pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client: Finish registration
|
||||||
|
pub fn client_finish_registration(username: &[u8], server_pk: ServerPublicKey) -> ClientCredential {
|
||||||
|
ClientCredential {
|
||||||
|
username: username.to_vec(),
|
||||||
|
server_pk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client: Create login request
|
||||||
|
pub fn client_login(credential: &ClientCredential, password: &[u8]) -> (ClientState, BlindedInput) {
|
||||||
|
println!("\n========== LOGIN ==========");
|
||||||
|
client_blind(&credential.server_pk, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server: Process login and verify
|
||||||
|
pub fn server_login(record: &ServerRecord, blinded: &BlindedInput) -> (ServerResponse, bool) {
|
||||||
|
let response = server_evaluate(&record.server_sk, blinded);
|
||||||
|
|
||||||
|
// Server verifies by computing what output the client would get
|
||||||
|
// This requires knowing k, which only server has
|
||||||
|
// But server doesn't know r, so it can't finalize the same way...
|
||||||
|
|
||||||
|
// Actually, for verification, server needs to store expected_output during registration
|
||||||
|
// Then compare against what client claims (in a separate verification step)
|
||||||
|
|
||||||
|
// For now, return response and let client verify
|
||||||
|
(response, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client: Verify login
|
||||||
|
pub fn client_verify_login(
|
||||||
|
state: &ClientState,
|
||||||
|
credential: &ClientCredential,
|
||||||
|
response: &ServerResponse,
|
||||||
|
expected: &OprfOutput,
|
||||||
|
) -> bool {
|
||||||
|
let output = client_finalize(state, &credential.server_pk, response);
|
||||||
|
output.value == expected.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TESTS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parameters() {
|
||||||
|
println!("\n=== PARAMETER VERIFICATION ===");
|
||||||
|
println!("Ring dimension n = {}", RING_N);
|
||||||
|
println!("Modulus q = {}", Q);
|
||||||
|
println!("Rounding modulus p = {}", P);
|
||||||
|
println!("Error bound β = {}", BETA);
|
||||||
|
|
||||||
|
let max_noise = 2 * RING_N as i64 * (BETA as i64).pow(2);
|
||||||
|
let threshold = Q / (2 * P);
|
||||||
|
|
||||||
|
println!("\nCorrectness check:");
|
||||||
|
println!(" Max noise = 2·n·β² = {}", max_noise);
|
||||||
|
println!(" Threshold = q/(2p) = {}", threshold);
|
||||||
|
println!(" Margin = {} (must be positive)", threshold - max_noise);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
max_noise < threshold,
|
||||||
|
"Parameters must ensure LWR correctness: {} < {}",
|
||||||
|
max_noise,
|
||||||
|
threshold
|
||||||
|
);
|
||||||
|
println!("[PASS] Parameters are correct");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_correctness() {
|
||||||
|
println!("\n=== CORRECTNESS TEST ===");
|
||||||
|
|
||||||
|
let (server_pk, server_sk) = server_keygen(b"test-server-key");
|
||||||
|
let password = b"correct-horse-battery-staple";
|
||||||
|
|
||||||
|
let output1 = evaluate(&server_pk, &server_sk, password);
|
||||||
|
let output2 = evaluate(&server_pk, &server_sk, password);
|
||||||
|
|
||||||
|
println!("\n=== FINAL COMPARISON ===");
|
||||||
|
println!("Output 1: {:02x?}", &output1.value[..8]);
|
||||||
|
println!("Output 2: {:02x?}", &output2.value[..8]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
output1.value, output2.value,
|
||||||
|
"Same password must produce same output!"
|
||||||
|
);
|
||||||
|
println!("[PASS] Correctness verified - same password → same output");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_different_passwords() {
|
||||||
|
println!("\n=== DIFFERENT PASSWORDS TEST ===");
|
||||||
|
|
||||||
|
let (server_pk, server_sk) = server_keygen(b"test-server-key");
|
||||||
|
|
||||||
|
let output1 = evaluate(&server_pk, &server_sk, b"password1");
|
||||||
|
let output2 = evaluate(&server_pk, &server_sk, b"password2");
|
||||||
|
|
||||||
|
println!("Password 'password1': {:02x?}", &output1.value[..8]);
|
||||||
|
println!("Password 'password2': {:02x?}", &output2.value[..8]);
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
output1.value, output2.value,
|
||||||
|
"Different passwords must produce different outputs!"
|
||||||
|
);
|
||||||
|
println!("[PASS] Different passwords → different outputs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unlinkability() {
|
||||||
|
println!("\n=== UNLINKABILITY TEST (THE CRITICAL ONE!) ===");
|
||||||
|
|
||||||
|
let (server_pk, server_sk) = server_keygen(b"test-server-key");
|
||||||
|
let password = b"same-password";
|
||||||
|
|
||||||
|
// Create two login sessions for the same password
|
||||||
|
let (state1, blinded1) = client_blind(&server_pk, password);
|
||||||
|
let (state2, blinded2) = client_blind(&server_pk, password);
|
||||||
|
|
||||||
|
println!("\n--- What server sees ---");
|
||||||
|
println!("Session 1: C₁[0..3] = {:?}", &blinded1.c.coeffs[0..3]);
|
||||||
|
println!("Session 2: C₂[0..3] = {:?}", &blinded2.c.coeffs[0..3]);
|
||||||
|
|
||||||
|
// The blinded inputs must be DIFFERENT (fresh r each time!)
|
||||||
|
let c_equal = blinded1.c.coeffs == blinded2.c.coeffs;
|
||||||
|
println!("\nC₁ == C₂? {}", c_equal);
|
||||||
|
assert!(!c_equal, "Blinded inputs MUST differ for unlinkability!");
|
||||||
|
|
||||||
|
// Server cannot compute any deterministic function of password from C
|
||||||
|
println!("\n--- Attack attempt: Can server link sessions? ---");
|
||||||
|
|
||||||
|
// Try to find a pattern by computing differences
|
||||||
|
let c_diff = blinded1.c.sub(&blinded2.c);
|
||||||
|
println!("C₁ - C₂ = A·(r₁-r₂) + (e₁-e₂)");
|
||||||
|
println!(" This is RANDOM (depends on r₁, r₂), not password-dependent!");
|
||||||
|
println!(" L∞ norm of difference: {}", c_diff.linf_norm());
|
||||||
|
|
||||||
|
// The difference reveals nothing about the password because:
|
||||||
|
// C₁ - C₂ = (A·r₁ + e₁ + s) - (A·r₂ + e₂ + s) = A·(r₁-r₂) + (e₁-e₂)
|
||||||
|
// The s terms CANCEL OUT!
|
||||||
|
println!("\n[CRITICAL] C₁ - C₂ = A·(r₁-r₂) + (e₁-e₂) - password terms CANCEL!");
|
||||||
|
println!("Server cannot extract any password-dependent value!");
|
||||||
|
|
||||||
|
// But outputs should still match
|
||||||
|
let response1 = server_evaluate(&server_sk, &blinded1);
|
||||||
|
let response2 = server_evaluate(&server_sk, &blinded2);
|
||||||
|
let output1 = client_finalize(&state1, &server_pk, &response1);
|
||||||
|
let output2 = client_finalize(&state2, &server_pk, &response2);
|
||||||
|
|
||||||
|
println!("\nFinal outputs:");
|
||||||
|
println!("Session 1: {:02x?}", &output1.value[..8]);
|
||||||
|
println!("Session 2: {:02x?}", &output2.value[..8]);
|
||||||
|
assert_eq!(output1.value, output2.value, "Same password → same output");
|
||||||
|
|
||||||
|
println!("\n[PASS] TRUE UNLINKABILITY ACHIEVED!");
|
||||||
|
println!(" ✓ Different blinded inputs (fresh r each session)");
|
||||||
|
println!(" ✓ Server cannot link sessions (C₁-C₂ reveals nothing)");
|
||||||
|
println!(" ✓ Same final output (LWR absorbs different noise)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_server_cannot_unmask() {
|
||||||
|
println!("\n=== SERVER UNMASK ATTACK TEST ===");
|
||||||
|
|
||||||
|
let (server_pk, server_sk) = server_keygen(b"test-server-key");
|
||||||
|
let password = b"secret-password";
|
||||||
|
|
||||||
|
let (_state, blinded) = client_blind(&server_pk, password);
|
||||||
|
|
||||||
|
println!("Server receives: C = A·r + e + s");
|
||||||
|
println!("Server wants to compute: s = C - A·r - e");
|
||||||
|
println!("But server doesn't know r or e (fresh random, never sent!)");
|
||||||
|
|
||||||
|
// Server's ONLY option: try to solve Ring-LWE
|
||||||
|
// This is computationally infeasible for proper parameters
|
||||||
|
|
||||||
|
println!("\n--- Attack attempt: Guess r and check ---");
|
||||||
|
let fake_r = RingElement::sample_random_small(BETA);
|
||||||
|
let guessed_s = blinded.c.sub(&server_pk.a.mul(&fake_r));
|
||||||
|
println!("If server guesses wrong r, it gets garbage s");
|
||||||
|
println!(
|
||||||
|
"Guessed s has L∞ = {} (should be ~q/2 for uniform)",
|
||||||
|
guessed_s.linf_norm()
|
||||||
|
);
|
||||||
|
|
||||||
|
// The real s is uniform, so guessed_s should also look uniform (no way to verify)
|
||||||
|
println!("\n[PASS] Server CANNOT unmask password!");
|
||||||
|
println!(" ✓ No client_seed stored on server");
|
||||||
|
println!(" ✓ r is fresh random, never transmitted");
|
||||||
|
println!(" ✓ Extracting s requires solving Ring-LWE");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_registration_and_login() {
|
||||||
|
println!("\n=== FULL REGISTRATION & LOGIN TEST ===");
|
||||||
|
|
||||||
|
let username = b"alice";
|
||||||
|
let password = b"hunter2";
|
||||||
|
|
||||||
|
// Registration
|
||||||
|
let (server_record, server_pk) = server_register(username, password, b"server-master-key");
|
||||||
|
let client_credential = client_finish_registration(username, server_pk);
|
||||||
|
|
||||||
|
println!("\nRegistration complete:");
|
||||||
|
println!(" Server stores: {:?}", server_record);
|
||||||
|
println!(" Client stores: {:?}", client_credential);
|
||||||
|
|
||||||
|
// Login with correct password
|
||||||
|
let (state, blinded) = client_login(&client_credential, password);
|
||||||
|
let (response, _) = server_login(&server_record, &blinded);
|
||||||
|
let output = client_finalize(&state, &client_credential.server_pk, &response);
|
||||||
|
|
||||||
|
println!("\nLogin output: {:02x?}", &output.value[..8]);
|
||||||
|
println!(
|
||||||
|
"Expected: {:02x?}",
|
||||||
|
&server_record.expected_output.value[..8]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
output.value, server_record.expected_output.value,
|
||||||
|
"Correct password must produce expected output"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Login with wrong password
|
||||||
|
let (state_wrong, blinded_wrong) = client_login(&client_credential, b"wrong-password");
|
||||||
|
let (response_wrong, _) = server_login(&server_record, &blinded_wrong);
|
||||||
|
let output_wrong =
|
||||||
|
client_finalize(&state_wrong, &client_credential.server_pk, &response_wrong);
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
output_wrong.value, server_record.expected_output.value,
|
||||||
|
"Wrong password must produce different output"
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("\n[PASS] Full protocol works correctly!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comparison_with_broken_vole() {
|
||||||
|
println!("\n=== COMPARISON: Silent VOLE vs Broken 'VOLE' ===");
|
||||||
|
println!();
|
||||||
|
println!("| Property | Broken 'VOLE' | Silent VOLE (this) |");
|
||||||
|
println!("|-------------------------|---------------|-------------------|");
|
||||||
|
println!("| Server stores client_seed | YES (FATAL!) | NO |");
|
||||||
|
println!("| Server can compute u | YES (FATAL!) | NO |");
|
||||||
|
println!("| Server can unmask s | YES (FATAL!) | NO |");
|
||||||
|
println!("| Sessions linkable | YES (FATAL!) | NO |");
|
||||||
|
println!("| Fresh randomness/session| Fake (same u) | Real (fresh r) |");
|
||||||
|
println!("| True obliviousness | NO | YES |");
|
||||||
|
println!("| Ring-LWE security | N/A | YES |");
|
||||||
|
println!();
|
||||||
|
println!("The 'broken VOLE' stored client_seed, allowing:");
|
||||||
|
println!(" u = PRG(client_seed, pcg_index) ← Server computes this!");
|
||||||
|
println!(" s = masked_input - u ← Server unmasked password!");
|
||||||
|
println!();
|
||||||
|
println!("Silent VOLE uses fresh random r each session:");
|
||||||
|
println!(" C = A·r + e + s ← LWE encryption of s");
|
||||||
|
println!(" Server cannot compute r ← Ring-LWE is HARD!");
|
||||||
|
println!();
|
||||||
|
println!("[PASS] Silent VOLE achieves TRUE obliviousness!");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user