constant_time_eq
A dependency-free Rust crate for comparing byte strings in constant time, resisting timing side-channel attacks.
Repository Health
Technical Analysis
constant_time_eq is a small, dependency-free Rust crate that compares two equal-length byte slices without leaking timing information about where or whether they differ. It underpins cryptographic code — MAC verification, password hash comparison, HMAC checks — where using == or .eq() on secret data can open a timing side channel that lets an attacker recover secrets byte by byte.
The crate ships multiple backends: a portable generic implementation built on word-sized XOR-and-OR accumulation with an optimizer-hiding assembly barrier, SSE2 and NEON vectorized paths for x86_64 and aarch64, and dedicated support for detecting and toggling ARM’s Data Independent Timing (DIT) hardware feature on aarch64 CPUs so that even circuit-level power and EM analysis is mitigated where the CPU supports it. It compiles as no_std by default (with the std feature enabling runtime DIT feature detection), has zero required dependencies, and exposes both slice-based and fixed-size-array comparison functions (constant_time_eq, constant_time_eq_n, plus 16/32/64-byte convenience wrappers).
What You Get
constant_time_eq- compares two&[u8]slices of equal length in constant time, returningfalseimmediately (still in constant time) for mismatched lengths.constant_time_eq_n- aconst N: usizegeneric variant for fixed-size&[u8; N]arrays, avoiding a length check entirely.- 16/32/64-byte convenience wrappers -
constant_time_eq_16,constant_time_eq_32,constant_time_eq_64sized for the most common digest and key lengths. - Architecture-specific fast paths - SSE2 (
src/sse2.rs) and NEON (src/neon.rs) vectorized implementations selected automatically at compile time, falling back to a portable generic word-based implementation (src/generic.rs) elsewhere. - ARM DIT/SB support -
src/dit.rsdetects and enables the aarch64 Data Independent Timing and Speculation Barrier hardware features at runtime (with thestdfeature) or compile time (no_std), running comparisons under hardware timing protection when available. - no_std compatible - builds with
#![no_std]when the defaultstdfeature is disabled, with zero required runtime dependencies.
Common Use Cases
- Verifying HMACs and MACs - comparing a computed authentication tag against a received one without leaking match position through timing.
- Password hash / token comparison - checking a stored digest, API key, or session token against user input without opening a timing oracle.
- Building cryptographic libraries - as a low-level building block inside higher-level crypto crates that need constant-time equality checks.
- Embedded and no_std cryptography - constant-time comparisons in firmware or embedded targets that can’t pull in a full crypto framework.
Under The Hood
Architecture
The public API in src/lib.rs resolves to a single simd module alias chosen entirely at compile time via #[cfg] gates — sse2 on x86/x86_64, neon on aarch64, or generic as the portable fallback — so callers always call the same constant_time_eq/constant_time_eq_n functions regardless of target, with the concrete backend fixed before the binary is built rather than dispatched at runtime. On aarch64, calls are additionally wrapped in with_dit (src/dit.rs), which toggles the processor’s Data Independent Timing (FEAT_DIT) and Speculation Barrier (FEAT_SB) features around the comparison, with feature detection cached in an AtomicU8 and resolved once. The generic backend (src/generic.rs) implements the core logic: it reads inputs in word-sized chunks (a Word type sized per target_pointer_width) via unaligned pointer reads, XORs and ORs them into an accumulator, and calls an optimizer_hide function built on inline assembly (pure, nomem, preserves_flags, nostack) after each step to stop the compiler from short-circuiting once a mismatch is found. This is a flat, single-purpose architecture with no dependency injection or branching data flow — the load-bearing invariant is that no branch or memory-access pattern depends on the compared byte values, and if the optimizer_hide barrier were ever bypassed by a sufficiently aggressive compiler, the crate’s entire safety guarantee would silently break with no type-level signal.
Tech Stack
The crate has zero required runtime dependencies; criterion 0.8.0 and count_instructions 0.2.0 appear only as dev-dependencies for benchmarking and instruction-count verification. It targets edition = "2024" with rust-version = "1.95.0", supports both std and no_std builds behind a default std feature, and has no build script — everything is resolved through #[cfg] target-detection at compile time plus a small amount of runtime feature detection on aarch64.
Code Quality
tests/exhaustive.rs flips every bit of every byte at every offset across a range of lengths and deliberately misaligned buffers to confirm every bit position affects the comparison result, and tests/count_instructions*.rs verify the instruction-level behavior of the accumulator. The crate applies #![deny(clippy::undocumented_unsafe_blocks)], so every unsafe block (inline assembly, unaligned reads) carries a mandatory SAFETY: comment. CI (.github/workflows/ci.yml) runs across four toolchains (MSRV, stable, beta, nightly) and four OSes, builds and tests with default and --no-default-features, in debug and release, and cross-compiles to six additional targets (x86_64, i686, aarch64, armv7, aarch64-apple-darwin, and more) — an unusually thorough matrix for a crate this size.
What Makes It Unique
Most constant-time comparison crates stop at hiding branches and memory-access patterns in software. This one goes further by directly managing ARM’s Data Independent Timing and Speculation Barrier hardware features (src/dit.rs) — detecting availability, caching the result, and wrapping comparisons in a DIT-enabled context — so the guarantee extends to circuit-level timing variation the CPU itself can introduce, not just what the instruction stream does. Combining that with hand-tuned SSE2/NEON vector backends and an exhaustive bit-flip test suite is a level of rigor uncommon outside dedicated cryptography crates.