peat_mesh/security/
device_id.rs1use super::error::SecurityError;
4use crate::transport::NodeId;
5use ed25519_dalek::VerifyingKey;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use std::fmt;
9
10#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub struct DeviceId([u8; 16]);
34
35impl DeviceId {
36 pub fn from_public_key(public_key: &VerifyingKey) -> Self {
40 let mut hasher = Sha256::new();
41 hasher.update(public_key.as_bytes());
42 let hash = hasher.finalize();
43
44 let mut id = [0u8; 16];
45 id.copy_from_slice(&hash[..16]);
46 DeviceId(id)
47 }
48
49 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SecurityError> {
51 if bytes.len() != 32 {
52 return Err(SecurityError::InvalidPublicKey(format!(
53 "expected 32 bytes, got {}",
54 bytes.len()
55 )));
56 }
57
58 let verifying_key = VerifyingKey::from_bytes(bytes.try_into().unwrap())
59 .map_err(|e| SecurityError::InvalidPublicKey(e.to_string()))?;
60
61 Ok(Self::from_public_key(&verifying_key))
62 }
63
64 pub fn from_bytes(bytes: [u8; 16]) -> Self {
66 DeviceId(bytes)
67 }
68
69 pub fn as_bytes(&self) -> &[u8; 16] {
71 &self.0
72 }
73
74 pub fn to_hex(self) -> String {
76 hex_encode(&self.0)
77 }
78
79 pub fn from_hex(hex: &str) -> Result<Self, SecurityError> {
81 let bytes = hex_decode(hex)
82 .map_err(|e| SecurityError::InvalidDeviceId(format!("invalid hex: {}", e)))?;
83
84 if bytes.len() != 16 {
85 return Err(SecurityError::InvalidDeviceId(format!(
86 "expected 16 bytes (32 hex chars), got {} bytes",
87 bytes.len()
88 )));
89 }
90
91 let mut id = [0u8; 16];
92 id.copy_from_slice(&bytes);
93 Ok(DeviceId(id))
94 }
95}
96
97impl fmt::Display for DeviceId {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(f, "{}", self.to_hex())
100 }
101}
102
103impl fmt::Debug for DeviceId {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 write!(f, "DeviceId({})", self.to_hex())
106 }
107}
108
109impl From<DeviceId> for NodeId {
110 fn from(device_id: DeviceId) -> Self {
111 NodeId::new(device_id.to_hex())
112 }
113}
114
115impl TryFrom<&NodeId> for DeviceId {
116 type Error = SecurityError;
117
118 fn try_from(node_id: &NodeId) -> Result<Self, Self::Error> {
119 DeviceId::from_hex(node_id.as_str())
120 }
121}
122
123fn hex_encode(bytes: &[u8]) -> String {
125 bytes.iter().map(|b| format!("{:02x}", b)).collect()
126}
127
128fn hex_decode(hex: &str) -> Result<Vec<u8>, String> {
129 if !hex.len().is_multiple_of(2) {
130 return Err("odd length hex string".to_string());
131 }
132
133 (0..hex.len())
134 .step_by(2)
135 .map(|i| {
136 u8::from_str_radix(&hex[i..i + 2], 16)
137 .map_err(|e| format!("invalid hex at position {}: {}", i, e))
138 })
139 .collect()
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use ed25519_dalek::SigningKey;
146 use rand_core::OsRng;
147
148 #[test]
149 fn test_device_id_from_public_key_deterministic() {
150 let signing_key = SigningKey::generate(&mut OsRng);
151 let verifying_key = signing_key.verifying_key();
152
153 let id1 = DeviceId::from_public_key(&verifying_key);
154 let id2 = DeviceId::from_public_key(&verifying_key);
155
156 assert_eq!(id1, id2);
157 }
158
159 #[test]
160 fn test_device_id_different_keys_different_ids() {
161 let key1 = SigningKey::generate(&mut OsRng);
162 let key2 = SigningKey::generate(&mut OsRng);
163
164 let id1 = DeviceId::from_public_key(&key1.verifying_key());
165 let id2 = DeviceId::from_public_key(&key2.verifying_key());
166
167 assert_ne!(id1, id2);
168 }
169
170 #[test]
171 fn test_device_id_hex_roundtrip() {
172 let key = SigningKey::generate(&mut OsRng);
173 let id = DeviceId::from_public_key(&key.verifying_key());
174
175 let hex = id.to_hex();
176 assert_eq!(hex.len(), 32);
177
178 let parsed = DeviceId::from_hex(&hex).unwrap();
179 assert_eq!(id, parsed);
180 }
181
182 #[test]
183 fn test_device_id_to_node_id() {
184 let key = SigningKey::generate(&mut OsRng);
185 let device_id = DeviceId::from_public_key(&key.verifying_key());
186
187 let node_id: NodeId = device_id.into();
188 assert_eq!(node_id.as_str(), device_id.to_hex());
189 }
190
191 #[test]
192 fn test_device_id_from_invalid_hex() {
193 let result = DeviceId::from_hex("not-valid-hex");
194 assert!(result.is_err());
195
196 let result = DeviceId::from_hex("abc"); assert!(result.is_err());
198
199 let result = DeviceId::from_hex("00112233"); assert!(result.is_err());
201 }
202
203 #[test]
204 fn test_from_public_key_bytes() {
205 let key = SigningKey::generate(&mut OsRng);
206 let pk_bytes = key.verifying_key().to_bytes();
207
208 let id = DeviceId::from_public_key_bytes(&pk_bytes).unwrap();
209 let expected = DeviceId::from_public_key(&key.verifying_key());
210 assert_eq!(id, expected);
211 }
212
213 #[test]
214 fn test_from_public_key_bytes_wrong_length() {
215 let result = DeviceId::from_public_key_bytes(&[0u8; 16]);
216 assert!(result.is_err());
217 }
218
219 #[test]
220 fn test_from_bytes_and_as_bytes() {
221 let raw = [1u8; 16];
222 let id = DeviceId::from_bytes(raw);
223 assert_eq!(id.as_bytes(), &raw);
224 }
225
226 #[test]
227 fn test_display() {
228 let id = DeviceId::from_bytes([0xab; 16]);
229 let s = format!("{}", id);
230 assert_eq!(s, "abababababababababababababababab");
231 }
232
233 #[test]
234 fn test_debug() {
235 let id = DeviceId::from_bytes([0xcd; 16]);
236 let s = format!("{:?}", id);
237 assert!(s.starts_with("DeviceId("));
238 assert!(s.contains("cdcdcdcd"));
239 }
240
241 #[test]
242 fn test_try_from_node_id() {
243 let key = SigningKey::generate(&mut OsRng);
244 let device_id = DeviceId::from_public_key(&key.verifying_key());
245 let node_id: NodeId = device_id.into();
246
247 let back: DeviceId = (&node_id).try_into().unwrap();
248 assert_eq!(back, device_id);
249 }
250
251 #[test]
252 fn test_try_from_invalid_node_id() {
253 let node_id = NodeId::new("not-hex".into());
254 let result: Result<DeviceId, _> = (&node_id).try_into();
255 assert!(result.is_err());
256 }
257
258 #[test]
259 fn test_hex_decode_invalid_chars() {
260 assert!(hex_decode("zz").is_err());
261 assert!(hex_decode("gg").is_err());
262 }
263
264 #[test]
265 fn test_hex_roundtrip() {
266 let bytes = vec![0x00, 0xff, 0x0a, 0xb5];
267 let encoded = hex_encode(&bytes);
268 assert_eq!(encoded, "00ff0ab5");
269 let decoded = hex_decode(&encoded).unwrap();
270 assert_eq!(decoded, bytes);
271 }
272}