Skip to content
Tacet

Tacet — Detect side channels

Detect side channels in cryptographic code with statistically rigorous methods.

Add tacet as a dev dependency:

Terminal window
cargo add tacet --dev

Most useful features are enabled by default. See installation for more details.

use tacet::{TimingOracle, AttackerModel, Outcome, helpers::InputPair};
let inputs = InputPair::new(
|| [0u8; 32], // Baseline: all zeros
|| rand::random::<[u8; 32]>() // Sample: random data
);
let outcome = TimingOracle::for_attacker(AttackerModel::AdjacentNetwork)
.test(inputs, |data| {
my_crypto_function(&data);
});
println!("{:?}", outcome);

This results in output like:

──────────────────────────────────────────────────────────────
Samples: 6000 per class
Quality: Good
⚠ Timing leak detected
Probability of leak: 100.0%
Effect: 63799.1 ns (uniform shift)
Shift: 63798.0 ns
Tail: 366.3 ns
95% CI: 57192.6–70405.6 ns
Exploitability (heuristic):
Attack vector: Any (trivially observable)
Queries needed: <100
──────────────────────────────────────────────────────────────

Traditional statistical timing analysis tools (dudect, RTLF, tlsfuzzer, etc) give you p-values or t-statistics that are hard to interpret and easy to get wrong. They’re prone to false positives from system noise and false negatives from insufficient sampling. It’s impractical to use them in CI given their inherent flakiness, and it’s typically understood that they provide only weak evidence of constant-time behavior.

Tacet is built from the ground-up to be statistically rigorous, reliable in real-world conditions, and easy to use in CI. When you run a tacet test, instead of a p-value, you get the probability of a leak and a three-way verdict: Pass, Fail, or Inconclusive. When measurement conditions can’t support a confident answer, we use Indeterminate to avoid providing false confidence. Sampling is automatic and adaptive, collecting data until either we have enough evidence to make a decision or we realize that a reliable result isn’t possible.

Tacet is also highly performant, works across platforms, and can be used to test code written in multiple programming languages (Rust, C, C++, Go, JavaScript/TypeScript).

Finally, tacet has experimental support for power and EM side-channel analysis, making it a versatile tool for side-channel researchers more generally. See the Power Analysis Guide for details.

At a high level, tacet fits a Bayesian model to the timing differences between two input classes (typically fixed vs. random), estimating the posterior probability that the true effect exceeds a threshold you specify. Rather than comparing means, the model uses the Wasserstein-1 distance (earth mover’s distance), which measures distribution differences via optimal transport. This single metric decomposes naturally into shift (median difference) and tail (spread of residuals) components, capturing both uniform shifts from branch changes and tail effects that arise from cache behavior. Sampling is adaptive: tacet collects data until the posterior crosses a decision boundary (> 95% → Fail, < 5% → Pass) or a quality gate determines the measurement can’t support a confident verdict (Inconclusive).

To handle temporal autocorrelation between samples, variance is estimated via block bootstrap on the interleaved acquisition stream. To prevent the prior from shrinking real leaks toward zero, the model uses a Student’s t prior whose scale adapts to the data. To guard against overconfident results when measurement noise is underestimated, the likelihood automatically inflates uncertainty when residuals are larger than expected. You can find a high-level description of how this all works here, and read about the full methodology here.

The library is pretty new, and I’m in the process of auditing real-world cryptographic implementations with it. When I was first building our test suite of ‘known constant time’ functions, I accidentally rediscovered CVE-2023-49092 (Marvin Attack) in the RustCrypto rsa crate, a ~500ns timing leak in RSA decryption. See the full investigation.