philbin 1.0.1

A pure Rust AEGIS library with SIMD and runtime CPU detection
Documentation
use crate::error::{Error, Result};
use constant_time_eq::constant_time_eq_n;

pub fn num_bits(slice: &[u8]) -> u64 {
  // NOTE: This only _looks_ like it can overflow, but it actually can't. Rust
  // guarantees[^1] that the maximum size of a slice is <= isize::MAX, and
  // isize::MAX * 8 is comfortably within a u64 on both 32 bit and 64 bit
  // platforms.
  //
  // ...and yet LLVM needs the assert to optimize well. Don't ask me why, I have
  // no idea. I just know that the benchmarks go VROOM with the assert.
  //
  // [^1]: See the following docs:
  // https://doc.rust-lang.org/core/ptr/index.html#allocation
  // https://doc.rust-lang.org/stable/reference/types/numeric.html#machine-dependent-integer-types
  let len = slice.len() as u64;
  assert!(len <= isize::MAX as u64); // NOTE: *Impossible* to trigger.
  len * 8
}

/// Get random bytes from a cryptographically secure random number generator
/// (CSRNG).
#[cfg(feature = "rand")]
pub fn random_bytes<const BYTES: usize>() -> Result<[u8; BYTES]> {
  use rand::TryRng;
  let mut data = [0; BYTES];
  // SECURITY: We do NOT use ThreadRng (rand crate's default RNG) because it
  // doesn't reseed on process fork. SysRng is the safest choice.
  //
  // TODO: Consider adding a Cargo feature to switch to ThreadRng. It's a valid
  // choice for applications that know they will never fork.
  let mut rng = rand::rngs::SysRng;
  rng
    .try_fill_bytes(&mut data)
    // SECURITY: We do NOT provide ANY information from the source `rand`
    // error to avoid leaking potentially useful info to an attacker.
    .map_err(|_| Error::RandError)?;

  Ok(data)
}

/// Validates the provided array contains bytes that are different from 0.
#[inline(always)]
pub fn validate_non_zero<const BYTES: usize>(
  data: [u8; BYTES],
) -> Result<[u8; BYTES]> {
  let all_zero = [0; BYTES];
  // SECURITY: The IF check is safe WRT timing attacks because:
  // - We use a constant-time comparison to check for all-zero.
  if constant_time_eq_n(&all_zero, &data) {
    Err(Error::AllZeroNotAllowed)
  } else {
    Ok(data)
  }
}

// A useful macro for compile-time assertions.
//
// NOTE: Can only be used "top-level", so not inside an impl block or trait.
macro_rules! const_assert {
  ($cond:expr) => {
    const _: () = assert!($cond);
  };
  ($cond:expr, $msg:literal) => {
    const _: () = assert!($cond, $msg);
  };
}
// Re-export macro to give it a "path-based scope".
pub(crate) use const_assert;