agent_store/
fingerprint.rs1use std::fmt;
14
15#[derive(Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Fingerprint([u8; 32]);
18
19impl Fingerprint {
20 pub fn from_bytes(bytes: [u8; 32]) -> Self {
22 Self(bytes)
23 }
24
25 pub fn from_ed25519_pubkey(pubkey: &[u8]) -> Self {
28 Self(*blake3::hash(pubkey).as_bytes())
29 }
30
31 pub fn as_bytes(&self) -> &[u8; 32] {
33 &self.0
34 }
35
36 pub fn to_hex(&self) -> String {
39 blake3::Hash::from_bytes(self.0).to_hex().to_string()
40 }
41
42 pub fn from_hex(s: &str) -> Option<Self> {
44 blake3::Hash::from_hex(s).ok().map(|h| Self(*h.as_bytes()))
45 }
46}
47
48impl fmt::Display for Fingerprint {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 f.write_str(&self.to_hex())
51 }
52}
53
54impl fmt::Debug for Fingerprint {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 write!(f, "Fingerprint({})", self.to_hex())
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn derivation_is_deterministic() {
66 let a = Fingerprint::from_ed25519_pubkey(b"some-ed25519-pubkey-bytes");
67 let b = Fingerprint::from_ed25519_pubkey(b"some-ed25519-pubkey-bytes");
68 assert_eq!(a, b);
69 }
70
71 #[test]
72 fn different_keys_differ() {
73 let a = Fingerprint::from_ed25519_pubkey(b"key-one");
74 let b = Fingerprint::from_ed25519_pubkey(b"key-two");
75 assert_ne!(a, b);
76 }
77
78 #[test]
79 fn hex_round_trips() {
80 let fp = Fingerprint::from_ed25519_pubkey(b"round-trip-me");
81 let hex = fp.to_hex();
82 assert_eq!(hex.len(), 64);
83 assert_eq!(Fingerprint::from_hex(&hex), Some(fp));
84 }
85
86 #[test]
87 fn from_hex_rejects_garbage() {
88 assert_eq!(Fingerprint::from_hex("not-hex"), None);
89 assert_eq!(Fingerprint::from_hex("dead"), None); }
91}