pub use purecrypto::hash::HashAlgorithm;
pub(crate) const MIN_OUTPUT_LEN: usize = 32;
pub(crate) fn validate(alg: HashAlgorithm) -> Result<(), String> {
if alg.is_legacy() {
return Err(format!(
"hash {} is legacy (broken or sub-128-bit collision resistance)",
alg.name()
));
}
if alg.output_len() < MIN_OUTPUT_LEN {
return Err(format!(
"hash {} emits {} bytes, need at least {MIN_OUTPUT_LEN}",
alg.name(),
alg.output_len()
));
}
Ok(())
}
pub(crate) fn digest32(alg: HashAlgorithm, data: &[u8]) -> [u8; 32] {
let d = alg.digest(data);
let mut out = [0u8; 32];
out.copy_from_slice(&d.as_slice()[..32]);
out
}
pub(crate) fn digest64(alg: HashAlgorithm, data: &[u8]) -> [u8; 64] {
let mut out = [0u8; 64];
if alg.output_len() == 64 {
out.copy_from_slice(alg.digest(data).as_slice());
return out;
}
let mut buf = Vec::with_capacity(data.len() + 1);
buf.push(0u8);
buf.extend_from_slice(data);
out[..32].copy_from_slice(&digest32(alg, &buf));
buf[0] = 1u8;
out[32..].copy_from_slice(&digest32(alg, &buf));
out
}
#[cfg(test)]
mod tests {
use super::*;
use purecrypto::hash::{sha256, sha512};
#[test]
fn legacy_and_short_digests_are_rejected() {
assert!(validate(HashAlgorithm::Sha1).is_err());
assert!(validate(HashAlgorithm::Md5).is_err());
assert!(validate(HashAlgorithm::Sha224).is_err());
for alg in [
HashAlgorithm::Sha256,
HashAlgorithm::Sha512,
HashAlgorithm::Sha3_256,
HashAlgorithm::Sha3_512,
HashAlgorithm::Keccak256,
HashAlgorithm::Blake2b512,
HashAlgorithm::Blake3,
] {
validate(alg).unwrap_or_else(|e| panic!("{} rejected: {e}", alg.name()));
}
}
#[test]
fn wide_digests_pass_through_narrow_ones_are_extended() {
assert_eq!(digest64(HashAlgorithm::Sha512, b"x").to_vec(), sha512(b"x"));
assert_eq!(
digest32(HashAlgorithm::Sha512, b"x").to_vec(),
sha512(b"x")[..32].to_vec()
);
let wide = digest64(HashAlgorithm::Sha256, b"x");
assert_eq!(wide[..32].to_vec(), sha256(&[&[0u8][..], b"x"].concat()));
assert_eq!(wide[32..].to_vec(), sha256(&[&[1u8][..], b"x"].concat()));
assert_ne!(wide[..32], wide[32..]);
assert_ne!(wide[..32].to_vec(), sha256(b"x").to_vec());
}
#[test]
fn every_accepted_algorithm_is_distinct() {
let mut names = std::collections::HashSet::new();
let mut d32 = std::collections::HashSet::new();
let mut d64 = std::collections::HashSet::new();
for &alg in HashAlgorithm::ALL {
if validate(alg).is_err() {
continue;
}
assert!(names.insert(alg.name()), "duplicate name {}", alg.name());
assert!(d32.insert(digest32(alg, b"same input")), "{}", alg.name());
assert!(d64.insert(digest64(alg, b"same input")), "{}", alg.name());
}
assert!(names.len() >= 7, "expected a broad accepted set");
}
#[test]
fn names_round_trip_through_from_name() {
for &alg in HashAlgorithm::ALL {
assert_eq!(HashAlgorithm::from_name(alg.name()), Some(alg));
}
}
}