Skip to content
/ tacet Public

Detect side channels with statistically rigorous methods.

License

Notifications You must be signed in to change notification settings

agucova/tacet

Repository files navigation

tacet

Detect side channels with statistically rigorous methods.
Available for Rust, JavaScript/TypeScript, C/C++, and Go.

crates.io npm JSR License


Language Package Install
Rust tacet cargo add tacet --dev
JavaScript @tacet/js bun add @tacet/js
C/C++ source Build instructions
Go tacet-go go get github.com/agucova/tacet/bindings/go

Documentation →

$ cargo test --test aes_timing -- --nocapture

[aes128_block_encrypt_constant_time]
tacet
──────────────────────────────────────────────────────────────

  Samples: 6000 per class
  Quality: Good

  ✓ No timing leak detected

    Probability of leak: 0.0%
    95% CI: 0.0–12.5 ns

──────────────────────────────────────────────────────────────

Quick Start

use tacet::{TimingOracle, AttackerModel, Outcome, helpers::InputPair};

#[test]
fn constant_time_compare() {
    let secret = [0u8; 32];

    let inputs = InputPair::new(
        || [0u8; 32],
        || rand::random::<[u8; 32]>(),
    );

    let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
        .test(inputs, |input| {
            constant_time_eq(&secret, &input);
        });

    match outcome {
        Outcome::Pass { .. } => { /* No leak */ }
        Outcome::Fail { exploitability, .. } => panic!("Timing leak: {:?}", exploitability),
        Outcome::Inconclusive { .. } => { /* Could not determine */ }
        Outcome::Unmeasurable { .. } => { /* Operation too fast */ }
    }
}

Important: The baseline input must be chosen to create timing asymmetry with the sample input. For comparison functions, baseline should match the secret so it runs the full comparison (slow) while random samples exit early (fast). See Choosing Input Classes for details.


What It Catches

// ✗ This looks constant-time but isn't (early-exit on mismatch)
fn naive_compare(a: &[u8], b: &[u8]) -> bool {
    a == b  // ← tacet detects this in ~1 second
}

// ✓ This is actually constant-time
fn ct_compare(a: &[u8], b: &[u8]) -> bool {
    subtle::ConstantTimeEq::ct_eq(a, b).into()
}

Why Tacet?

Empirical timing tools like DudeCT are hard to use and yield results that are difficult to interpret.

Tacet gives you what you actually want: the probability your code has a timing leak, plus how exploitable it would be.

DudeCT Tacet
Output t-statistic + p-value Probability of leak (0–100%)
False positives Unbounded (more samples → more FPs) Converges to correct answer
Effect size Not provided Estimated in nanoseconds
Exploitability Manual interpretation Automatic classification
CI-friendly Flaky without tuning Works out of the box

Real-World Validation

While testing the library, I incidentally rediscovered CVE-2023-49092 (Marvin Attack) in the RustCrypto rsa crate—a ~500ns timing leak in RSA decryption. I wasn't looking for it; the library just flagged it. See the full investigation.


Attacker Model Presets

Choose based on your threat model:

Preset Threshold Use case
SharedHardware 0.6 ns (~2 cycles) SGX, cross-VM, containers, hyperthreading
AdjacentNetwork 100 ns LAN, HTTP/2 (Timeless Timing Attacks)
RemoteNetwork 50 μs Public APIs, general internet
Research 0 Detect any difference (not for CI)
Custom { threshold_ns } user-defined Custom threshold
// Internet-facing API
TimingOracle::for_attacker(AttackerModel::RemoteNetwork)

// Internal microservice or HTTP/2 API
TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)

// SGX enclave or container
TimingOracle::for_attacker(AttackerModel::SharedHardware)

Interpreting Results

Outcome Variants

Variant Meaning Key Fields
Pass No timing leak (P(leak) < 5%) leak_probability, effect, quality
Fail Timing leak confirmed (P(leak) > 95%) leak_probability, effect, exploitability
Inconclusive Cannot determine (5% < P(leak) < 95%) reason, leak_probability, effect
Unmeasurable Operation too fast to measure recommendation, platform

Exploitability Levels

Level Effect Size Meaning
SharedHardwareOnly < 10 ns Requires shared physical core
Http2Multiplexing 10–100 ns Exploitable via HTTP/2
StandardRemote 100 ns – 10 μs Exploitable with network timing
ObviousLeak > 10 μs Trivially exploitable

Running Tests

# Run timing tests (single-threaded is recommended)
cargo test --test timing_tests -- --test-threads=1 --nocapture

# With higher-precision PMU timer (macOS ARM64)
sudo -E cargo test --test timing_tests -- --test-threads=1

# With higher-precision PMU timer (Linux)
sudo cargo test --test timing_tests -- --test-threads=1

See CI Integration for more details.

Platform Support

Platform Timer Resolution Notes
x86_64 rdtsc ~0.3 ns Best precision
Apple Silicon kperf ~1 ns Requires sudo + --test-threads=1
Apple Silicon cntvct ~42 ns Default, uses adaptive batching
Linux ARM64 perf_event ~1 ns Requires sudo or CAP_PERFMON

Beyond timing: Experimental support for power and EM side-channel analysis is available in the Rust crate via the power feature. See the Power Analysis Guide.


Documentation

References

License

MPL-2.0