secure_gate/traits/constant_time_eq_ext.rs
1//! Extension trait for probabilistic constant-time equality via BLAKE3 hashing.
2//!
3//! Provides fast equality checks for secrets by hashing inputs (BLAKE3) then
4//! comparing the fixed 32-byte digests in constant time (via `ct_eq`/`subtle`).
5//!
6//! Ideal for **large or variable-length secrets** (e.g. ML-KEM ciphertexts ~1–1.5 KiB,
7//! ML-DSA signatures ~2–4 KiB) where direct byte-by-byte `ct_eq` becomes slow or
8//! increases side-channel surface.
9//!
10//! Extends [`crate::ConstantTimeEq`] to add hash-based probabilistic equality.
11//!
12//! ## Security Properties
13//! - **Timing-safe**: BLAKE3 is data-independent; final 32-byte compare is constant-time.
14//! - **Length hiding**: Original length not observable via timing/cache.
15//! - **Keyed mode** (with `"rand"` feature): Per-process random key resists precomputation /
16//! multi-target attacks across comparisons.
17//! - **Probabilistic**: Collision probability ~2⁻²⁵⁶ — negligible for equality checks,
18//! but use [`crate::ConstantTimeEq`] for strict deterministic equality.
19//!
20//! ## Usage Recommendations
21//! - For most use cases, prefer [`ConstantTimeEqExt::ct_eq_auto`] — it automatically selects the best strategy based on size.
22//! - Use plain [`ConstantTimeEqExt::ct_eq_hash`] only for large inputs (>32 bytes) or when uniform probabilistic behavior is needed.
23//! - Use [`crate::ConstantTimeEq`] for small deterministic equality (<32 bytes).
24//!
25//! ## Performance
26//! - Fixed overhead (~120–150 ns on small inputs) + very low per-byte cost.
27//! - Beats full `ct_eq` for > ~300–500 bytes (2× at 1 KiB, 5–8× at 100 KiB+).
28//! - Prefer [`crate::ConstantTimeEq`] for tiny fixed-size tags (< 128–256 bytes).
29//!
30//! ## Warnings
31//! - **DoS risk**: Hashing very large untrusted inputs is costly — rate-limit or bound sizes.
32//! - **Not zero-collision**: Extremely unlikely false positives; don't rely on it for uniqueness.
33//!
34//! ## Example
35//! ```
36//! # #[cfg(feature = "ct-eq-hash")]
37//! # {
38//! use secure_gate::{Fixed, ConstantTimeEqExt};
39//! let a: Fixed<[u8; 2048]> = Fixed::new([42u8; 2048]); // e.g. large fixed data
40//! let b: Fixed<[u8; 2048]> = Fixed::new([42u8; 2048]); // matching value
41//! assert!(a.ct_eq_hash(&b)); // Efficient comparison for large data
42//! # }
43//! ```
44#[cfg(feature = "ct-eq-hash")]
45#[allow(clippy::len_without_is_empty)]
46pub trait ConstantTimeEqExt: crate::ConstantTimeEq {
47 /// Get the length of the secret data in bytes.
48 ///
49 /// Note: This trait does **not** provide `.is_empty()` to avoid method ambiguity with
50 /// `ExposeSecret::len`, which already offers the same functionality via `len()`.
51 /// Use `.len() == 0` or `.expose_secret().is_empty()` when you need emptiness checks.
52 fn len(&self) -> usize;
53
54 /// Force BLAKE3 digest comparison (constant-time on 32-byte output).
55 ///
56 /// **Probabilistic** when `"rand"` feature is enabled (per-process random key).
57 /// **Deterministic** otherwise.
58 ///
59 /// Collision probability ~2⁻²⁵⁶ — negligible for equality checks,
60 /// but **not zero**. Use `ct_eq` when strict determinism is required.
61 ///
62 /// Keyed mode resists multi-target precomputation attacks across many comparisons.
63 ///
64 /// DoS warning: hashing very large untrusted inputs is costly — bound sizes.
65 fn ct_eq_hash(&self, other: &Self) -> bool;
66
67 /// Recommended hybrid constant-time equality check.
68 ///
69 /// - Length mismatch → `false` (public metadata, non-constant-time compare)
70 /// - Size ≤ threshold → `self.ct_eq(other)` (strict deterministic)
71 /// - Size > threshold → `self.ct_eq_hash(other)` (probabilistic, fast)
72 ///
73 /// Default threshold: **32 bytes**
74 /// Customize with `threshold_bytes: Some(n)` if your benchmarks show a different optimal crossover point (e.g., `64`, `1024`, or `0` for always using `ct_eq`).
75 ///
76 /// Prefer this method in almost all cases unless you need:
77 /// - Guaranteed zero-collision → use `ct_eq`
78 /// - Uniform probabilistic behavior → use `ct_eq_hash`
79 fn ct_eq_auto(&self, other: &Self, threshold_bytes: Option<usize>) -> bool {
80 // Default implementation (can be overridden if desired)
81 if self.len() != other.len() {
82 return false;
83 }
84
85 let thresh = threshold_bytes.unwrap_or(32);
86
87 if self.len() <= thresh {
88 self.ct_eq(other)
89 } else {
90 self.ct_eq_hash(other)
91 }
92 }
93}