pub trait ConstantTimeEq {
#[must_use]
fn ct_eq(&self, other: &Self) -> bool;
}
impl<const N: usize> ConstantTimeEq for [u8; N] {
#[inline]
fn ct_eq(&self, other: &Self) -> bool {
constant_time_eq(self, other)
}
}
impl ConstantTimeEq for [u8] {
#[inline]
fn ct_eq(&self, other: &Self) -> bool {
constant_time_eq(self, other)
}
}
#[inline]
#[must_use]
pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let len = a.len();
let mut acc0 = 0u64;
let mut acc1 = 0u64;
let mut acc2 = 0u64;
let mut acc3 = 0u64;
let mut i = 0usize;
let end32 = len & !31;
let a_ptr = a.as_ptr();
let b_ptr = b.as_ptr();
while i < end32 {
unsafe {
acc0 |=
core::ptr::read_unaligned(a_ptr.add(i).cast::<u64>()) ^ core::ptr::read_unaligned(b_ptr.add(i).cast::<u64>());
acc1 |= core::ptr::read_unaligned(a_ptr.add(i.strict_add(8)).cast::<u64>())
^ core::ptr::read_unaligned(b_ptr.add(i.strict_add(8)).cast::<u64>());
acc2 |= core::ptr::read_unaligned(a_ptr.add(i.strict_add(16)).cast::<u64>())
^ core::ptr::read_unaligned(b_ptr.add(i.strict_add(16)).cast::<u64>());
acc3 |= core::ptr::read_unaligned(a_ptr.add(i.strict_add(24)).cast::<u64>())
^ core::ptr::read_unaligned(b_ptr.add(i.strict_add(24)).cast::<u64>());
}
i = i.strict_add(32);
}
let mut acc = acc0 | acc1 | acc2 | acc3;
let end8 = len & !7;
while i < end8 {
unsafe {
acc |=
core::ptr::read_unaligned(a_ptr.add(i).cast::<u64>()) ^ core::ptr::read_unaligned(b_ptr.add(i).cast::<u64>());
}
i = i.strict_add(8);
}
while i < len {
unsafe {
acc |= (*a_ptr.add(i) ^ *b_ptr.add(i)) as u64;
}
i = i.strict_add(1);
}
let mut observed = acc;
unsafe {
core::ptr::write_volatile(&mut observed, acc);
core::ptr::read_volatile(&observed) == 0
}
}
#[inline(always)]
pub(crate) fn zeroize_no_fence(buf: &mut [u8]) {
let (prefix, words, suffix) = unsafe { buf.align_to_mut::<u64>() };
for byte in prefix.iter_mut() {
unsafe { core::ptr::write_volatile(byte, 0) };
}
for word in words.iter_mut() {
unsafe { core::ptr::write_volatile(word, 0) };
}
for byte in suffix.iter_mut() {
unsafe { core::ptr::write_volatile(byte, 0) };
}
}
#[inline(always)]
pub fn zeroize(buf: &mut [u8]) {
zeroize_no_fence(buf);
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
#[cfg(feature = "pbkdf2")]
mod word_zero_sealed {
pub trait WordZero: Copy {
const ZERO: Self;
}
impl WordZero for u8 {
const ZERO: Self = 0;
}
impl WordZero for u16 {
const ZERO: Self = 0;
}
impl WordZero for u32 {
const ZERO: Self = 0;
}
impl WordZero for u64 {
const ZERO: Self = 0;
}
impl WordZero for u128 {
const ZERO: Self = 0;
}
impl WordZero for usize {
const ZERO: Self = 0;
}
}
#[cfg(feature = "pbkdf2")]
pub(crate) use word_zero_sealed::WordZero;
#[cfg(feature = "pbkdf2")]
#[inline(always)]
pub(crate) fn zeroize_words_no_fence<T: WordZero>(words: &mut [T]) {
for word in words {
unsafe { core::ptr::write_volatile(word, T::ZERO) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equal_slices() {
assert!(constant_time_eq(b"abcdef", b"abcdef"));
}
#[test]
fn differ_first_byte() {
assert!(!constant_time_eq(b"\x00bcdef", b"\xFFbcdef"));
}
#[test]
fn differ_last_byte() {
assert!(!constant_time_eq(b"abcde\x00", b"abcde\xFF"));
}
#[test]
fn differ_middle_byte() {
assert!(!constant_time_eq(b"ab\x00def", b"ab\xFFdef"));
}
#[test]
fn both_empty() {
assert!(constant_time_eq(b"", b""));
}
#[test]
fn one_empty_one_not() {
assert!(!constant_time_eq(b"", b"x"));
assert!(!constant_time_eq(b"x", b""));
}
#[test]
fn length_mismatch() {
assert!(!constant_time_eq(b"short", b"longer"));
assert!(!constant_time_eq(b"longer", b"short"));
}
#[test]
fn single_byte_equal() {
assert!(constant_time_eq(&[0x42], &[0x42]));
}
#[test]
fn single_byte_differ() {
assert!(!constant_time_eq(&[0x00], &[0x01]));
}
#[test]
fn all_zeros_equal() {
assert!(constant_time_eq(&[0u8; 64], &[0u8; 64]));
}
#[test]
fn all_ones_equal() {
assert!(constant_time_eq(&[0xFF; 64], &[0xFF; 64]));
}
#[test]
fn equal_misaligned_long_slices() {
let a = [0x5Au8; 73];
let b = [0x5Au8; 73];
assert!(constant_time_eq(&a[1..], &b[1..]));
}
#[test]
fn differ_misaligned_long_slices() {
let a = [0x5Au8; 73];
let mut b = [0x5Au8; 73];
b[64] ^= 0x80;
assert!(!constant_time_eq(&a[1..], &b[1..]));
}
#[test]
fn ct_eq_fixed_array() {
let a = [1u8, 2, 3, 4];
let b = [1u8, 2, 3, 4];
let c = [1u8, 2, 3, 5];
assert!(a.ct_eq(&b));
assert!(!a.ct_eq(&c));
}
#[test]
fn ct_eq_slice() {
let a: &[u8] = &[10, 20, 30];
let b: &[u8] = &[10, 20, 30];
let c: &[u8] = &[10, 20, 31];
assert!(a.ct_eq(b));
assert!(!a.ct_eq(c));
}
#[test]
fn zeroize_clears_buffer() {
let mut buf = [0xFFu8; 37]; zeroize(&mut buf);
assert!(buf.iter().all(|&b| b == 0));
}
#[test]
fn zeroize_empty_is_noop() {
let mut buf = [];
zeroize(&mut buf); }
}