saorsa_core/identity/
node_identity.rs1use crate::error::IdentityError;
27use crate::{P2PError, Result};
28use serde::{Deserialize, Serialize};
29use sha2::{Digest, Sha256};
30use std::fmt;
31
32use ant_quic::crypto::pqc::types::{MlDsaPublicKey, MlDsaSecretKey, MlDsaSignature};
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
39pub struct NodeId(pub [u8; 32]);
40
41impl NodeId {
42 pub fn from_public_key(public_key: &MlDsaPublicKey) -> Self {
44 let mut hasher = Sha256::new();
45 hasher.update(public_key.as_bytes());
46 let hash = hasher.finalize();
47 let mut id = [0u8; 32];
48 id.copy_from_slice(&hash);
49 Self(id)
50 }
51
52 pub fn to_bytes(&self) -> &[u8; 32] {
54 &self.0
55 }
56
57 pub fn xor_distance(&self, other: &NodeId) -> [u8; 32] {
59 let mut distance = [0u8; 32];
60 for (i, out) in distance.iter_mut().enumerate() {
61 *out = self.0[i] ^ other.0[i];
62 }
63 distance
64 }
65
66 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self> {
68 if bytes.len() != 1952 {
70 return Err(P2PError::Identity(IdentityError::InvalidFormat(
71 "Invalid ML-DSA public key length".to_string().into(),
72 )));
73 }
74
75 let public_key = MlDsaPublicKey::from_bytes(bytes).map_err(|e| {
77 IdentityError::InvalidFormat(format!("Invalid ML-DSA public key: {:?}", e).into())
78 })?;
79
80 Ok(NodeId::from_public_key(&public_key))
81 }
82
83 pub fn from_bytes(bytes: [u8; 32]) -> Self {
85 Self(bytes)
86 }
87}
88
89impl fmt::Display for NodeId {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{}", hex::encode(&self.0[..8])) }
93}
94
95#[derive(Clone)]
97pub struct PublicNodeIdentity {
98 public_key: MlDsaPublicKey,
100 node_id: NodeId,
102}
103
104impl PublicNodeIdentity {
105 pub fn node_id(&self) -> &NodeId {
107 &self.node_id
108 }
109
110 pub fn public_key(&self) -> &MlDsaPublicKey {
112 &self.public_key
113 }
114
115 }
118
119pub struct NodeIdentity {
121 secret_key: MlDsaSecretKey,
123 public_key: MlDsaPublicKey,
125 node_id: NodeId,
127}
128
129impl NodeIdentity {
130 pub fn generate() -> Result<Self> {
132 let (public_key, secret_key) =
134 crate::quantum_crypto::generate_ml_dsa_keypair().map_err(|e| {
135 P2PError::Identity(IdentityError::InvalidFormat(
136 format!("Failed to generate ML-DSA key pair: {}", e).into(),
137 ))
138 })?;
139
140 let node_id = NodeId::from_public_key(&public_key);
141
142 Ok(Self {
143 secret_key,
144 public_key,
145 node_id,
146 })
147 }
148
149 pub fn from_seed(_seed: &[u8; 32]) -> Result<Self> {
151 let (public_key, secret_key) =
155 crate::quantum_crypto::generate_ml_dsa_keypair().map_err(|e| {
156 P2PError::Identity(IdentityError::InvalidFormat(
157 format!("Failed to generate ML-DSA key pair: {}", e).into(),
158 ))
159 })?;
160
161 let node_id = NodeId::from_public_key(&public_key);
162
163 Ok(Self {
164 secret_key,
165 public_key,
166 node_id,
167 })
168 }
169
170 pub fn node_id(&self) -> &NodeId {
172 &self.node_id
173 }
174
175 pub fn public_key(&self) -> &MlDsaPublicKey {
177 &self.public_key
178 }
179
180 pub fn secret_key_bytes(&self) -> &[u8] {
184 self.secret_key.as_bytes()
185 }
186
187 pub fn sign(&self, message: &[u8]) -> Result<MlDsaSignature> {
189 crate::quantum_crypto::ml_dsa_sign(&self.secret_key, message).map_err(|e| {
190 P2PError::Identity(IdentityError::InvalidFormat(
191 format!("ML-DSA signing failed: {:?}", e).into(),
192 ))
193 })
194 }
195
196 pub fn verify(&self, message: &[u8], signature: &MlDsaSignature) -> Result<bool> {
198 crate::quantum_crypto::ml_dsa_verify(&self.public_key, message, signature).map_err(|e| {
199 P2PError::Identity(IdentityError::InvalidFormat(
200 format!("ML-DSA verification failed: {:?}", e).into(),
201 ))
202 })
203 }
204
205 pub fn to_public(&self) -> PublicNodeIdentity {
207 PublicNodeIdentity {
208 public_key: self.public_key.clone(),
209 node_id: self.node_id.clone(),
210 }
211 }
212}
213
214impl NodeIdentity {
215 pub fn from_secret_key(_secret_key: MlDsaSecretKey) -> Result<Self> {
219 Err(P2PError::Identity(IdentityError::InvalidFormat(
220 "Creating identity from secret key alone is not supported"
221 .to_string()
222 .into(),
223 )))
224 }
225}
226
227impl NodeIdentity {
228 pub async fn save_to_file(&self, path: &std::path::Path) -> Result<()> {
230 use tokio::fs;
231 let data = self.export();
232 let json = serde_json::to_string_pretty(&data).map_err(|e| {
233 P2PError::Identity(crate::error::IdentityError::InvalidFormat(
234 format!("Failed to serialize identity: {}", e).into(),
235 ))
236 })?;
237
238 if let Some(parent) = path.parent() {
239 fs::create_dir_all(parent).await.map_err(|e| {
240 P2PError::Identity(crate::error::IdentityError::InvalidFormat(
241 format!("Failed to create directory: {}", e).into(),
242 ))
243 })?;
244 }
245
246 tokio::fs::write(path, json).await.map_err(|e| {
247 P2PError::Identity(crate::error::IdentityError::InvalidFormat(
248 format!("Failed to write identity file: {}", e).into(),
249 ))
250 })?;
251 Ok(())
252 }
253
254 pub async fn load_from_file(path: &std::path::Path) -> Result<Self> {
256 let json = tokio::fs::read_to_string(path).await.map_err(|e| {
257 P2PError::Identity(crate::error::IdentityError::InvalidFormat(
258 format!("Failed to read identity file: {}", e).into(),
259 ))
260 })?;
261 let data: IdentityData = serde_json::from_str(&json).map_err(|e| {
262 P2PError::Identity(crate::error::IdentityError::InvalidFormat(
263 format!("Failed to deserialize identity: {}", e).into(),
264 ))
265 })?;
266 Self::import(&data)
267 }
268}
269
270#[derive(Serialize, Deserialize)]
272pub struct IdentityData {
273 pub secret_key: Vec<u8>,
275}
276
277impl NodeIdentity {
278 pub fn export(&self) -> IdentityData {
280 IdentityData {
281 secret_key: self.secret_key.as_bytes().to_vec(),
282 }
283 }
284
285 pub fn import(_data: &IdentityData) -> Result<Self> {
288 Err(P2PError::Identity(IdentityError::InvalidFormat(
290 "Import from persisted data not yet implemented"
291 .to_string()
292 .into(),
293 )))
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_node_id_generation() {
303 let (public_key, _secret_key) = crate::quantum_crypto::generate_ml_dsa_keypair()
304 .expect("ML-DSA key generation should succeed");
305 let node_id = NodeId::from_public_key(&public_key);
306
307 assert_eq!(node_id.to_bytes().len(), 32);
309
310 let node_id2 = NodeId::from_public_key(&public_key);
312 assert_eq!(node_id, node_id2);
313 }
314
315 #[test]
316 fn test_xor_distance() {
317 let id1 = NodeId([0u8; 32]);
318 let mut id2_bytes = [0u8; 32];
319 id2_bytes[0] = 0xFF;
320 let id2 = NodeId(id2_bytes);
321
322 let distance = id1.xor_distance(&id2);
323 assert_eq!(distance[0], 0xFF);
324 for i in 1..32 {
325 assert_eq!(distance[i], 0);
326 }
327 }
328
329 #[test]
330 fn test_proof_of_work() {
331 }
333
334 #[test]
335 fn test_identity_generation() {
336 let identity = NodeIdentity::generate().expect("Identity generation should succeed");
337
338 let message = b"Hello, P2P!";
340 let signature = identity.sign(message).unwrap();
341 assert!(identity.verify(message, &signature).unwrap());
342
343 assert!(!identity.verify(b"Wrong message", &signature).unwrap());
345 }
346
347 #[test]
348 fn test_deterministic_generation() {
349 let seed = [0x42; 32];
350 let identity1 = NodeIdentity::from_seed(&seed).expect("Identity from seed should succeed");
351 let identity2 = NodeIdentity::from_seed(&seed).expect("Identity from seed should succeed");
352
353 assert_eq!(identity1.node_id, identity2.node_id);
355 assert_eq!(
356 identity1.public_key().as_bytes(),
357 identity2.public_key().as_bytes()
358 );
359 }
360
361 #[test]
362 fn test_identity_persistence() {
363 let identity = NodeIdentity::generate().expect("Identity generation should succeed");
364
365 let data = identity.export();
367
368 let imported = NodeIdentity::import(&data).expect("Import should succeed with valid data");
370
371 assert_eq!(identity.node_id, imported.node_id);
373 assert_eq!(
374 identity.public_key().as_bytes(),
375 imported.public_key().as_bytes()
376 );
377
378 let message = b"Test message";
380 let signature = imported.sign(message);
381 assert!(identity.verify(message, &signature.unwrap()).unwrap());
382 }
383}