A lexicon-driven AppView for ATProto.
1//! Constant-time comparison helpers for secrets and their hashes.
2//!
3//! Comparing secret material (or its digest) with `==` can leak how many
4//! leading bytes matched via early-exit timing. These helpers compare in time
5//! independent of the *content* of equal-length inputs. Input length is not
6//! treated as secret — the values compared here are fixed-length hashes or
7//! attacker-known challenges — so an early length-mismatch return is fine.
8
9/// Constant-time equality over two byte slices. `subtle`'s slice comparison
10/// short-circuits only on a length mismatch (length is not secret here); for
11/// equal-length inputs it compares every byte regardless of where they differ.
12pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
13 use subtle::ConstantTimeEq;
14 a.ct_eq(b).into()
15}
16
17/// Constant-time equality over two strings (compares their UTF-8 bytes).
18pub fn ct_eq_str(a: &str, b: &str) -> bool {
19 ct_eq(a.as_bytes(), b.as_bytes())
20}
21
22#[cfg(test)]
23mod tests {
24 use super::*;
25
26 #[test]
27 fn equal_values_match() {
28 assert!(ct_eq_str("a1b2c3", "a1b2c3"));
29 assert!(ct_eq(b"\x00\x01\x02", b"\x00\x01\x02"));
30 }
31
32 #[test]
33 fn different_same_length_do_not_match() {
34 assert!(!ct_eq_str("a1b2c3", "a1b2c4"));
35 // Differing only in the first byte must also be rejected.
36 assert!(!ct_eq_str("X1b2c3", "a1b2c3"));
37 }
38
39 #[test]
40 fn different_length_does_not_match() {
41 assert!(!ct_eq_str("abc", "abcd"));
42 assert!(!ct_eq_str("abcd", "abc"));
43 }
44
45 #[test]
46 fn empty_values_match() {
47 assert!(ct_eq_str("", ""));
48 }
49}