use sha2::{Digest, Sha256};
const DOMAIN_TAG: &[u8] = b"vetting-session-match/v1\0";
const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
#[must_use]
pub fn vetting_match_code(session_document_id: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(DOMAIN_TAG);
hasher.update(session_document_id.as_bytes());
encode(&hasher.finalize())
}
fn encode(digest: &[u8]) -> String {
let bits = digest[..5]
.iter()
.fold(0u64, |acc, byte| (acc << 8) | u64::from(*byte));
let mut out = String::with_capacity(9);
for i in 0..8 {
if i == 4 {
out.push('-');
}
let shift = 35 - 5 * i;
out.push(char::from(CROCKFORD[((bits >> shift) & 0x1f) as usize]));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_is_two_groups_of_four_crockford_characters() {
let code = vetting_match_code("urn:uuid:5b0e1c2a-7d4f-4a51-9c6e-2f1b8d3a9e70");
assert_eq!(code.len(), 9);
assert_eq!(code.as_bytes()[4], b'-');
assert!(
code.chars()
.filter(|c| *c != '-')
.all(|c| CROCKFORD.contains(&(c as u8)))
);
}
#[test]
fn deterministic_and_input_sensitive() {
let a = vetting_match_code("urn:uuid:a");
assert_eq!(a, vetting_match_code("urn:uuid:a"));
assert_ne!(a, vetting_match_code("urn:uuid:b"));
}
#[test]
fn forty_bits_fill_eight_characters_exactly() {
assert_eq!(encode(&[0xff; 32]), "ZZZZ-ZZZZ");
assert_eq!(encode(&[0x00; 32]), "0000-0000");
}
#[test]
fn never_collides_with_the_personhood_derivation() {
let id = "5b0e1c2a-7d4f-4a51-9c6e-2f1b8d3a9e70";
let mut personhood = Sha256::new();
personhood.update(b"vtc-personhood-match/v1\0");
personhood.update(id.as_bytes());
assert_ne!(vetting_match_code(id), encode(&personhood.finalize()));
}
}