Skip to main content

zap/
identity.rs

1//! W3C Decentralized Identifier (DID) Implementation
2//!
3//! Implements W3C DID Core 1.0 specification with support for:
4//! - did:lux - Lux blockchain-anchored DIDs
5//! - did:key - Self-certifying DIDs from cryptographic keys
6//! - did:web - DNS-based DIDs
7//!
8//! # Example
9//!
10//! ```rust,ignore
11//! use zap::identity::{Did, DidMethod, NodeIdentity};
12//!
13//! // Parse existing DID
14//! let did = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")?;
15//!
16//! // Create from ML-DSA public key
17//! let did = Did::from_mldsa_key(&public_key_bytes)?;
18//!
19//! // Generate DID Document
20//! let doc = did.document()?;
21//!
22//! // Create node identity
23//! let identity = NodeIdentity::generate()?;
24//! ```
25
26use crate::error::{Error, Result};
27use serde::{Deserialize, Serialize};
28use std::fmt;
29
30#[cfg(feature = "pq")]
31use crate::crypto::PQSignature;
32
33/// Multibase prefix for base58btc encoding
34const MULTIBASE_BASE58BTC: char = 'z';
35
36/// Multicodec prefix for ML-DSA-65 public key (0xED = Ed25519, 0x1309 = ML-DSA-65)
37/// Using 0x1309 as provisional multicodec for ML-DSA-65
38const MULTICODEC_MLDSA65: [u8; 2] = [0x13, 0x09];
39
40/// DID method identifier
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum DidMethod {
44    /// Lux blockchain-anchored DID
45    Lux,
46    /// Self-certifying DID from cryptographic key
47    Key,
48    /// DNS-based DID
49    Web,
50}
51
52impl DidMethod {
53    /// Get the method string for DID URI
54    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    /// Parse method from string
63    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/// W3C Decentralized Identifier (DID)
80///
81/// A DID is a globally unique identifier that enables verifiable,
82/// decentralized digital identity.
83#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
84pub struct Did {
85    /// The DID method (lux, key, web)
86    pub method: DidMethod,
87    /// The method-specific identifier
88    pub id: String,
89}
90
91impl Did {
92    /// Create a new DID with the specified method and ID
93    pub fn new(method: DidMethod, id: String) -> Self {
94        Self { method, id }
95    }
96
97    /// Parse a DID from a string in the format "did:method:id"
98    ///
99    /// # Example
100    ///
101    /// ```rust,ignore
102    /// let did = Did::parse("did:lux:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")?;
103    /// assert_eq!(did.method, DidMethod::Lux);
104    /// ```
105    pub fn parse(s: &str) -> Result<Self> {
106        // DID syntax: did:method:method-specific-id
107        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..]; // Skip "did:"
115        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    /// Create a DID from an ML-DSA-65 public key
135    ///
136    /// The resulting DID uses the did:key method with multibase-encoded
137    /// multicodec-prefixed public key.
138    ///
139    /// # Example
140    ///
141    /// ```rust,ignore
142    /// let signer = PQSignature::generate()?;
143    /// let did = Did::from_mldsa_key(&signer.public_key_bytes())?;
144    /// println!("DID: {}", did); // did:key:z6Mk...
145    /// ```
146    pub fn from_mldsa_key(public_key: &[u8]) -> Result<Self> {
147        // Expected ML-DSA-65 public key size
148        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        // Create multicodec-prefixed key
159        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        // Encode with multibase (base58btc)
164        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    /// Create a Lux blockchain-anchored DID from an ML-DSA public key
174    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    /// Create a web DID from a domain and optional path
183    ///
184    /// # Example
185    ///
186    /// ```rust,ignore
187    /// let did = Did::from_web("example.com", Some("users/alice"))?;
188    /// assert_eq!(did.to_string(), "did:web:example.com:users:alice");
189    /// ```
190    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        // Validate domain (basic check)
196        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                // Replace '/' with ':' per did:web spec
206                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    /// Get the full DID URI string
219    pub fn uri(&self) -> String {
220        format!("did:{}:{}", self.method, self.id)
221    }
222
223    /// Generate a W3C DID Document for this DID
224    ///
225    /// The document includes verification methods and service endpoints.
226    pub fn document(&self) -> Result<DidDocument> {
227        let did_uri = self.uri();
228
229        // Create verification method based on DID type
230        let verification_method = match self.method {
231            DidMethod::Key | DidMethod::Lux => {
232                // Extract public key from multibase-encoded identifier
233                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        // Create default service endpoint for ZAP protocol
258        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    /// Extract the raw key material from a did:key or did:lux identifier
282    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        // Check multibase prefix
288        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        // Decode base58btc (skip the multibase prefix)
297        let decoded = bs58::decode(&self.id[1..])
298            .into_vec()
299            .map_err(|e| Error::Identity(format!("invalid base58btc encoding: {}", e)))?;
300
301        // Skip multicodec prefix (2 bytes for ML-DSA-65)
302        if decoded.len() < 2 {
303            return Err(Error::Identity("DID identifier too short".to_string()));
304        }
305
306        // Verify multicodec prefix matches ML-DSA-65
307        if decoded[0..2] != MULTICODEC_MLDSA65 {
308            // Allow other key types, just return raw material
309            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/// W3C DID Document
331///
332/// A DID Document contains information associated with a DID,
333/// including verification methods and service endpoints.
334#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct DidDocument {
337    /// JSON-LD context
338    #[serde(rename = "@context")]
339    pub context: Vec<String>,
340
341    /// The DID subject
342    pub id: String,
343
344    /// Optional controller DID
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub controller: Option<String>,
347
348    /// Verification methods (public keys)
349    #[serde(default, skip_serializing_if = "Vec::is_empty")]
350    pub verification_method: Vec<VerificationMethod>,
351
352    /// Authentication verification methods
353    #[serde(default, skip_serializing_if = "Vec::is_empty")]
354    pub authentication: Vec<String>,
355
356    /// Assertion/credential issuance verification methods
357    #[serde(default, skip_serializing_if = "Vec::is_empty")]
358    pub assertion_method: Vec<String>,
359
360    /// Key agreement verification methods
361    #[serde(default, skip_serializing_if = "Vec::is_empty")]
362    pub key_agreement: Vec<String>,
363
364    /// Capability invocation verification methods
365    #[serde(default, skip_serializing_if = "Vec::is_empty")]
366    pub capability_invocation: Vec<String>,
367
368    /// Capability delegation verification methods
369    #[serde(default, skip_serializing_if = "Vec::is_empty")]
370    pub capability_delegation: Vec<String>,
371
372    /// Service endpoints
373    #[serde(default, skip_serializing_if = "Vec::is_empty")]
374    pub service: Vec<Service>,
375}
376
377impl DidDocument {
378    /// Get the primary verification method
379    pub fn primary_verification_method(&self) -> Option<&VerificationMethod> {
380        self.verification_method.first()
381    }
382
383    /// Get a verification method by ID
384    pub fn get_verification_method(&self, id: &str) -> Option<&VerificationMethod> {
385        self.verification_method.iter().find(|vm| vm.id == id)
386    }
387
388    /// Get a service by ID
389    pub fn get_service(&self, id: &str) -> Option<&Service> {
390        self.service.iter().find(|s| s.id == id)
391    }
392
393    /// Serialize to JSON
394    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    /// Deserialize from JSON
399    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/// Verification method type
405#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
406pub enum VerificationMethodType {
407    /// JSON Web Key 2020
408    JsonWebKey2020,
409    /// Multikey (new W3C standard)
410    Multikey,
411    /// ML-DSA-65 Verification Key 2024 (post-quantum)
412    #[serde(rename = "MlDsa65VerificationKey2024")]
413    MlDsa65VerificationKey2024,
414}
415
416/// Verification method (public key) in DID Document
417#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct VerificationMethod {
420    /// Verification method ID (e.g., "did:example:123#keys-1")
421    pub id: String,
422
423    /// Verification method type
424    #[serde(rename = "type")]
425    pub type_: VerificationMethodType,
426
427    /// Controller DID
428    pub controller: String,
429
430    /// Public key in multibase encoding
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub public_key_multibase: Option<String>,
433
434    /// Public key as JWK
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub public_key_jwk: Option<serde_json::Value>,
437
438    /// Blockchain account ID (for Lux DIDs)
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub blockchain_account_id: Option<String>,
441}
442
443/// Service type
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
445pub enum ServiceType {
446    /// ZAP Agent service
447    ZapAgent,
448    /// DID Communication
449    #[serde(rename = "DIDCommMessaging")]
450    DidCommMessaging,
451    /// Linked Domains
452    LinkedDomains,
453    /// Credential Registry
454    CredentialRegistry,
455}
456
457/// Service endpoint
458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
459#[serde(untagged)]
460pub enum ServiceEndpoint {
461    /// Single URI endpoint
462    Uri(String),
463    /// Multiple URI endpoints
464    Uris(Vec<String>),
465    /// Structured endpoint with additional properties
466    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/// Service endpoint in DID Document
476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub struct Service {
479    /// Service ID
480    pub id: String,
481
482    /// Service type
483    #[serde(rename = "type")]
484    pub type_: ServiceType,
485
486    /// Service endpoint URI or structured endpoint
487    pub service_endpoint: ServiceEndpoint,
488}
489
490/// Node identity combining DID with cryptographic keypair
491///
492/// Used for authenticated node participation in the ZAP network.
493#[derive(Debug, Clone)]
494pub struct NodeIdentity {
495    /// The node's DID
496    pub did: Did,
497
498    /// ML-DSA-65 public key bytes
499    pub public_key: Vec<u8>,
500
501    /// Optional staked amount (in smallest unit)
502    pub stake: Option<u64>,
503
504    /// Optional stake registry reference
505    pub stake_registry: Option<String>,
506
507    #[cfg(feature = "pq")]
508    /// ML-DSA-65 signer (private key)
509    signer: Option<PQSignature>,
510
511    #[cfg(not(feature = "pq"))]
512    /// PhantomData field for when pq feature is disabled
513    _signer: std::marker::PhantomData<()>,
514}
515
516impl NodeIdentity {
517    /// Create a new node identity from an existing DID and public key
518    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    /// Generate a new node identity with fresh ML-DSA-65 keypair
532    #[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    /// Generate a new node identity
548    ///
549    /// Returns an error when pq feature is disabled.
550    #[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    /// Sign a message with this node's private key
558    #[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    /// Sign a message with this node's private key
568    ///
569    /// Returns an error when pq feature is disabled.
570    #[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    /// Verify a signature against this node's public key
578    #[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    /// Verify a signature against this node's public key
591    ///
592    /// Returns an error when pq feature is disabled.
593    #[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    /// Set the stake amount for this node
601    pub fn with_stake(mut self, amount: u64) -> Self {
602        self.stake = Some(amount);
603        self
604    }
605
606    /// Set the stake registry reference
607    pub fn with_registry(mut self, registry: String) -> Self {
608        self.stake_registry = Some(registry);
609        self
610    }
611
612    /// Get the DID document for this node identity
613    pub fn document(&self) -> Result<DidDocument> {
614        self.did.document()
615    }
616
617    /// Check if this node has signing capability
618    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
630/// Trait for stake registry implementations
631///
632/// A stake registry tracks staked amounts for DIDs and can be used
633/// for weighted consensus and reputation systems.
634pub trait StakeRegistry: Send + Sync {
635    /// Get the staked amount for a DID
636    fn get_stake(&self, did: &Did) -> Result<u64>;
637
638    /// Set the staked amount for a DID
639    fn set_stake(&mut self, did: &Did, amount: u64) -> Result<()>;
640
641    /// Check if a DID has sufficient stake
642    fn has_sufficient_stake(&self, did: &Did, minimum: u64) -> Result<bool> {
643        Ok(self.get_stake(did)? >= minimum)
644    }
645
646    /// Get total staked amount across all DIDs
647    fn total_stake(&self) -> Result<u64>;
648
649    /// Get the stake weight (0.0-1.0) for a DID relative to total
650    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/// In-memory stake registry for testing
661#[derive(Debug, Default)]
662pub struct InMemoryStakeRegistry {
663    stakes: std::collections::HashMap<String, u64>,
664}
665
666impl InMemoryStakeRegistry {
667    /// Create a new empty stake registry
668    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        // Test signing
821        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        // Verify we can generate document
839        let doc = did.document().unwrap();
840        assert!(!doc.verification_method.is_empty());
841    }
842}