1use std::fmt;
11
12use sha2::{Digest, Sha256};
13
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
16pub struct PeerId([u8; 32]);
17
18impl PeerId {
19 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
21 PeerId(bytes)
22 }
23
24 pub const fn as_bytes(&self) -> &[u8; 32] {
26 &self.0
27 }
28
29 pub fn to_hex(&self) -> String {
31 let mut s = String::with_capacity(64);
32 for b in self.0 {
33 s.push(char::from_digit((b >> 4) as u32, 16).unwrap());
34 s.push(char::from_digit((b & 0x0f) as u32, 16).unwrap());
35 }
36 s
37 }
38
39 pub fn from_hex(hex: &str) -> Option<Self> {
41 if hex.len() != 64 {
42 return None;
43 }
44 let mut out = [0u8; 32];
45 for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
46 let hi = (chunk[0] as char).to_digit(16)?;
47 let lo = (chunk[1] as char).to_digit(16)?;
48 out[i] = ((hi << 4) | lo) as u8;
49 }
50 Some(PeerId(out))
51 }
52}
53
54impl fmt::Debug for PeerId {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 write!(f, "PeerId({})", self.to_hex())
57 }
58}
59
60impl fmt::Display for PeerId {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.write_str(&self.to_hex())
63 }
64}
65
66pub fn peer_id_from_tls_spki_der(spki_der: &[u8]) -> PeerId {
72 PeerId(Sha256::digest(spki_der).into())
73}
74
75pub fn peer_id_from_leaf_cert_der(cert_der: &[u8]) -> Option<PeerId> {
78 let (_, x509) = x509_parser::parse_x509_certificate(cert_der).ok()?;
79 Some(peer_id_from_tls_spki_der(
80 x509.tbs_certificate.subject_pki.raw,
81 ))
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn hex_round_trips() {
90 let id = PeerId::from_bytes([0xABu8; 32]);
91 let hex = id.to_hex();
92 assert_eq!(hex.len(), 64);
93 assert_eq!(PeerId::from_hex(&hex), Some(id));
94 }
95
96 #[test]
97 fn from_hex_rejects_bad_input() {
98 assert_eq!(PeerId::from_hex("tooshort"), None);
99 assert_eq!(PeerId::from_hex(&"z".repeat(64)), None);
100 }
101
102 #[test]
103 fn peer_id_is_sha256_of_spki() {
104 let spki = b"a fake SPKI DER blob";
105 let expected: [u8; 32] = Sha256::digest(spki).into();
106 assert_eq!(peer_id_from_tls_spki_der(spki).as_bytes(), &expected);
107 }
108
109 #[test]
110 fn peer_id_from_unparseable_cert_is_none() {
111 assert_eq!(peer_id_from_leaf_cert_der(b"not a certificate"), None);
112 }
113
114 #[test]
115 fn display_equals_hex() {
116 let id = PeerId::from_bytes([0x01u8; 32]);
117 assert_eq!(format!("{id}"), id.to_hex());
118 }
119}