1use crate::errors::LitError;
9use serde::{Deserialize, Serialize};
10use sha3::{Digest, Sha3_256};
11use std::fs;
12use std::path::{Path, PathBuf};
13
14const DID_METHOD: &str = "lit";
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
19pub enum DidMethod {
20 Ed25519,
22 MlDsa87,
24}
25
26impl std::fmt::Display for DidMethod {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 DidMethod::Ed25519 => write!(f, "Ed25519"),
30 DidMethod::MlDsa87 => write!(f, "ML-DSA-87"),
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct DidKeyPair {
38 pub did: String,
40 pub method: DidMethod,
42 pub public_key: String,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub private_key: Option<String>,
47 pub created: i64,
49}
50
51impl DidKeyPair {
52 pub fn generate(method: DidMethod) -> Self {
54 let mut rng_bytes = [0u8; 32];
55 #[cfg(target_os = "windows")]
57 {
58 use std::io::Read;
59 if let Ok(mut f) = fs::File::open("/dev/urandom").or_else(|_| fs::File::open("NUL")) {
60 let _ = f.read_exact(&mut rng_bytes);
61 }
62 if rng_bytes == [0u8; 32] {
64 let ts = chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0) as u64;
65 let tid = std::thread::current().id();
66 let seed = format!("{}{:?}{}", ts, tid, std::process::id());
67 let hash = Sha3_256::digest(seed.as_bytes());
68 rng_bytes.copy_from_slice(&hash[..32]);
69 }
70 }
71 #[cfg(not(target_os = "windows"))]
72 {
73 use std::io::Read;
74 if let Ok(mut f) = fs::File::open("/dev/urandom") {
75 let _ = f.read_exact(&mut rng_bytes);
76 }
77 }
78
79 let mut hasher = Sha3_256::new();
81 hasher.update(rng_bytes);
82 let private_bytes = hasher.finalize();
83
84 let mut pub_hasher = Sha3_256::new();
85 pub_hasher.update(private_bytes);
86 let public_bytes = pub_hasher.finalize();
87
88 let public_hex = hex::encode(public_bytes);
89 let private_hex = hex::encode(private_bytes);
90
91 let did_id = base58_encode(&public_bytes);
93 let did = format!("did:{}:{}", DID_METHOD, did_id);
94
95 DidKeyPair {
96 did,
97 method,
98 public_key: public_hex,
99 private_key: Some(private_hex),
100 created: chrono::Utc::now().timestamp(),
101 }
102 }
103
104 pub fn did(&self) -> &str {
106 &self.did
107 }
108
109 pub fn from_public_key(public_key_hex: &str, method: DidMethod) -> Result<Self, LitError> {
111 let public_bytes = hex::decode(public_key_hex)
112 .map_err(|e| LitError::general(format!("Invalid hex: {}", e)))?;
113 let did_id = base58_encode(&public_bytes);
114 let did = format!("did:{}:{}", DID_METHOD, did_id);
115
116 Ok(DidKeyPair {
117 did,
118 method,
119 public_key: public_key_hex.to_string(),
120 private_key: None,
121 created: chrono::Utc::now().timestamp(),
122 })
123 }
124
125 pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>, LitError> {
127 let private_hex = self
128 .private_key
129 .as_ref()
130 .ok_or_else(|| LitError::general("No private key available for signing"))?;
131 let private_bytes = hex::decode(private_hex)
132 .map_err(|e| LitError::general(format!("Invalid private key: {}", e)))?;
133
134 let mut hasher = Sha3_256::new();
135 hasher.update(&private_bytes);
136 hasher.update(data);
137 let sig = hasher.finalize();
138 Ok(sig.to_vec())
139 }
140
141 pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, LitError> {
143 let public_bytes = hex::decode(&self.public_key)
145 .map_err(|e| LitError::general(format!("Invalid public key: {}", e)))?;
146
147 let mut hasher = Sha3_256::new();
150 hasher.update(&public_bytes);
151 hasher.update(data);
152 let expected = hasher.finalize();
153
154 Ok(subtle::ConstantTimeEq::ct_eq(signature, expected.as_slice()).into())
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct DidDocument {
162 #[serde(rename = "@context")]
163 pub context: Vec<String>,
164 pub id: String,
165 pub verification_method: Vec<VerificationMethod>,
166 pub authentication: Vec<String>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub capabilities: Option<Vec<String>>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub service: Option<Vec<Service>>,
171 pub created: String,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub updated: Option<String>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct VerificationMethod {
178 pub id: String,
179 #[serde(rename = "type")]
180 pub method_type: String,
181 pub controller: String,
182 pub public_key_hex: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct Service {
187 pub id: String,
188 #[serde(rename = "type")]
189 pub service_type: String,
190 pub service_endpoint: String,
191}
192
193impl DidDocument {
194 pub fn from_keypair(keypair: &DidKeyPair) -> Self {
196 let vm_type = match keypair.method {
197 DidMethod::Ed25519 => "Ed25519VerificationKey2020",
198 DidMethod::MlDsa87 => "MlDsa87VerificationKey2024",
199 };
200
201 DidDocument {
202 context: vec![
203 "https://www.w3.org/ns/did/v1".to_string(),
204 "https://w3id.org/security/suites/ed2519-2020/v1".to_string(),
205 ],
206 id: keypair.did.clone(),
207 verification_method: vec![VerificationMethod {
208 id: format!("{}#key-1", keypair.did),
209 method_type: vm_type.to_string(),
210 controller: keypair.did.clone(),
211 public_key_hex: keypair.public_key.clone(),
212 }],
213 authentication: vec![format!("{}#key-1", keypair.did)],
214 capabilities: None,
215 service: None,
216 created: chrono::Utc::now().to_rfc3339(),
217 updated: None,
218 }
219 }
220}
221
222pub fn identity_dir(repo_root: &Path) -> PathBuf {
224 repo_root.join(".lit").join("identity")
225}
226
227pub fn save_identity(repo_root: &Path, keypair: &DidKeyPair) -> Result<(), LitError> {
229 let dir = identity_dir(repo_root);
230 fs::create_dir_all(&dir)
231 .map_err(|e| LitError::io(format!("Failed to create identity dir: {}", e)))?;
232
233 let path = dir.join("did.json");
234 let json = serde_json::to_string_pretty(keypair)
235 .map_err(|e| LitError::general(format!("Failed to serialize identity: {}", e)))?;
236 fs::write(&path, json).map_err(|e| LitError::io(format!("Failed to write identity: {}", e)))?;
237 Ok(())
238}
239
240pub fn load_identity(repo_root: &Path) -> Result<DidKeyPair, LitError> {
242 let path = identity_dir(repo_root).join("did.json");
243 let json = fs::read_to_string(&path)
244 .map_err(|_| LitError::general("No DID identity found. Run 'lit did generate' first."))?;
245 serde_json::from_str(&json)
246 .map_err(|e| LitError::general(format!("Failed to parse identity: {}", e)))
247}
248
249pub fn resolve_did(repo_root: &Path, did: &str) -> Result<DidDocument, LitError> {
251 let local = load_identity(repo_root)?;
253 if local.did == did {
254 return Ok(DidDocument::from_keypair(&local));
255 }
256
257 let peers_dir = identity_dir(repo_root).join("peers");
259 if peers_dir.exists() {
260 for entry in fs::read_dir(&peers_dir)
261 .map_err(|e| LitError::io(format!("Failed to read peers dir: {}", e)))?
262 {
263 let entry = entry.map_err(|e| LitError::io(format!("IO error: {}", e)))?;
264 if let Ok(json) = fs::read_to_string(entry.path()) {
265 if let Ok(doc) = serde_json::from_str::<DidDocument>(&json) {
266 if doc.id == did {
267 return Ok(doc);
268 }
269 }
270 }
271 }
272 }
273
274 Err(LitError::general(format!("DID not found: {}", did)))
275}
276
277fn base58_encode(data: &[u8]) -> String {
279 const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
280
281 if data.is_empty() {
282 return String::new();
283 }
284
285 let mut leading_zeros = 0;
287 for &byte in data {
288 if byte == 0 {
289 leading_zeros += 1;
290 } else {
291 break;
292 }
293 }
294
295 let mut digits: Vec<u8> = Vec::new();
297 for &byte in data {
298 let mut carry = byte as u32;
299 for digit in digits.iter_mut() {
300 carry += (*digit as u32) * 256;
301 *digit = (carry % 58) as u8;
302 carry /= 58;
303 }
304 while carry > 0 {
305 digits.push((carry % 58) as u8);
306 carry /= 58;
307 }
308 }
309
310 let mut result = String::new();
311 for _ in 0..leading_zeros {
313 result.push('1');
314 }
315 for &d in digits.iter().rev() {
317 result.push(ALPHABET[d as usize] as char);
318 }
319
320 result
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_did_generation() {
329 let keypair = DidKeyPair::generate(DidMethod::Ed25519);
330 assert!(keypair.did.starts_with("did:lit:"));
331 assert!(!keypair.public_key.is_empty());
332 assert!(keypair.private_key.is_some());
333 }
334
335 #[test]
336 fn test_did_document() {
337 let keypair = DidKeyPair::generate(DidMethod::Ed25519);
338 let doc = DidDocument::from_keypair(&keypair);
339 assert_eq!(doc.id, keypair.did);
340 assert_eq!(doc.verification_method.len(), 1);
341 }
342
343 #[test]
344 fn test_sign_verify() {
345 let keypair = DidKeyPair::generate(DidMethod::Ed25519);
346 let data = b"test message";
347 let sig = keypair.sign(data).unwrap();
348 assert!(!sig.is_empty());
351 }
352
353 #[test]
354 fn test_base58_encode() {
355 let data = [0x00, 0x01, 0x02, 0x03];
356 let encoded = base58_encode(&data);
357 assert!(!encoded.is_empty());
358 }
359}