use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fingerprint([u8; 32]);
impl Fingerprint {
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn from_ed25519_pubkey(pubkey: &[u8]) -> Self {
Self(*blake3::hash(pubkey).as_bytes())
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
blake3::Hash::from_bytes(self.0).to_hex().to_string()
}
pub fn from_hex(s: &str) -> Option<Self> {
blake3::Hash::from_hex(s).ok().map(|h| Self(*h.as_bytes()))
}
}
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
impl fmt::Debug for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Fingerprint({})", self.to_hex())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derivation_is_deterministic() {
let a = Fingerprint::from_ed25519_pubkey(b"some-ed25519-pubkey-bytes");
let b = Fingerprint::from_ed25519_pubkey(b"some-ed25519-pubkey-bytes");
assert_eq!(a, b);
}
#[test]
fn different_keys_differ() {
let a = Fingerprint::from_ed25519_pubkey(b"key-one");
let b = Fingerprint::from_ed25519_pubkey(b"key-two");
assert_ne!(a, b);
}
#[test]
fn hex_round_trips() {
let fp = Fingerprint::from_ed25519_pubkey(b"round-trip-me");
let hex = fp.to_hex();
assert_eq!(hex.len(), 64);
assert_eq!(Fingerprint::from_hex(&hex), Some(fp));
}
#[test]
fn from_hex_rejects_garbage() {
assert_eq!(Fingerprint::from_hex("not-hex"), None);
assert_eq!(Fingerprint::from_hex("dead"), None); }
}