279 lines
8.6 KiB
Rust
279 lines
8.6 KiB
Rust
//! DudeCT-based timing verification for constant-time operations.
|
|
//!
|
|
//! 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 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
|
|
}
|
|
|
|
const NUM_SAMPLES: usize = 10_000;
|
|
|
|
fn timing_ring_mul(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let mut inputs: Vec<(RingElement, RingElement)> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let mut coeffs_a = [0i32; RING_N];
|
|
let mut coeffs_b = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs_a[i] = 0;
|
|
coeffs_b[i] = 0;
|
|
}
|
|
inputs.push((
|
|
RingElement { coeffs: coeffs_a },
|
|
RingElement { coeffs: coeffs_b },
|
|
));
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut coeffs_a = [0i32; RING_N];
|
|
let mut coeffs_b = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs_a[i] = rng.gen_range(0..Q);
|
|
coeffs_b[i] = rng.gen_range(0..Q);
|
|
}
|
|
inputs.push((
|
|
RingElement { coeffs: coeffs_a },
|
|
RingElement { coeffs: coeffs_b },
|
|
));
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, (a, b)) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || a.mul(&b));
|
|
}
|
|
}
|
|
|
|
fn timing_linf_norm(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let mut inputs: Vec<RingElement> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let coeffs = [0i32; RING_N];
|
|
inputs.push(RingElement { coeffs });
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = rng.gen_range(0..Q);
|
|
}
|
|
inputs.push(RingElement { coeffs });
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, elem) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || elem.linf_norm());
|
|
}
|
|
}
|
|
|
|
fn timing_round_to_binary(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let mut inputs: Vec<RingElement> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = Q / 4;
|
|
}
|
|
inputs.push(RingElement { coeffs });
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = (3 * Q) / 4;
|
|
}
|
|
inputs.push(RingElement { coeffs });
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, elem) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || elem.round_to_binary());
|
|
}
|
|
}
|
|
|
|
fn timing_ring_eq(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let mut inputs: Vec<(RingElement, RingElement)> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let coeffs = [0i32; RING_N];
|
|
inputs.push((RingElement { coeffs }, RingElement { coeffs }));
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut coeffs_a = [0i32; RING_N];
|
|
let mut coeffs_b = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs_a[i] = rng.gen_range(0..Q);
|
|
coeffs_b[i] = rng.gen_range(0..Q);
|
|
}
|
|
inputs.push((
|
|
RingElement { coeffs: coeffs_a },
|
|
RingElement { coeffs: coeffs_b },
|
|
));
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, (a, b)) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || a.eq(&b));
|
|
}
|
|
}
|
|
|
|
fn timing_extract_bits(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let mut inputs: Vec<(ReconciliationHelper, RingElement)> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = Q / 8;
|
|
}
|
|
let elem = RingElement { coeffs };
|
|
let helper = ReconciliationHelper::from_ring(&elem);
|
|
inputs.push((helper, elem));
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = (7 * Q) / 8;
|
|
}
|
|
let elem = RingElement { coeffs };
|
|
let helper = ReconciliationHelper::from_ring(&elem);
|
|
inputs.push((helper, elem));
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, (helper, elem)) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || helper.extract_bits(&elem));
|
|
}
|
|
}
|
|
|
|
fn timing_server_bits(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let mut inputs: Vec<RingElement> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = Q / 4;
|
|
}
|
|
inputs.push(RingElement { coeffs });
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut coeffs = [0i32; RING_N];
|
|
for i in 0..RING_N {
|
|
coeffs[i] = (3 * Q) / 4;
|
|
}
|
|
inputs.push(RingElement { coeffs });
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, elem) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || ReconciliationHelper::server_bits(&elem));
|
|
}
|
|
}
|
|
|
|
fn timing_client_blind(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let pp = PublicParams::generate(b"timing-test-seed");
|
|
let mut inputs: Vec<Vec<u8>> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
inputs.push(vec![0u8; 16]);
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut pwd = vec![0u8; 16];
|
|
rng.fill(&mut pwd[..]);
|
|
inputs.push(pwd);
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, password) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || client_blind(&pp, &password));
|
|
}
|
|
}
|
|
|
|
fn timing_server_evaluate(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let pp = PublicParams::generate(b"timing-test-seed");
|
|
let key = ServerKey::generate(&pp, b"timing-test-key");
|
|
let mut inputs = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
let (_, blinded) = client_blind(&pp, &[0u8; 16]);
|
|
inputs.push(blinded);
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut pwd = [0u8; 16];
|
|
rng.fill(&mut pwd[..]);
|
|
let (_, blinded) = client_blind(&pp, &pwd);
|
|
inputs.push(blinded);
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, blinded) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || server_evaluate(&key, &blinded));
|
|
}
|
|
}
|
|
|
|
fn timing_full_protocol(runner: &mut CtRunner, rng: &mut BenchRng) {
|
|
let pp = PublicParams::generate(b"timing-test-seed");
|
|
let key = ServerKey::generate(&pp, b"timing-test-key");
|
|
let mut inputs: Vec<Vec<u8>> = Vec::new();
|
|
let mut classes = Vec::new();
|
|
|
|
for _ in 0..NUM_SAMPLES {
|
|
if coin_flip(rng) {
|
|
inputs.push(vec![0u8; 16]);
|
|
classes.push(Class::Left);
|
|
} else {
|
|
let mut pwd = vec![0u8; 16];
|
|
rng.fill(&mut pwd[..]);
|
|
inputs.push(pwd);
|
|
classes.push(Class::Right);
|
|
}
|
|
}
|
|
|
|
for (class, password) in classes.into_iter().zip(inputs.into_iter()) {
|
|
runner.run_one(class, || {
|
|
let (state, blinded) = client_blind(&pp, &password);
|
|
let response = server_evaluate(&key, &blinded);
|
|
client_finalize(&state, &key.b, &response)
|
|
});
|
|
}
|
|
}
|
|
|
|
ctbench_main!(
|
|
timing_ring_mul,
|
|
timing_linf_norm,
|
|
timing_round_to_binary,
|
|
timing_ring_eq,
|
|
timing_extract_bits,
|
|
timing_server_bits,
|
|
timing_client_blind,
|
|
timing_server_evaluate,
|
|
timing_full_protocol
|
|
);
|