use sha2::{Digest, Sha256};
use uuid::Uuid;
const DOMAIN_TAG: &[u8] = b"vtc-personhood-match/v1\0";
pub const MATCH_CODE_EXT_KEY: &str = "org.openvtc.match-code";
const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const CODE_CHARS: usize = 8;
const CODE_BITS: usize = CODE_CHARS * 5;
pub fn derive(challenge_id: Uuid) -> String {
let mut hasher = Sha256::new();
hasher.update(DOMAIN_TAG);
hasher.update(challenge_id.as_bytes());
let digest = hasher.finalize();
let mut out = String::with_capacity(CODE_CHARS + 1);
for (i, bit_offset) in (0..CODE_BITS).step_by(5).enumerate() {
let mut idx = 0u8;
for bit in 0..5 {
let abs = bit_offset + bit;
let byte = digest[abs / 8];
let taken = (byte >> (7 - (abs % 8))) & 1;
idx = (idx << 1) | taken;
}
if i == 4 {
out.push('-');
}
out.push(CROCKFORD[idx as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derivation_is_deterministic() {
let id = Uuid::parse_str("6f1c4f9e-7c2a-4f4b-9a3e-2b1d0c5e8a77").expect("uuid");
assert_eq!(derive(id), derive(id));
}
#[test]
fn derivation_matches_the_vector_the_client_pins() {
let id = Uuid::parse_str("6f1c4f9e-7c2a-4f4b-9a3e-2b1d0c5e8a77").expect("uuid");
assert_eq!(derive(id), "5CY1-GZEE");
}
#[test]
fn shape_is_four_dash_four_from_crockford() {
let code = derive(Uuid::new_v4());
assert_eq!(code.len(), CODE_CHARS + 1, "code is 8 chars plus separator");
assert_eq!(code.as_bytes()[4], b'-', "separator sits in the middle");
for c in code.bytes().filter(|c| *c != b'-') {
assert!(
CROCKFORD.contains(&c),
"character {} is outside Crockford's alphabet",
c as char
);
}
}
#[test]
fn alphabet_omits_mishearable_characters() {
for c in *b"ILOU" {
assert!(
!CROCKFORD.contains(&c),
"Crockford omits {} — it is confusable when spoken",
c as char
);
}
assert_eq!(CROCKFORD.len(), 32, "base32 needs exactly 32 symbols");
}
#[test]
fn bit_budget_is_fully_consumed() {
assert_eq!(CODE_BITS, 40, "8 chars x 5 bits");
const {
assert!(
CODE_BITS <= 256,
"cannot draw more bits than SHA-256 produces"
)
};
}
#[test]
fn distinct_challenges_yield_distinct_codes() {
let mut seen = std::collections::HashSet::new();
for _ in 0..2_000 {
assert!(
seen.insert(derive(Uuid::new_v4())),
"collision across 2k samples means the derivation lost entropy"
);
}
}
}