constant_time_eq

A dependency-free Rust crate for comparing byte strings in constant time, resisting timing side-channel attacks.

Library
Cargo
v0.5.0
48stars
Apache-2.0 OR MIT-0 OR CC0-1.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
26/100Needs Attention
Development Activity12
Maintenance0
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
79/100Good
Architecture85
Code Quality92
Innovation82
Learning Curve55

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, returning false immediately (still in constant time) for mismatched lengths.
  • constant_time_eq_n - a const N: usize generic 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_64 sized 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.rs detects and enables the aarch64 Data Independent Timing and Speculation Barrier hardware features at runtime (with the std feature) or compile time (no_std), running comparisons under hardware timing protection when available.
  • no_std compatible - builds with #![no_std] when the default std feature 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search