use sha2::{Digest, Sha256};
const HASH_HEX_LEN: usize = 16;
const HASH_BYTE_LEN: usize = HASH_HEX_LEN / 2;
const NIBBLE: &[u8; 16] = b"0123456789abcdef";
pub fn principal_hash(sub: &str) -> String {
let digest = Sha256::digest(sub.as_bytes());
let mut out = String::with_capacity(HASH_HEX_LEN);
for byte in digest.iter().take(HASH_BYTE_LEN) {
out.push(NIBBLE[(byte >> 4) as usize] as char);
out.push(NIBBLE[(byte & 0x0f) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn principal_hash_is_deterministic_and_16_hex_chars() {
let a = principal_hash("alice@example.com");
let b = principal_hash("alice@example.com");
assert_eq!(a, b);
assert_eq!(a.len(), HASH_HEX_LEN);
assert!(a.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn principal_hash_differs_per_sub() {
assert_ne!(principal_hash("alice"), principal_hash("bob"));
}
#[test]
fn principal_hash_handles_empty_string() {
let h = principal_hash("");
assert_eq!(h.len(), HASH_HEX_LEN);
assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
}
}