Tacet — Detect side channels
Install
Section titled “Install”Add tacet as a dev dependency:
cargo add tacet --devMost useful features are enabled by default. See installation for more details.
Install via your preferred package manager:
bun add @tacet/jsWe support Node.js, Deno and Bun. See installation for more platform details.
Install the shared library and headers:
curl -fsSL https://raw.githubusercontent.com/agucova/tacet/main/install.sh | bashOr via Homebrew on macOS:
brew install agucova/tacet/tacet-cSee installation for pkg-config setup and CMake integration.
Install the shared library and headers:
curl -fsSL https://raw.githubusercontent.com/agucova/tacet/main/install.sh | bashOr via Homebrew on macOS:
brew install agucova/tacet/tacet-cSee installation for pkg-config setup and CMake integration (requires C++20).
Install via Go modules:
go get github.com/agucova/tacet/crates/tacet-gogo run github.com/agucova/tacet/crates/tacet-go/cmd/tacet-install@latestThis will download a pre-built native library (~12MB) for your platform. Requires Go 1.22+ with CGo. See installation for details.
Quick example
Section titled “Quick example”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 classQuality: 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──────────────────────────────────────────────────────────────Why Tacet?
Section titled “Why Tacet?”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.
How does it work?
Section titled “How does it work?”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.
Real-world validation
Section titled “Real-world validation”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.