qudag_network/
dark_resolver.rs

1use blake3::Hasher;
2use bs58;
3use rand_core::{CryptoRng, RngCore};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7use std::time::{SystemTime, UNIX_EPOCH};
8use thiserror::Error;
9
10// Import crypto primitives from the crypto module
11use qudag_crypto::ml_dsa::{MlDsaError, MlDsaKeyPair, MlDsaPublicKey};
12use qudag_crypto::ml_kem::MlKem768;
13
14use crate::types::NetworkAddress;
15use crate::types::PeerId;
16
17/// Errors that can occur during dark domain operations
18#[derive(Error, Debug)]
19pub enum DarkResolverError {
20    #[error("Domain name already registered")]
21    DomainExists,
22    #[error("Domain not found")]
23    DomainNotFound,
24    #[error("Invalid domain name format")]
25    InvalidDomain,
26    #[error("Cryptographic operation failed: {0}")]
27    CryptoError(String),
28    #[error("Domain record access error")]
29    StorageError,
30    #[error("Domain has expired")]
31    DomainExpired,
32    #[error("Invalid signature")]
33    InvalidSignature,
34    #[error("Address generation failed: {0}")]
35    AddressGenerationError(String),
36    #[error("DHT operation failed: {0}")]
37    DhtError(String),
38    #[error("ML-DSA error: {0}")]
39    MlDsaError(#[from] MlDsaError),
40}
41
42/// A resolved dark domain record with quantum-resistant signatures
43#[derive(Clone, Debug, Serialize, Deserialize)]
44pub struct DarkDomainRecord {
45    /// ML-DSA public key for signature verification
46    pub signing_public_key: Vec<u8>,
47    /// ML-KEM public key for encryption
48    pub encryption_public_key: Vec<u8>,
49    /// Network addresses (can have multiple)
50    pub addresses: Vec<NetworkAddress>,
51    /// Human-readable alias
52    pub alias: Option<String>,
53    /// Time-to-live in seconds
54    pub ttl: u32,
55    /// Registration timestamp
56    pub registered_at: u64,
57    /// Expiration timestamp
58    pub expires_at: u64,
59    /// Owner's PeerId
60    pub owner_id: PeerId,
61    /// Record signature using ML-DSA
62    pub signature: Vec<u8>,
63    /// Additional metadata
64    pub metadata: HashMap<String, String>,
65}
66
67/// Dark address derived from ML-DSA public key
68#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
69pub struct DarkAddress {
70    /// The .dark address (base58 encoded hash)
71    pub address: String,
72    /// Full domain name (e.g., "mynode.dark")
73    pub domain: String,
74}
75
76/// Address book entry for human-readable names
77#[derive(Clone, Debug, Serialize, Deserialize)]
78pub struct AddressBookEntry {
79    /// Human-readable name
80    pub name: String,
81    /// Associated dark address
82    pub dark_address: DarkAddress,
83    /// Optional notes
84    pub notes: Option<String>,
85    /// Added timestamp
86    pub added_at: u64,
87}
88
89impl DarkDomainRecord {
90    /// Create a new domain record
91    pub fn new(
92        signing_keypair: &MlDsaKeyPair,
93        encryption_public_key: Vec<u8>,
94        addresses: Vec<NetworkAddress>,
95        alias: Option<String>,
96        ttl: u32,
97        owner_id: PeerId,
98    ) -> Result<Self, DarkResolverError> {
99        let now = SystemTime::now()
100            .duration_since(UNIX_EPOCH)
101            .unwrap()
102            .as_secs();
103
104        let mut record = Self {
105            signing_public_key: signing_keypair.public_key().to_vec(),
106            encryption_public_key,
107            addresses,
108            alias,
109            ttl,
110            registered_at: now,
111            expires_at: now + ttl as u64,
112            owner_id,
113            signature: vec![],
114            metadata: HashMap::new(),
115        };
116
117        // Sign the record
118        record.sign(signing_keypair)?;
119        Ok(record)
120    }
121
122    /// Sign the record with ML-DSA
123    fn sign(&mut self, keypair: &MlDsaKeyPair) -> Result<(), DarkResolverError> {
124        let mut rng = rand::thread_rng();
125        let message = self.to_signable_bytes()?;
126        self.signature = keypair
127            .sign(&message, &mut rng)
128            .map_err(|e| DarkResolverError::MlDsaError(e))?;
129        Ok(())
130    }
131
132    /// Verify the record's signature
133    pub fn verify_signature(&self) -> Result<(), DarkResolverError> {
134        let public_key = MlDsaPublicKey::from_bytes(&self.signing_public_key)
135            .map_err(|e| DarkResolverError::MlDsaError(e))?;
136        let message = self.to_signable_bytes()?;
137        public_key
138            .verify(&message, &self.signature)
139            .map_err(|e| DarkResolverError::MlDsaError(e))?;
140        Ok(())
141    }
142
143    /// Convert record to bytes for signing (excludes signature field)
144    fn to_signable_bytes(&self) -> Result<Vec<u8>, DarkResolverError> {
145        let mut hasher = Hasher::new();
146        hasher.update(&self.signing_public_key);
147        hasher.update(&self.encryption_public_key);
148        for addr in &self.addresses {
149            hasher.update(
150                &bincode::serialize(addr)
151                    .map_err(|e| DarkResolverError::CryptoError(e.to_string()))?,
152            );
153        }
154        if let Some(alias) = &self.alias {
155            hasher.update(alias.as_bytes());
156        }
157        hasher.update(&self.ttl.to_le_bytes());
158        hasher.update(&self.registered_at.to_le_bytes());
159        hasher.update(&self.expires_at.to_le_bytes());
160        hasher.update(
161            &bincode::serialize(&self.owner_id)
162                .map_err(|e| DarkResolverError::CryptoError(e.to_string()))?,
163        );
164        Ok(hasher.finalize().as_bytes().to_vec())
165    }
166
167    /// Check if the record has expired
168    pub fn is_expired(&self) -> bool {
169        let now = SystemTime::now()
170            .duration_since(UNIX_EPOCH)
171            .unwrap()
172            .as_secs();
173        now > self.expires_at
174    }
175}
176
177/// Dark domain resolver that manages .dark domain registrations and lookups
178pub struct DarkResolver {
179    /// Thread-safe storage for domain records
180    domains: Arc<RwLock<HashMap<String, DarkDomainRecord>>>,
181    /// Address book for human-readable names
182    address_book: Arc<RwLock<HashMap<String, AddressBookEntry>>>,
183    /// Reverse lookup: dark address -> domain name
184    reverse_lookup: Arc<RwLock<HashMap<String, String>>>,
185    /// DHT client for distributed storage (placeholder)
186    dht_client: Option<Arc<dyn DhtClient>>,
187}
188
189/// Trait for DHT client operations
190pub trait DhtClient: Send + Sync {
191    /// Store a value in the DHT
192    fn put(&self, key: &[u8], value: &[u8]) -> Result<(), DarkResolverError>;
193    /// Retrieve a value from the DHT
194    fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, DarkResolverError>;
195    /// Remove a value from the DHT
196    fn remove(&self, key: &[u8]) -> Result<(), DarkResolverError>;
197}
198
199impl Default for DarkResolver {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205impl DarkResolver {
206    /// Creates a new dark domain resolver
207    pub fn new() -> Self {
208        Self {
209            domains: Arc::new(RwLock::new(HashMap::new())),
210            address_book: Arc::new(RwLock::new(HashMap::new())),
211            reverse_lookup: Arc::new(RwLock::new(HashMap::new())),
212            dht_client: None,
213        }
214    }
215
216    /// Create a resolver with DHT client
217    pub fn with_dht(dht_client: Arc<dyn DhtClient>) -> Self {
218        Self {
219            domains: Arc::new(RwLock::new(HashMap::new())),
220            address_book: Arc::new(RwLock::new(HashMap::new())),
221            reverse_lookup: Arc::new(RwLock::new(HashMap::new())),
222            dht_client: Some(dht_client),
223        }
224    }
225
226    /// Generate a .dark address from ML-DSA public key
227    pub fn generate_dark_address(
228        public_key: &[u8],
229        custom_name: Option<&str>,
230    ) -> Result<DarkAddress, DarkResolverError> {
231        // Hash the public key with BLAKE3
232        let mut hasher = Hasher::new();
233        hasher.update(b"dark_address_v1");
234        hasher.update(public_key);
235        let hash = hasher.finalize();
236
237        // Take first 20 bytes and encode as base58
238        let address_bytes = &hash.as_bytes()[..20];
239        let address = bs58::encode(address_bytes).into_string();
240
241        // Generate domain name
242        let domain = if let Some(name) = custom_name {
243            if !Self::is_valid_custom_name(name) {
244                return Err(DarkResolverError::InvalidDomain);
245            }
246            format!("{}.dark", name)
247        } else {
248            // Use first 8 chars of address as subdomain
249            format!("{}.dark", &address[..8].to_lowercase())
250        };
251
252        Ok(DarkAddress { address, domain })
253    }
254
255    /// Validate custom name for .dark domain
256    fn is_valid_custom_name(name: &str) -> bool {
257        // Must be 3-63 chars, alphanumeric + hyphens, not start/end with hyphen
258        name.len() >= 3
259            && name.len() <= 63
260            && name.chars().all(|c| c.is_alphanumeric() || c == '-')
261            && !name.starts_with('-')
262            && !name.ends_with('-')
263    }
264
265    /// Register a new .dark domain with quantum-resistant signatures
266    pub fn register_domain<R: CryptoRng + RngCore>(
267        &self,
268        custom_name: Option<&str>,
269        addresses: Vec<NetworkAddress>,
270        alias: Option<String>,
271        ttl: u32,
272        owner_id: PeerId,
273        rng: &mut R,
274    ) -> Result<DarkAddress, DarkResolverError> {
275        // Generate ML-DSA keypair for signing
276        let signing_keypair =
277            MlDsaKeyPair::generate(rng).map_err(|e| DarkResolverError::MlDsaError(e))?;
278
279        // Generate ML-KEM keypair for encryption
280        let (kem_public, _kem_secret) =
281            MlKem768::keygen().map_err(|e| DarkResolverError::CryptoError(e.to_string()))?;
282
283        // Generate dark address from signing public key
284        let dark_address = Self::generate_dark_address(signing_keypair.public_key(), custom_name)?;
285
286        // Validate domain doesn't exist
287        if !Self::is_valid_dark_domain(&dark_address.domain) {
288            return Err(DarkResolverError::InvalidDomain);
289        }
290
291        // Create domain record
292        let record = DarkDomainRecord::new(
293            &signing_keypair,
294            kem_public.as_bytes().to_vec(),
295            addresses,
296            alias,
297            ttl,
298            owner_id,
299        )?;
300
301        // Store locally
302        {
303            let mut domains = self
304                .domains
305                .write()
306                .map_err(|_| DarkResolverError::StorageError)?;
307
308            if domains.contains_key(&dark_address.domain) {
309                return Err(DarkResolverError::DomainExists);
310            }
311
312            domains.insert(dark_address.domain.clone(), record.clone());
313        }
314
315        // Update reverse lookup
316        {
317            let mut reverse = self
318                .reverse_lookup
319                .write()
320                .map_err(|_| DarkResolverError::StorageError)?;
321            reverse.insert(dark_address.address.clone(), dark_address.domain.clone());
322        }
323
324        // Store in DHT if available
325        if let Some(dht) = &self.dht_client {
326            let key = Self::domain_to_dht_key(&dark_address.domain);
327            let value = bincode::serialize(&record)
328                .map_err(|e| DarkResolverError::DhtError(e.to_string()))?;
329            dht.put(&key, &value)?;
330        }
331
332        Ok(dark_address)
333    }
334
335    /// Convert domain to DHT key
336    fn domain_to_dht_key(domain: &str) -> Vec<u8> {
337        let mut hasher = Hasher::new();
338        hasher.update(b"dark_domain:");
339        hasher.update(domain.as_bytes());
340        hasher.finalize().as_bytes().to_vec()
341    }
342
343    /// Look up a .dark domain and return its record
344    pub fn lookup_domain(&self, domain: &str) -> Result<DarkDomainRecord, DarkResolverError> {
345        // Validate domain name
346        if !Self::is_valid_dark_domain(domain) {
347            return Err(DarkResolverError::InvalidDomain);
348        }
349
350        // Try local storage first
351        {
352            let domains = self
353                .domains
354                .read()
355                .map_err(|_| DarkResolverError::StorageError)?;
356
357            if let Some(record) = domains.get(domain) {
358                // Check expiration
359                if record.is_expired() {
360                    return Err(DarkResolverError::DomainExpired);
361                }
362                // Verify signature
363                record.verify_signature()?;
364                return Ok(record.clone());
365            }
366        }
367
368        // Try DHT if not found locally
369        if let Some(dht) = &self.dht_client {
370            let key = Self::domain_to_dht_key(domain);
371            if let Some(value) = dht.get(&key)? {
372                let record: DarkDomainRecord = bincode::deserialize(&value)
373                    .map_err(|e| DarkResolverError::DhtError(e.to_string()))?;
374
375                // Verify and cache
376                if record.is_expired() {
377                    return Err(DarkResolverError::DomainExpired);
378                }
379                record.verify_signature()?;
380
381                // Cache locally
382                let mut domains = self
383                    .domains
384                    .write()
385                    .map_err(|_| DarkResolverError::StorageError)?;
386                domains.insert(domain.to_string(), record.clone());
387
388                return Ok(record);
389            }
390        }
391
392        Err(DarkResolverError::DomainNotFound)
393    }
394
395    /// Resolve a .dark domain to network addresses
396    pub fn resolve_addresses(
397        &self,
398        domain: &str,
399    ) -> Result<Vec<NetworkAddress>, DarkResolverError> {
400        let record = self.lookup_domain(domain)?;
401        Ok(record.addresses)
402    }
403
404    /// Add entry to address book
405    pub fn add_to_address_book(
406        &self,
407        name: String,
408        dark_address: DarkAddress,
409        notes: Option<String>,
410    ) -> Result<(), DarkResolverError> {
411        let entry = AddressBookEntry {
412            name: name.clone(),
413            dark_address,
414            notes,
415            added_at: SystemTime::now()
416                .duration_since(UNIX_EPOCH)
417                .unwrap()
418                .as_secs(),
419        };
420
421        let mut book = self
422            .address_book
423            .write()
424            .map_err(|_| DarkResolverError::StorageError)?;
425        book.insert(name, entry);
426        Ok(())
427    }
428
429    /// Look up address book entry by name
430    pub fn lookup_address_book(&self, name: &str) -> Result<AddressBookEntry, DarkResolverError> {
431        let book = self
432            .address_book
433            .read()
434            .map_err(|_| DarkResolverError::StorageError)?;
435        book.get(name)
436            .cloned()
437            .ok_or(DarkResolverError::DomainNotFound)
438    }
439
440    /// List all address book entries
441    pub fn list_address_book(&self) -> Result<Vec<AddressBookEntry>, DarkResolverError> {
442        let book = self
443            .address_book
444            .read()
445            .map_err(|_| DarkResolverError::StorageError)?;
446        Ok(book.values().cloned().collect())
447    }
448
449    /// Update domain record (requires signature from owner)
450    pub fn update_domain(
451        &self,
452        domain: &str,
453        record: DarkDomainRecord,
454    ) -> Result<(), DarkResolverError> {
455        // Verify the new record's signature
456        record.verify_signature()?;
457
458        // Get existing record to verify ownership
459        let existing = self.lookup_domain(domain)?;
460
461        // Verify same owner (by comparing signing public keys)
462        if existing.signing_public_key != record.signing_public_key {
463            return Err(DarkResolverError::InvalidSignature);
464        }
465
466        // Update local storage
467        {
468            let mut domains = self
469                .domains
470                .write()
471                .map_err(|_| DarkResolverError::StorageError)?;
472            domains.insert(domain.to_string(), record.clone());
473        }
474
475        // Update DHT
476        if let Some(dht) = &self.dht_client {
477            let key = Self::domain_to_dht_key(domain);
478            let value = bincode::serialize(&record)
479                .map_err(|e| DarkResolverError::DhtError(e.to_string()))?;
480            dht.put(&key, &value)?;
481        }
482
483        Ok(())
484    }
485
486    /// Remove expired domains
487    pub fn cleanup_expired(&self) -> Result<usize, DarkResolverError> {
488        let mut count = 0;
489        let mut to_remove = Vec::new();
490
491        // Find expired domains
492        {
493            let domains = self
494                .domains
495                .read()
496                .map_err(|_| DarkResolverError::StorageError)?;
497            for (domain, record) in domains.iter() {
498                if record.is_expired() {
499                    to_remove.push(domain.clone());
500                }
501            }
502        }
503
504        // Remove expired domains
505        {
506            let mut domains = self
507                .domains
508                .write()
509                .map_err(|_| DarkResolverError::StorageError)?;
510            let mut reverse = self
511                .reverse_lookup
512                .write()
513                .map_err(|_| DarkResolverError::StorageError)?;
514
515            for domain in to_remove {
516                if let Some(record) = domains.remove(&domain) {
517                    count += 1;
518                    // Remove from reverse lookup
519                    let addr = Self::generate_dark_address(&record.signing_public_key, None)?;
520                    reverse.remove(&addr.address);
521
522                    // Remove from DHT
523                    if let Some(dht) = &self.dht_client {
524                        let key = Self::domain_to_dht_key(&domain);
525                        let _ = dht.remove(&key);
526                    }
527                }
528            }
529        }
530
531        Ok(count)
532    }
533
534    /// Validates a .dark domain name format
535    fn is_valid_dark_domain(domain: &str) -> bool {
536        // Must end with .dark
537        if !domain.ends_with(".dark") {
538            return false;
539        }
540
541        // Extract subdomain
542        let subdomain = &domain[..domain.len() - 5];
543
544        // Validation rules:
545        // - Subdomain length between 3 and 63 chars
546        // - Alphanumeric + hyphens
547        // - Cannot start or end with hyphen
548        // - No consecutive hyphens
549        subdomain.len() >= 3
550            && subdomain.len() <= 63
551            && !subdomain.starts_with('-')
552            && !subdomain.ends_with('-')
553            && !subdomain.contains("--")
554            && subdomain.chars().all(|c| c.is_alphanumeric() || c == '-')
555    }
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561    use rand::thread_rng;
562
563    // Mock DHT client for testing
564    struct MockDhtClient {
565        storage: Arc<RwLock<HashMap<Vec<u8>, Vec<u8>>>>,
566    }
567
568    impl MockDhtClient {
569        fn new() -> Self {
570            Self {
571                storage: Arc::new(RwLock::new(HashMap::new())),
572            }
573        }
574    }
575
576    impl DhtClient for MockDhtClient {
577        fn put(&self, key: &[u8], value: &[u8]) -> Result<(), DarkResolverError> {
578            let mut storage = self
579                .storage
580                .write()
581                .map_err(|_| DarkResolverError::StorageError)?;
582            storage.insert(key.to_vec(), value.to_vec());
583            Ok(())
584        }
585
586        fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, DarkResolverError> {
587            let storage = self
588                .storage
589                .read()
590                .map_err(|_| DarkResolverError::StorageError)?;
591            Ok(storage.get(key).cloned())
592        }
593
594        fn remove(&self, key: &[u8]) -> Result<(), DarkResolverError> {
595            let mut storage = self
596                .storage
597                .write()
598                .map_err(|_| DarkResolverError::StorageError)?;
599            storage.remove(key);
600            Ok(())
601        }
602    }
603
604    #[test]
605    fn test_valid_dark_domains() {
606        assert!(DarkResolver::is_valid_dark_domain("test.dark"));
607        assert!(DarkResolver::is_valid_dark_domain("my-domain.dark"));
608        assert!(DarkResolver::is_valid_dark_domain("node123.dark"));
609        assert!(DarkResolver::is_valid_dark_domain("a2b.dark"));
610
611        // Invalid cases
612        assert!(!DarkResolver::is_valid_dark_domain("invalid"));
613        assert!(!DarkResolver::is_valid_dark_domain(".dark"));
614        assert!(!DarkResolver::is_valid_dark_domain("test.darknet"));
615        assert!(!DarkResolver::is_valid_dark_domain("-test.dark"));
616        assert!(!DarkResolver::is_valid_dark_domain("test-.dark"));
617        assert!(!DarkResolver::is_valid_dark_domain("test--domain.dark"));
618        assert!(!DarkResolver::is_valid_dark_domain("ab.dark")); // too short
619        assert!(!DarkResolver::is_valid_dark_domain(&format!(
620            "{}.dark",
621            "a".repeat(64)
622        ))); // too long
623    }
624
625    #[test]
626    fn test_dark_address_generation() {
627        let public_key = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
628
629        // Generate without custom name
630        let addr1 = DarkResolver::generate_dark_address(&public_key, None).unwrap();
631        assert!(addr1.address.len() > 0);
632        assert!(addr1.domain.ends_with(".dark"));
633
634        // Generate with custom name
635        let addr2 = DarkResolver::generate_dark_address(&public_key, Some("mynode")).unwrap();
636        assert_eq!(addr2.domain, "mynode.dark");
637
638        // Same public key should generate same address
639        let addr3 = DarkResolver::generate_dark_address(&public_key, None).unwrap();
640        assert_eq!(addr1.address, addr3.address);
641    }
642
643    #[test]
644    fn test_domain_registration_and_resolution() {
645        let mut rng = thread_rng();
646        let resolver = DarkResolver::with_dht(Arc::new(MockDhtClient::new()));
647        let owner_id = PeerId::random();
648        let addresses = vec![
649            NetworkAddress::new([1, 2, 3, 4], 8080),
650            NetworkAddress::new([5, 6, 7, 8], 9090),
651        ];
652
653        // Register domain
654        let dark_addr = resolver
655            .register_domain(
656                Some("testnode"),
657                addresses.clone(),
658                Some("Test Node".to_string()),
659                3600, // 1 hour TTL
660                owner_id.clone(),
661                &mut rng,
662            )
663            .unwrap();
664
665        assert_eq!(dark_addr.domain, "testnode.dark");
666
667        // Lookup domain
668        let record = resolver.lookup_domain(&dark_addr.domain).unwrap();
669        assert_eq!(record.addresses, addresses);
670        assert_eq!(record.alias, Some("Test Node".to_string()));
671        assert_eq!(record.owner_id, owner_id);
672        assert_eq!(record.ttl, 3600);
673
674        // Resolve addresses
675        let resolved = resolver.resolve_addresses(&dark_addr.domain).unwrap();
676        assert_eq!(resolved, addresses);
677
678        // Try to register same custom name (should fail)
679        let result = resolver.register_domain(
680            Some("testnode"),
681            vec![],
682            None,
683            3600,
684            PeerId::random(),
685            &mut rng,
686        );
687        assert!(matches!(result, Err(DarkResolverError::DomainExists)));
688    }
689
690    #[test]
691    fn test_address_book() {
692        let resolver = DarkResolver::new();
693        let dark_addr = DarkAddress {
694            address: "3HGvnkH2VwR3cD8r7shs7V".to_string(),
695            domain: "mynode.dark".to_string(),
696        };
697
698        // Add to address book
699        resolver
700            .add_to_address_book(
701                "Alice's Node".to_string(),
702                dark_addr.clone(),
703                Some("Primary node".to_string()),
704            )
705            .unwrap();
706
707        // Lookup by name
708        let entry = resolver.lookup_address_book("Alice's Node").unwrap();
709        assert_eq!(entry.dark_address, dark_addr);
710        assert_eq!(entry.notes, Some("Primary node".to_string()));
711
712        // List all entries
713        let entries = resolver.list_address_book().unwrap();
714        assert_eq!(entries.len(), 1);
715    }
716
717    #[test]
718    fn test_domain_expiration() {
719        let mut rng = thread_rng();
720        let resolver = DarkResolver::new();
721        let owner_id = PeerId::random();
722
723        // Create an already expired record
724        let signing_keypair = MlDsaKeyPair::generate(&mut rng).unwrap();
725        let (kem_public, _) = MlKem768::keygen().unwrap();
726
727        let mut record = DarkDomainRecord {
728            signing_public_key: signing_keypair.public_key().to_vec(),
729            encryption_public_key: kem_public.as_bytes().to_vec(),
730            addresses: vec![NetworkAddress::new([1, 2, 3, 4], 8080)],
731            alias: None,
732            ttl: 60,
733            registered_at: 1000,
734            expires_at: 1060, // Already expired
735            owner_id,
736            signature: vec![],
737            metadata: HashMap::new(),
738        };
739
740        // Sign the record
741        record.sign(&signing_keypair).unwrap();
742
743        // Manually insert expired record
744        {
745            let mut domains = resolver.domains.write().unwrap();
746            domains.insert("expired.dark".to_string(), record);
747        }
748
749        // Try to lookup - should fail with DomainExpired
750        let result = resolver.lookup_domain("expired.dark");
751        assert!(matches!(result, Err(DarkResolverError::DomainExpired)));
752
753        // Cleanup should remove it
754        let removed = resolver.cleanup_expired().unwrap();
755        assert_eq!(removed, 1);
756
757        // Should now be not found
758        let result = resolver.lookup_domain("expired.dark");
759        assert!(matches!(result, Err(DarkResolverError::DomainNotFound)));
760    }
761
762    #[test]
763    fn test_signature_verification() {
764        let mut rng = thread_rng();
765        let signing_keypair = MlDsaKeyPair::generate(&mut rng).unwrap();
766        let (kem_public, _) = MlKem768::keygen().unwrap();
767        let owner_id = PeerId::random();
768
769        // Create and sign a record
770        let record = DarkDomainRecord::new(
771            &signing_keypair,
772            kem_public.as_bytes().to_vec(),
773            vec![NetworkAddress::new([1, 2, 3, 4], 8080)],
774            None,
775            3600,
776            owner_id,
777        )
778        .unwrap();
779
780        // Verify signature should succeed
781        assert!(record.verify_signature().is_ok());
782
783        // Tamper with the record
784        let mut tampered = record.clone();
785        tampered.ttl = 7200; // Change TTL
786
787        // Verification should fail
788        assert!(tampered.verify_signature().is_err());
789    }
790}