1use crate::error::{Error, Result};
27use serde::{Deserialize, Serialize};
28use std::fmt;
29
30#[cfg(feature = "pq")]
31use crate::crypto::PQSignature;
32
33const MULTIBASE_BASE58BTC: char = 'z';
35
36const MULTICODEC_MLDSA65: [u8; 2] = [0x13, 0x09];
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum DidMethod {
44 Lux,
46 Key,
48 Web,
50}
51
52impl DidMethod {
53 pub fn as_str(&self) -> &'static str {
55 match self {
56 DidMethod::Lux => "lux",
57 DidMethod::Key => "key",
58 DidMethod::Web => "web",
59 }
60 }
61
62 pub fn from_str(s: &str) -> Result<Self> {
64 match s {
65 "lux" => Ok(DidMethod::Lux),
66 "key" => Ok(DidMethod::Key),
67 "web" => Ok(DidMethod::Web),
68 _ => Err(Error::Identity(format!("unknown DID method: {}", s))),
69 }
70 }
71}
72
73impl fmt::Display for DidMethod {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "{}", self.as_str())
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
84pub struct Did {
85 pub method: DidMethod,
87 pub id: String,
89}
90
91impl Did {
92 pub fn new(method: DidMethod, id: String) -> Self {
94 Self { method, id }
95 }
96
97 pub fn parse(s: &str) -> Result<Self> {
106 if !s.starts_with("did:") {
108 return Err(Error::Identity(format!(
109 "invalid DID: must start with 'did:', got '{}'",
110 s
111 )));
112 }
113
114 let rest = &s[4..]; let parts: Vec<&str> = rest.splitn(2, ':').collect();
116
117 if parts.len() != 2 {
118 return Err(Error::Identity(format!(
119 "invalid DID format: expected 'did:method:id', got '{}'",
120 s
121 )));
122 }
123
124 let method = DidMethod::from_str(parts[0])?;
125 let id = parts[1].to_string();
126
127 if id.is_empty() {
128 return Err(Error::Identity("DID identifier cannot be empty".to_string()));
129 }
130
131 Ok(Self { method, id })
132 }
133
134 pub fn from_mldsa_key(public_key: &[u8]) -> Result<Self> {
147 const MLDSA_PUBLIC_KEY_SIZE: usize = 1952;
149
150 if public_key.len() != MLDSA_PUBLIC_KEY_SIZE {
151 return Err(Error::Identity(format!(
152 "invalid ML-DSA public key size: expected {}, got {}",
153 MLDSA_PUBLIC_KEY_SIZE,
154 public_key.len()
155 )));
156 }
157
158 let mut prefixed = Vec::with_capacity(MULTICODEC_MLDSA65.len() + public_key.len());
160 prefixed.extend_from_slice(&MULTICODEC_MLDSA65);
161 prefixed.extend_from_slice(public_key);
162
163 let encoded = bs58::encode(&prefixed).into_string();
165 let id = format!("{}{}", MULTIBASE_BASE58BTC, encoded);
166
167 Ok(Self {
168 method: DidMethod::Key,
169 id,
170 })
171 }
172
173 pub fn from_mldsa_key_lux(public_key: &[u8]) -> Result<Self> {
175 let key_did = Self::from_mldsa_key(public_key)?;
176 Ok(Self {
177 method: DidMethod::Lux,
178 id: key_did.id,
179 })
180 }
181
182 pub fn from_web(domain: &str, path: Option<&str>) -> Result<Self> {
191 if domain.is_empty() {
192 return Err(Error::Identity("domain cannot be empty".to_string()));
193 }
194
195 if domain.contains('/') || domain.contains(':') {
197 return Err(Error::Identity(format!(
198 "invalid domain for did:web: {}",
199 domain
200 )));
201 }
202
203 let id = match path {
204 Some(p) if !p.is_empty() => {
205 let path_parts = p.replace('/', ":");
207 format!("{}:{}", domain, path_parts)
208 }
209 _ => domain.to_string(),
210 };
211
212 Ok(Self {
213 method: DidMethod::Web,
214 id,
215 })
216 }
217
218 pub fn uri(&self) -> String {
220 format!("did:{}:{}", self.method, self.id)
221 }
222
223 pub fn document(&self) -> Result<DidDocument> {
227 let did_uri = self.uri();
228
229 let verification_method = match self.method {
231 DidMethod::Key | DidMethod::Lux => {
232 let key_material = self.extract_key_material()?;
234 VerificationMethod {
235 id: format!("{}#keys-1", did_uri),
236 type_: VerificationMethodType::JsonWebKey2020,
237 controller: did_uri.clone(),
238 public_key_multibase: Some(self.id.clone()),
239 public_key_jwk: None,
240 blockchain_account_id: if self.method == DidMethod::Lux {
241 Some(format!("lux:{}", hex::encode(&key_material[..20])))
242 } else {
243 None
244 },
245 }
246 }
247 DidMethod::Web => VerificationMethod {
248 id: format!("{}#keys-1", did_uri),
249 type_: VerificationMethodType::JsonWebKey2020,
250 controller: did_uri.clone(),
251 public_key_multibase: None,
252 public_key_jwk: None,
253 blockchain_account_id: None,
254 },
255 };
256
257 let service = Service {
259 id: format!("{}#zap-agent", did_uri),
260 type_: ServiceType::ZapAgent,
261 service_endpoint: ServiceEndpoint::Uri(format!("zap://{}", self.id)),
262 };
263
264 Ok(DidDocument {
265 context: vec![
266 "https://www.w3.org/ns/did/v1".to_string(),
267 "https://w3id.org/security/suites/jws-2020/v1".to_string(),
268 ],
269 id: did_uri.clone(),
270 controller: None,
271 verification_method: vec![verification_method],
272 authentication: vec![format!("{}#keys-1", did_uri)],
273 assertion_method: vec![format!("{}#keys-1", did_uri)],
274 key_agreement: vec![],
275 capability_invocation: vec![format!("{}#keys-1", did_uri)],
276 capability_delegation: vec![],
277 service: vec![service],
278 })
279 }
280
281 fn extract_key_material(&self) -> Result<Vec<u8>> {
283 if self.id.is_empty() {
284 return Err(Error::Identity("empty DID identifier".to_string()));
285 }
286
287 let first_char = self.id.chars().next().unwrap();
289 if first_char != MULTIBASE_BASE58BTC {
290 return Err(Error::Identity(format!(
291 "unsupported multibase encoding: expected '{}', got '{}'",
292 MULTIBASE_BASE58BTC, first_char
293 )));
294 }
295
296 let decoded = bs58::decode(&self.id[1..])
298 .into_vec()
299 .map_err(|e| Error::Identity(format!("invalid base58btc encoding: {}", e)))?;
300
301 if decoded.len() < 2 {
303 return Err(Error::Identity("DID identifier too short".to_string()));
304 }
305
306 if decoded[0..2] != MULTICODEC_MLDSA65 {
308 return Ok(decoded);
310 }
311
312 Ok(decoded[2..].to_vec())
313 }
314}
315
316impl fmt::Display for Did {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 write!(f, "{}", self.uri())
319 }
320}
321
322impl std::str::FromStr for Did {
323 type Err = Error;
324
325 fn from_str(s: &str) -> Result<Self> {
326 Did::parse(s)
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct DidDocument {
337 #[serde(rename = "@context")]
339 pub context: Vec<String>,
340
341 pub id: String,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub controller: Option<String>,
347
348 #[serde(default, skip_serializing_if = "Vec::is_empty")]
350 pub verification_method: Vec<VerificationMethod>,
351
352 #[serde(default, skip_serializing_if = "Vec::is_empty")]
354 pub authentication: Vec<String>,
355
356 #[serde(default, skip_serializing_if = "Vec::is_empty")]
358 pub assertion_method: Vec<String>,
359
360 #[serde(default, skip_serializing_if = "Vec::is_empty")]
362 pub key_agreement: Vec<String>,
363
364 #[serde(default, skip_serializing_if = "Vec::is_empty")]
366 pub capability_invocation: Vec<String>,
367
368 #[serde(default, skip_serializing_if = "Vec::is_empty")]
370 pub capability_delegation: Vec<String>,
371
372 #[serde(default, skip_serializing_if = "Vec::is_empty")]
374 pub service: Vec<Service>,
375}
376
377impl DidDocument {
378 pub fn primary_verification_method(&self) -> Option<&VerificationMethod> {
380 self.verification_method.first()
381 }
382
383 pub fn get_verification_method(&self, id: &str) -> Option<&VerificationMethod> {
385 self.verification_method.iter().find(|vm| vm.id == id)
386 }
387
388 pub fn get_service(&self, id: &str) -> Option<&Service> {
390 self.service.iter().find(|s| s.id == id)
391 }
392
393 pub fn to_json(&self) -> Result<String> {
395 serde_json::to_string_pretty(self).map_err(|e| Error::Identity(format!("JSON serialization failed: {}", e)))
396 }
397
398 pub fn from_json(json: &str) -> Result<Self> {
400 serde_json::from_str(json).map_err(|e| Error::Identity(format!("JSON deserialization failed: {}", e)))
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
406pub enum VerificationMethodType {
407 JsonWebKey2020,
409 Multikey,
411 #[serde(rename = "MlDsa65VerificationKey2024")]
413 MlDsa65VerificationKey2024,
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct VerificationMethod {
420 pub id: String,
422
423 #[serde(rename = "type")]
425 pub type_: VerificationMethodType,
426
427 pub controller: String,
429
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub public_key_multibase: Option<String>,
433
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub public_key_jwk: Option<serde_json::Value>,
437
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub blockchain_account_id: Option<String>,
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
445pub enum ServiceType {
446 ZapAgent,
448 #[serde(rename = "DIDCommMessaging")]
450 DidCommMessaging,
451 LinkedDomains,
453 CredentialRegistry,
455}
456
457#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
459#[serde(untagged)]
460pub enum ServiceEndpoint {
461 Uri(String),
463 Uris(Vec<String>),
465 Structured {
467 uri: String,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 accept: Option<Vec<String>>,
470 #[serde(skip_serializing_if = "Option::is_none")]
471 routing_keys: Option<Vec<String>>,
472 },
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub struct Service {
479 pub id: String,
481
482 #[serde(rename = "type")]
484 pub type_: ServiceType,
485
486 pub service_endpoint: ServiceEndpoint,
488}
489
490#[derive(Debug, Clone)]
494pub struct NodeIdentity {
495 pub did: Did,
497
498 pub public_key: Vec<u8>,
500
501 pub stake: Option<u64>,
503
504 pub stake_registry: Option<String>,
506
507 #[cfg(feature = "pq")]
508 signer: Option<PQSignature>,
510
511 #[cfg(not(feature = "pq"))]
512 _signer: std::marker::PhantomData<()>,
514}
515
516impl NodeIdentity {
517 pub fn new(did: Did, public_key: Vec<u8>) -> Self {
519 Self {
520 did,
521 public_key,
522 stake: None,
523 stake_registry: None,
524 #[cfg(feature = "pq")]
525 signer: None,
526 #[cfg(not(feature = "pq"))]
527 _signer: std::marker::PhantomData,
528 }
529 }
530
531 #[cfg(feature = "pq")]
533 pub fn generate() -> Result<Self> {
534 let signer = PQSignature::generate()?;
535 let public_key = signer.public_key_bytes();
536 let did = Did::from_mldsa_key_lux(&public_key)?;
537
538 Ok(Self {
539 did,
540 public_key,
541 stake: None,
542 stake_registry: None,
543 signer: Some(signer),
544 })
545 }
546
547 #[cfg(not(feature = "pq"))]
551 pub fn generate() -> Result<Self> {
552 Err(Error::Identity(
553 "node identity generation requires 'pq' feature".to_string(),
554 ))
555 }
556
557 #[cfg(feature = "pq")]
559 pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
560 let signer = self
561 .signer
562 .as_ref()
563 .ok_or_else(|| Error::Identity("no private key available for signing".to_string()))?;
564 signer.sign(message)
565 }
566
567 #[cfg(not(feature = "pq"))]
571 pub fn sign(&self, _message: &[u8]) -> Result<Vec<u8>> {
572 Err(Error::Identity(
573 "signing requires 'pq' feature".to_string(),
574 ))
575 }
576
577 #[cfg(feature = "pq")]
579 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
580 let verifier = match &self.signer {
581 Some(s) => s.verify(message, signature)?,
582 None => {
583 let v = PQSignature::from_public_key(&self.public_key)?;
584 v.verify(message, signature)?;
585 }
586 };
587 Ok(verifier)
588 }
589
590 #[cfg(not(feature = "pq"))]
594 pub fn verify(&self, _message: &[u8], _signature: &[u8]) -> Result<()> {
595 Err(Error::Identity(
596 "verification requires 'pq' feature".to_string(),
597 ))
598 }
599
600 pub fn with_stake(mut self, amount: u64) -> Self {
602 self.stake = Some(amount);
603 self
604 }
605
606 pub fn with_registry(mut self, registry: String) -> Self {
608 self.stake_registry = Some(registry);
609 self
610 }
611
612 pub fn document(&self) -> Result<DidDocument> {
614 self.did.document()
615 }
616
617 pub fn can_sign(&self) -> bool {
619 #[cfg(feature = "pq")]
620 {
621 self.signer.is_some()
622 }
623 #[cfg(not(feature = "pq"))]
624 {
625 false
626 }
627 }
628}
629
630pub trait StakeRegistry: Send + Sync {
635 fn get_stake(&self, did: &Did) -> Result<u64>;
637
638 fn set_stake(&mut self, did: &Did, amount: u64) -> Result<()>;
640
641 fn has_sufficient_stake(&self, did: &Did, minimum: u64) -> Result<bool> {
643 Ok(self.get_stake(did)? >= minimum)
644 }
645
646 fn total_stake(&self) -> Result<u64>;
648
649 fn stake_weight(&self, did: &Did) -> Result<f64> {
651 let stake = self.get_stake(did)?;
652 let total = self.total_stake()?;
653 if total == 0 {
654 return Ok(0.0);
655 }
656 Ok(stake as f64 / total as f64)
657 }
658}
659
660#[derive(Debug, Default)]
662pub struct InMemoryStakeRegistry {
663 stakes: std::collections::HashMap<String, u64>,
664}
665
666impl InMemoryStakeRegistry {
667 pub fn new() -> Self {
669 Self::default()
670 }
671}
672
673impl StakeRegistry for InMemoryStakeRegistry {
674 fn get_stake(&self, did: &Did) -> Result<u64> {
675 Ok(*self.stakes.get(&did.uri()).unwrap_or(&0))
676 }
677
678 fn set_stake(&mut self, did: &Did, amount: u64) -> Result<()> {
679 self.stakes.insert(did.uri(), amount);
680 Ok(())
681 }
682
683 fn total_stake(&self) -> Result<u64> {
684 Ok(self.stakes.values().sum())
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691
692 #[test]
693 fn test_did_parse_lux() {
694 let did = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
695 assert_eq!(did.method, DidMethod::Lux);
696 assert_eq!(did.id, "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
697 }
698
699 #[test]
700 fn test_did_parse_key() {
701 let did = Did::parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
702 assert_eq!(did.method, DidMethod::Key);
703 }
704
705 #[test]
706 fn test_did_parse_web() {
707 let did = Did::parse("did:web:example.com:users:alice").unwrap();
708 assert_eq!(did.method, DidMethod::Web);
709 assert_eq!(did.id, "example.com:users:alice");
710 }
711
712 #[test]
713 fn test_did_parse_invalid() {
714 assert!(Did::parse("not-a-did").is_err());
715 assert!(Did::parse("did:unknown:abc").is_err());
716 assert!(Did::parse("did:lux:").is_err());
717 }
718
719 #[test]
720 fn test_did_from_web() {
721 let did = Did::from_web("example.com", Some("users/alice")).unwrap();
722 assert_eq!(did.uri(), "did:web:example.com:users:alice");
723
724 let did2 = Did::from_web("example.com", None).unwrap();
725 assert_eq!(did2.uri(), "did:web:example.com");
726 }
727
728 #[test]
729 fn test_did_method_display() {
730 assert_eq!(DidMethod::Lux.to_string(), "lux");
731 assert_eq!(DidMethod::Key.to_string(), "key");
732 assert_eq!(DidMethod::Web.to_string(), "web");
733 }
734
735 #[test]
736 fn test_did_document_generation() {
737 let did = Did::parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
738 let doc = did.document().unwrap();
739
740 assert_eq!(doc.id, "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
741 assert!(!doc.verification_method.is_empty());
742 assert!(!doc.authentication.is_empty());
743 assert!(!doc.service.is_empty());
744 }
745
746 #[test]
747 fn test_did_document_json_roundtrip() {
748 let did = Did::parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
749 let doc = did.document().unwrap();
750
751 let json = doc.to_json().unwrap();
752 let parsed = DidDocument::from_json(&json).unwrap();
753
754 assert_eq!(doc.id, parsed.id);
755 assert_eq!(doc.verification_method.len(), parsed.verification_method.len());
756 }
757
758 #[test]
759 fn test_stake_registry() {
760 let mut registry = InMemoryStakeRegistry::new();
761 let did = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
762
763 assert_eq!(registry.get_stake(&did).unwrap(), 0);
764
765 registry.set_stake(&did, 1000).unwrap();
766 assert_eq!(registry.get_stake(&did).unwrap(), 1000);
767
768 assert!(registry.has_sufficient_stake(&did, 500).unwrap());
769 assert!(!registry.has_sufficient_stake(&did, 2000).unwrap());
770
771 assert_eq!(registry.total_stake().unwrap(), 1000);
772 }
773
774 #[test]
775 fn test_stake_weight() {
776 let mut registry = InMemoryStakeRegistry::new();
777 let did1 = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
778 let did2 = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doL").unwrap();
779
780 registry.set_stake(&did1, 750).unwrap();
781 registry.set_stake(&did2, 250).unwrap();
782
783 let weight1 = registry.stake_weight(&did1).unwrap();
784 let weight2 = registry.stake_weight(&did2).unwrap();
785
786 assert!((weight1 - 0.75).abs() < 0.001);
787 assert!((weight2 - 0.25).abs() < 0.001);
788 }
789
790 #[test]
791 fn test_node_identity_new() {
792 let did = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
793 let identity = NodeIdentity::new(did.clone(), vec![0u8; 1952]);
794
795 assert_eq!(identity.did, did);
796 assert_eq!(identity.stake, None);
797 assert!(!identity.can_sign());
798 }
799
800 #[test]
801 fn test_node_identity_with_stake() {
802 let did = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
803 let identity = NodeIdentity::new(did, vec![0u8; 1952])
804 .with_stake(5000)
805 .with_registry("lux:mainnet".to_string());
806
807 assert_eq!(identity.stake, Some(5000));
808 assert_eq!(identity.stake_registry, Some("lux:mainnet".to_string()));
809 }
810
811 #[cfg(feature = "pq")]
812 #[test]
813 fn test_node_identity_generate() {
814 let identity = NodeIdentity::generate().unwrap();
815
816 assert!(identity.can_sign());
817 assert_eq!(identity.did.method, DidMethod::Lux);
818 assert!(!identity.public_key.is_empty());
819
820 let message = b"test message";
822 let signature = identity.sign(message).unwrap();
823 identity.verify(message, &signature).unwrap();
824 }
825
826 #[cfg(feature = "pq")]
827 #[test]
828 fn test_did_from_mldsa_key() {
829 use crate::crypto::PQSignature;
830
831 let signer = PQSignature::generate().unwrap();
832 let public_key = signer.public_key_bytes();
833
834 let did = Did::from_mldsa_key(&public_key).unwrap();
835 assert_eq!(did.method, DidMethod::Key);
836 assert!(did.id.starts_with('z'));
837
838 let doc = did.document().unwrap();
840 assert!(!doc.verification_method.is_empty());
841 }
842}