pub struct TimingOracle { /* private fields */ }Expand description
Main entry point for adaptive Bayesian timing analysis.
Use the builder pattern to configure and run timing tests. The oracle uses a two-phase approach:
- Calibration: Collect initial samples to estimate covariance and priors
- Adaptive loop: Collect batches until decision thresholds are reached
§Example
use tacet::{TimingOracle, AttackerModel, helpers::InputPair, Outcome};
let inputs = InputPair::new(
|| [0u8; 32], // baseline: returns constant value
|| rand::random(), // sample: generates varied values
);
// Choose attacker model based on your threat scenario
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.test(inputs, |data| my_function(data));
match outcome {
Outcome::Pass { leak_probability, .. } => {
println!("No leak detected (P={:.1}%)", leak_probability * 100.0);
}
Outcome::Fail { leak_probability, exploitability, .. } => {
println!("Leak detected! P={:.1}%, {:?}", leak_probability * 100.0, exploitability);
}
Outcome::Inconclusive { reason, .. } => {
println!("Inconclusive: {:?}", reason);
}
Outcome::Unmeasurable { recommendation, .. } => {
println!("Operation too fast: {}", recommendation);
}
}§Automatic PMU Detection
When running with sudo/root privileges, the library automatically uses cycle-accurate PMU timing (kperf on macOS, perf_event on Linux). No code changes needed - just run with sudo.
§Attacker Model Presets
Choose the appropriate attacker model for your threat scenario:
| Preset | theta | Use case |
|---|---|---|
SharedHardware | ~0.6ns | SGX, cross-VM, containers |
AdjacentNetwork | 100ns | LAN, HTTP/2 endpoints |
RemoteNetwork | 50us | Public APIs, general internet |
Research | 0 | Academic analysis (not for CI) |
Implementations§
Source§impl TimingOracle
impl TimingOracle
Sourcepub fn for_attacker(model: AttackerModel) -> Self
pub fn for_attacker(model: AttackerModel) -> Self
Create with an attacker model preset.
The attacker model determines the minimum effect threshold (theta) that is considered practically significant. Different attacker models represent different threat scenarios with varying capabilities.
§Example
use tacet::{TimingOracle, AttackerModel};
// For public APIs exposed to the internet
let oracle = TimingOracle::for_attacker(AttackerModel::RemoteNetwork);
// For internal LAN services or HTTP/2 endpoints
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork);
// For SGX enclaves or shared hosting (most strict)
let oracle = TimingOracle::for_attacker(AttackerModel::SharedHardware);§Presets
| Preset | theta | Use case |
|---|---|---|
SharedHardware | ~0.6ns | SGX, cross-VM, containers |
AdjacentNetwork | 100ns | LAN, HTTP/2 endpoints |
RemoteNetwork | 50us | Public APIs, general internet |
Research | 0 | Academic analysis (not for CI) |
Sourcepub fn timer_spec(self, spec: TimerSpec) -> Self
pub fn timer_spec(self, spec: TimerSpec) -> Self
Set the timer specification.
Controls which timer implementation is used:
TimerSpec::Auto(default): Try cycle-accurate timer first, fall back to system timerTimerSpec::SystemTimer: Always use system timer (rdtsc on x86_64, cntvct_el0 on ARM64)TimerSpec::RequireCycleAccurate: Require cycle-accurate timing or panic
§Example
use tacet::{TimingOracle, AttackerModel, TimerSpec};
// Force system timer (no cycle-accurate timing)
let result = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.timer_spec(TimerSpec::SystemTimer)
.test(...);Sourcepub fn system_timer(self) -> Self
pub fn system_timer(self) -> Self
Use the system timer only (no cycle-accurate timing).
Shorthand for .timer_spec(TimerSpec::SystemTimer).
Sourcepub fn require_high_precision(self) -> Self
pub fn require_high_precision(self) -> Self
Require high-precision timing (≤2ns resolution).
Shorthand for .timer_spec(TimerSpec::RequireHighPrecision).
Uses runtime detection: system timer if sufficient, else PMU timer.
Panics if no high-precision timer is available.
Sourcepub fn require_cycle_accurate(self) -> Self
pub fn require_cycle_accurate(self) -> Self
Require cycle-accurate timing.
Shorthand for .timer_spec(TimerSpec::RequireCycleAccurate).
Panics if cycle-accurate timing is unavailable.
Sourcepub fn time_budget(self, duration: Duration) -> Self
pub fn time_budget(self, duration: Duration) -> Self
Set the time budget for the adaptive sampling loop.
The oracle will stop and return Inconclusive if this time limit is
reached without achieving a conclusive result.
Default: 60 seconds
§Example
use tacet::{TimingOracle, AttackerModel};
use std::time::Duration;
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.time_budget(Duration::from_secs(30));Sourcepub fn time_budget_secs(self, secs: u64) -> Self
pub fn time_budget_secs(self, secs: u64) -> Self
Set the time budget in seconds.
Convenience method for .time_budget(Duration::from_secs(secs)).
Sourcepub fn max_samples(self, n: usize) -> Self
pub fn max_samples(self, n: usize) -> Self
Set the maximum number of samples per class.
The oracle will stop and return Inconclusive if this limit is reached
without achieving a conclusive result.
Default: 1,000,000
§Panics
Panics if n is 0.
Sourcepub fn batch_size(self, n: usize) -> Self
pub fn batch_size(self, n: usize) -> Self
Set the batch size for adaptive sampling.
Larger batches are more efficient but less responsive to early stopping.
Default: 1,000
§Panics
Panics if n is 0.
Sourcepub fn calibration_samples(self, n: usize) -> Self
pub fn calibration_samples(self, n: usize) -> Self
Set the number of calibration samples.
These samples are collected at the start to estimate the covariance matrix and set Bayesian priors. This is a fixed overhead.
Default: 5,000
§Panics
Panics if n is 0.
Sourcepub fn pass_threshold(self, threshold: f64) -> Self
pub fn pass_threshold(self, threshold: f64) -> Self
Set the pass threshold for leak probability.
If the posterior probability of a timing leak falls below this threshold, the test passes. Default: 0.05 (5%).
Lower values require more confidence to pass (more conservative).
§Panics
Panics if threshold is not in (0, 1) or >= fail_threshold.
Sourcepub fn fail_threshold(self, threshold: f64) -> Self
pub fn fail_threshold(self, threshold: f64) -> Self
Set the fail threshold for leak probability.
If the posterior probability of a timing leak exceeds this threshold, the test fails. Default: 0.95 (95%).
Higher values require more confidence to fail (more conservative).
§Panics
Panics if threshold is not in (0, 1) or <= pass_threshold.
Sourcepub fn warmup(self, n: usize) -> Self
pub fn warmup(self, n: usize) -> Self
Set warmup iterations.
Warmup iterations warm CPU caches, stabilize frequency scaling, and trigger any JIT compilation before measurement begins.
Default: 1,000
Sourcepub fn cov_bootstrap_iterations(self, n: usize) -> Self
pub fn cov_bootstrap_iterations(self, n: usize) -> Self
Set bootstrap iterations for covariance estimation.
Used during calibration to estimate the noise covariance matrix. More iterations give better estimates but take longer.
Default: 2,000
§Panics
Panics if n is 0.
Sourcepub fn outlier_percentile(self, p: f64) -> Self
pub fn outlier_percentile(self, p: f64) -> Self
Set outlier filtering percentile.
Must be in the range (0, 1]. Set to 1.0 to disable filtering.
§Panics
Panics if p is not in the range (0, 1].
Sourcepub fn prior_no_leak(self, p: f64) -> Self
pub fn prior_no_leak(self, p: f64) -> Self
Set prior probability of no leak.
Must be in the range (0, 1).
§Panics
Panics if p is not in the range (0, 1).
Sourcepub fn force_discrete_mode(self, force: bool) -> Self
pub fn force_discrete_mode(self, force: bool) -> Self
Force discrete mode for testing.
When set to true, the oracle uses discrete mode (m-out-of-n bootstrap
with mid-quantiles) regardless of actual timer resolution. This is
primarily useful for testing the discrete mode code path on machines
with high-resolution timers.
§Example
use tacet::{TimingOracle, AttackerModel};
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.force_discrete_mode(true) // Force discrete mode for testing
.test(inputs, |data| operation(data));Sourcepub fn cpu_affinity(self, enabled: bool) -> Self
pub fn cpu_affinity(self, enabled: bool) -> Self
Enable or disable CPU affinity pinning.
When enabled (default), the measurement thread is pinned to its current CPU to reduce noise from thread migration between cores.
- Linux: Enforced via
sched_setaffinity(no privileges needed) - macOS: Advisory hint via
thread_policy_set(kernel may ignore)
§Example
use tacet::{TimingOracle, AttackerModel};
// Disable CPU affinity if it causes issues
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.cpu_affinity(false)
.test(inputs, |data| operation(data));Sourcepub fn thread_priority(self, enabled: bool) -> Self
pub fn thread_priority(self, enabled: bool) -> Self
Enable or disable thread priority elevation.
When enabled (default), attempts to raise thread priority to reduce preemption during measurement. This is best-effort and fails silently if privileges are insufficient.
- Linux: Lowers nice value and sets
SCHED_BATCHpolicy - macOS: Lowers nice value and sets thread precedence hint
§Example
use tacet::{TimingOracle, AttackerModel};
// Disable priority elevation if it causes issues
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.thread_priority(false)
.test(inputs, |data| operation(data));Sourcepub fn frequency_stabilization_ms(self, ms: u64) -> Self
pub fn frequency_stabilization_ms(self, ms: u64) -> Self
Set the frequency stabilization duration in milliseconds.
Before measurement begins, a brief spin-wait loop runs to let the CPU frequency ramp up and stabilize. Many CPUs start in low-power mode and take several milliseconds to reach their turbo/boost frequency.
Set to 0 to disable frequency stabilization.
Default: 5 ms.
§Example
use tacet::{TimingOracle, AttackerModel};
// Increase stabilization time for laptops with aggressive power management
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.frequency_stabilization_ms(10)
.test(inputs, |data| operation(data));Sourcepub fn from_env(self) -> Self
pub fn from_env(self) -> Self
Merge configuration from environment variables.
Reads the following environment variables to override settings:
TO_TIME_BUDGET_SECS: Time budget in secondsTO_MAX_SAMPLES: Maximum samples per classTO_BATCH_SIZE: Batch size for adaptive samplingTO_CALIBRATION_SAMPLES: Number of calibration samplesTO_PASS_THRESHOLD: Pass threshold (e.g., “0.05”)TO_FAIL_THRESHOLD: Fail threshold (e.g., “0.95”)TO_MIN_EFFECT_NS: Minimum effect of concern in nanosecondsTO_SEED: Deterministic measurement seed
§Example
use tacet::{TimingOracle, AttackerModel};
// In CI, set TO_TIME_BUDGET_SECS=120 to increase time budget
let oracle = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork).from_env();Sourcepub fn test<T, F1, F2, F>(
self,
inputs: InputPair<T, F1, F2>,
operation: F,
) -> Outcome
pub fn test<T, F1, F2, F>( self, inputs: InputPair<T, F1, F2>, operation: F, ) -> Outcome
Run a timing test with pre-generated inputs.
This is the primary API for timing tests. It handles input pre-generation internally to ensure accurate measurements without generator overhead.
§Example
use tacet::{TimingOracle, AttackerModel, helpers::InputPair};
let inputs = InputPair::new([0u8; 32], || rand::random());
let result = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.test(inputs, |data| {
my_crypto_function(data);
});§How It Works
- Pre-generates all baseline and sample inputs before measurement
- Runs warmup iterations
- Calibration phase: collects samples to estimate covariance
- Adaptive phase: collects batches until a decision is reached
§Arguments
inputs- AnInputPaircontaining the baseline and sample generatorsoperation- Closure that performs the operation under test
§Returns
An Outcome which is one of:
Pass: No timing leak detectedFail: Timing leak confirmedInconclusive: Cannot reach a definitive conclusionUnmeasurable: Operation too fast to measure reliably
Sourcepub fn analyze_raw_samples(
&self,
baseline_ns: &[f64],
test_ns: &[f64],
) -> Outcome
pub fn analyze_raw_samples( &self, baseline_ns: &[f64], test_ns: &[f64], ) -> Outcome
Analyze pre-collected timing samples in a single pass.
This method computes the posterior probability of a timing leak given
fixed sets of baseline and test samples. Unlike the test method, it
does not collect new samples - it works with what it has.
Useful for:
- Analyzing data from external tools (SILENT, dudect, etc.)
- Replaying historical measurements
- Testing with synthetic or simulated data
§Arguments
baseline_ns- Baseline timing samples in nanosecondstest_ns- Test timing samples in nanoseconds
§Example
use tacet::{TimingOracle, AttackerModel, Outcome};
// Load pre-collected samples
let baseline_ns: Vec<f64> = load_samples("baseline.csv");
let test_ns: Vec<f64> = load_samples("test.csv");
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.analyze_raw_samples(&baseline_ns, &test_ns);
match outcome {
Outcome::Pass { .. } => println!("No leak detected"),
Outcome::Fail { .. } => println!("Leak detected!"),
_ => {}
}Sourcepub fn analyze_timing_data(
&self,
data: &TimingData,
cpu_freq_ghz: Option<f64>,
) -> Outcome
pub fn analyze_timing_data( &self, data: &TimingData, cpu_freq_ghz: Option<f64>, ) -> Outcome
Analyze timing data loaded from a file or external source.
This is a convenience wrapper around analyze_raw_samples that accepts
TimingData loaded via the data module.
§Arguments
data- Timing data with baseline and test samplescpu_freq_ghz- CPU frequency in GHz (for cycle-to-ns conversion, optional)
§Example
use tacet::{TimingOracle, AttackerModel, data::load_silent_csv};
use std::path::Path;
let data = load_silent_csv(Path::new("measurements.csv")).unwrap();
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.analyze_timing_data(&data, Some(3.0)); // 3 GHz CPUTrait Implementations§
Source§impl Clone for TimingOracle
impl Clone for TimingOracle
Source§fn clone(&self) -> TimingOracle
fn clone(&self) -> TimingOracle
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for TimingOracle
impl RefUnwindSafe for TimingOracle
impl Send for TimingOracle
impl Sync for TimingOracle
impl Unpin for TimingOracle
impl UnwindSafe for TimingOracle
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.