use blake3::Hasher;
use bs58;
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
use qudag_crypto::ml_dsa::{MlDsaError, MlDsaKeyPair, MlDsaPublicKey};
use qudag_crypto::ml_kem::MlKem768;
use crate::types::NetworkAddress;
use crate::types::PeerId;
#[derive(Error, Debug)]
pub enum DarkResolverError {
#[error("Domain name already registered")]
DomainExists,
#[error("Domain not found")]
DomainNotFound,
#[error("Invalid domain name format")]
InvalidDomain,
#[error("Cryptographic operation failed: {0}")]
CryptoError(String),
#[error("Domain record access error")]
StorageError,
#[error("Domain has expired")]
DomainExpired,
#[error("Invalid signature")]
InvalidSignature,
#[error("Address generation failed: {0}")]
AddressGenerationError(String),
#[error("DHT operation failed: {0}")]
DhtError(String),
#[error("ML-DSA error: {0}")]
MlDsaError(#[from] MlDsaError),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DarkDomainRecord {
pub signing_public_key: Vec<u8>,
pub encryption_public_key: Vec<u8>,
pub addresses: Vec<NetworkAddress>,
pub alias: Option<String>,
pub ttl: u32,
pub registered_at: u64,
pub expires_at: u64,
pub owner_id: PeerId,
pub signature: Vec<u8>,
pub metadata: HashMap<String, String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DarkAddress {
pub address: String,
pub domain: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AddressBookEntry {
pub name: String,
pub dark_address: DarkAddress,
pub notes: Option<String>,
pub added_at: u64,
}
impl DarkDomainRecord {
pub fn new(
signing_keypair: &MlDsaKeyPair,
encryption_public_key: Vec<u8>,
addresses: Vec<NetworkAddress>,
alias: Option<String>,
ttl: u32,
owner_id: PeerId,
) -> Result<Self, DarkResolverError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut record = Self {
signing_public_key: signing_keypair.public_key().to_vec(),
encryption_public_key,
addresses,
alias,
ttl,
registered_at: now,
expires_at: now + ttl as u64,
owner_id,
signature: vec![],
metadata: HashMap::new(),
};
record.sign(signing_keypair)?;
Ok(record)
}
fn sign(&mut self, keypair: &MlDsaKeyPair) -> Result<(), DarkResolverError> {
let mut rng = rand::thread_rng();
let message = self.to_signable_bytes()?;
self.signature = keypair
.sign(&message, &mut rng)
.map_err(|e| DarkResolverError::MlDsaError(e))?;
Ok(())
}
pub fn verify_signature(&self) -> Result<(), DarkResolverError> {
let public_key = MlDsaPublicKey::from_bytes(&self.signing_public_key)
.map_err(|e| DarkResolverError::MlDsaError(e))?;
let message = self.to_signable_bytes()?;
public_key
.verify(&message, &self.signature)
.map_err(|e| DarkResolverError::MlDsaError(e))?;
Ok(())
}
fn to_signable_bytes(&self) -> Result<Vec<u8>, DarkResolverError> {
let mut hasher = Hasher::new();
hasher.update(&self.signing_public_key);
hasher.update(&self.encryption_public_key);
for addr in &self.addresses {
hasher.update(
&bincode::serialize(addr)
.map_err(|e| DarkResolverError::CryptoError(e.to_string()))?,
);
}
if let Some(alias) = &self.alias {
hasher.update(alias.as_bytes());
}
hasher.update(&self.ttl.to_le_bytes());
hasher.update(&self.registered_at.to_le_bytes());
hasher.update(&self.expires_at.to_le_bytes());
hasher.update(
&bincode::serialize(&self.owner_id)
.map_err(|e| DarkResolverError::CryptoError(e.to_string()))?,
);
Ok(hasher.finalize().as_bytes().to_vec())
}
pub fn is_expired(&self) -> bool {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
now > self.expires_at
}
}
pub struct DarkResolver {
domains: Arc<RwLock<HashMap<String, DarkDomainRecord>>>,
address_book: Arc<RwLock<HashMap<String, AddressBookEntry>>>,
reverse_lookup: Arc<RwLock<HashMap<String, String>>>,
dht_client: Option<Arc<dyn DhtClient>>,
}
pub trait DhtClient: Send + Sync {
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), DarkResolverError>;
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, DarkResolverError>;
fn remove(&self, key: &[u8]) -> Result<(), DarkResolverError>;
}
impl Default for DarkResolver {
fn default() -> Self {
Self::new()
}
}
impl DarkResolver {
pub fn new() -> Self {
Self {
domains: Arc::new(RwLock::new(HashMap::new())),
address_book: Arc::new(RwLock::new(HashMap::new())),
reverse_lookup: Arc::new(RwLock::new(HashMap::new())),
dht_client: None,
}
}
pub fn with_dht(dht_client: Arc<dyn DhtClient>) -> Self {
Self {
domains: Arc::new(RwLock::new(HashMap::new())),
address_book: Arc::new(RwLock::new(HashMap::new())),
reverse_lookup: Arc::new(RwLock::new(HashMap::new())),
dht_client: Some(dht_client),
}
}
pub fn generate_dark_address(
public_key: &[u8],
custom_name: Option<&str>,
) -> Result<DarkAddress, DarkResolverError> {
let mut hasher = Hasher::new();
hasher.update(b"dark_address_v1");
hasher.update(public_key);
let hash = hasher.finalize();
let address_bytes = &hash.as_bytes()[..20];
let address = bs58::encode(address_bytes).into_string();
let domain = if let Some(name) = custom_name {
if !Self::is_valid_custom_name(name) {
return Err(DarkResolverError::InvalidDomain);
}
format!("{}.dark", name)
} else {
format!("{}.dark", &address[..8].to_lowercase())
};
Ok(DarkAddress { address, domain })
}
fn is_valid_custom_name(name: &str) -> bool {
name.len() >= 3
&& name.len() <= 63
&& name.chars().all(|c| c.is_alphanumeric() || c == '-')
&& !name.starts_with('-')
&& !name.ends_with('-')
}
pub fn register_domain<R: CryptoRng + RngCore>(
&self,
custom_name: Option<&str>,
addresses: Vec<NetworkAddress>,
alias: Option<String>,
ttl: u32,
owner_id: PeerId,
rng: &mut R,
) -> Result<DarkAddress, DarkResolverError> {
let signing_keypair =
MlDsaKeyPair::generate(rng).map_err(|e| DarkResolverError::MlDsaError(e))?;
let (kem_public, _kem_secret) =
MlKem768::keygen().map_err(|e| DarkResolverError::CryptoError(e.to_string()))?;
let dark_address = Self::generate_dark_address(signing_keypair.public_key(), custom_name)?;
if !Self::is_valid_dark_domain(&dark_address.domain) {
return Err(DarkResolverError::InvalidDomain);
}
let record = DarkDomainRecord::new(
&signing_keypair,
kem_public.as_bytes().to_vec(),
addresses,
alias,
ttl,
owner_id,
)?;
{
let mut domains = self
.domains
.write()
.map_err(|_| DarkResolverError::StorageError)?;
if domains.contains_key(&dark_address.domain) {
return Err(DarkResolverError::DomainExists);
}
domains.insert(dark_address.domain.clone(), record.clone());
}
{
let mut reverse = self
.reverse_lookup
.write()
.map_err(|_| DarkResolverError::StorageError)?;
reverse.insert(dark_address.address.clone(), dark_address.domain.clone());
}
if let Some(dht) = &self.dht_client {
let key = Self::domain_to_dht_key(&dark_address.domain);
let value = bincode::serialize(&record)
.map_err(|e| DarkResolverError::DhtError(e.to_string()))?;
dht.put(&key, &value)?;
}
Ok(dark_address)
}
fn domain_to_dht_key(domain: &str) -> Vec<u8> {
let mut hasher = Hasher::new();
hasher.update(b"dark_domain:");
hasher.update(domain.as_bytes());
hasher.finalize().as_bytes().to_vec()
}
pub fn lookup_domain(&self, domain: &str) -> Result<DarkDomainRecord, DarkResolverError> {
if !Self::is_valid_dark_domain(domain) {
return Err(DarkResolverError::InvalidDomain);
}
{
let domains = self
.domains
.read()
.map_err(|_| DarkResolverError::StorageError)?;
if let Some(record) = domains.get(domain) {
if record.is_expired() {
return Err(DarkResolverError::DomainExpired);
}
record.verify_signature()?;
return Ok(record.clone());
}
}
if let Some(dht) = &self.dht_client {
let key = Self::domain_to_dht_key(domain);
if let Some(value) = dht.get(&key)? {
let record: DarkDomainRecord = bincode::deserialize(&value)
.map_err(|e| DarkResolverError::DhtError(e.to_string()))?;
if record.is_expired() {
return Err(DarkResolverError::DomainExpired);
}
record.verify_signature()?;
let mut domains = self
.domains
.write()
.map_err(|_| DarkResolverError::StorageError)?;
domains.insert(domain.to_string(), record.clone());
return Ok(record);
}
}
Err(DarkResolverError::DomainNotFound)
}
pub fn resolve_addresses(
&self,
domain: &str,
) -> Result<Vec<NetworkAddress>, DarkResolverError> {
let record = self.lookup_domain(domain)?;
Ok(record.addresses)
}
pub fn add_to_address_book(
&self,
name: String,
dark_address: DarkAddress,
notes: Option<String>,
) -> Result<(), DarkResolverError> {
let entry = AddressBookEntry {
name: name.clone(),
dark_address,
notes,
added_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
};
let mut book = self
.address_book
.write()
.map_err(|_| DarkResolverError::StorageError)?;
book.insert(name, entry);
Ok(())
}
pub fn lookup_address_book(&self, name: &str) -> Result<AddressBookEntry, DarkResolverError> {
let book = self
.address_book
.read()
.map_err(|_| DarkResolverError::StorageError)?;
book.get(name)
.cloned()
.ok_or(DarkResolverError::DomainNotFound)
}
pub fn list_address_book(&self) -> Result<Vec<AddressBookEntry>, DarkResolverError> {
let book = self
.address_book
.read()
.map_err(|_| DarkResolverError::StorageError)?;
Ok(book.values().cloned().collect())
}
pub fn update_domain(
&self,
domain: &str,
record: DarkDomainRecord,
) -> Result<(), DarkResolverError> {
record.verify_signature()?;
let existing = self.lookup_domain(domain)?;
if existing.signing_public_key != record.signing_public_key {
return Err(DarkResolverError::InvalidSignature);
}
{
let mut domains = self
.domains
.write()
.map_err(|_| DarkResolverError::StorageError)?;
domains.insert(domain.to_string(), record.clone());
}
if let Some(dht) = &self.dht_client {
let key = Self::domain_to_dht_key(domain);
let value = bincode::serialize(&record)
.map_err(|e| DarkResolverError::DhtError(e.to_string()))?;
dht.put(&key, &value)?;
}
Ok(())
}
pub fn cleanup_expired(&self) -> Result<usize, DarkResolverError> {
let mut count = 0;
let mut to_remove = Vec::new();
{
let domains = self
.domains
.read()
.map_err(|_| DarkResolverError::StorageError)?;
for (domain, record) in domains.iter() {
if record.is_expired() {
to_remove.push(domain.clone());
}
}
}
{
let mut domains = self
.domains
.write()
.map_err(|_| DarkResolverError::StorageError)?;
let mut reverse = self
.reverse_lookup
.write()
.map_err(|_| DarkResolverError::StorageError)?;
for domain in to_remove {
if let Some(record) = domains.remove(&domain) {
count += 1;
let addr = Self::generate_dark_address(&record.signing_public_key, None)?;
reverse.remove(&addr.address);
if let Some(dht) = &self.dht_client {
let key = Self::domain_to_dht_key(&domain);
let _ = dht.remove(&key);
}
}
}
}
Ok(count)
}
fn is_valid_dark_domain(domain: &str) -> bool {
if !domain.ends_with(".dark") {
return false;
}
let subdomain = &domain[..domain.len() - 5];
subdomain.len() >= 3
&& subdomain.len() <= 63
&& !subdomain.starts_with('-')
&& !subdomain.ends_with('-')
&& !subdomain.contains("--")
&& subdomain.chars().all(|c| c.is_alphanumeric() || c == '-')
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::thread_rng;
struct MockDhtClient {
storage: Arc<RwLock<HashMap<Vec<u8>, Vec<u8>>>>,
}
impl MockDhtClient {
fn new() -> Self {
Self {
storage: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl DhtClient for MockDhtClient {
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), DarkResolverError> {
let mut storage = self
.storage
.write()
.map_err(|_| DarkResolverError::StorageError)?;
storage.insert(key.to_vec(), value.to_vec());
Ok(())
}
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, DarkResolverError> {
let storage = self
.storage
.read()
.map_err(|_| DarkResolverError::StorageError)?;
Ok(storage.get(key).cloned())
}
fn remove(&self, key: &[u8]) -> Result<(), DarkResolverError> {
let mut storage = self
.storage
.write()
.map_err(|_| DarkResolverError::StorageError)?;
storage.remove(key);
Ok(())
}
}
#[test]
fn test_valid_dark_domains() {
assert!(DarkResolver::is_valid_dark_domain("test.dark"));
assert!(DarkResolver::is_valid_dark_domain("my-domain.dark"));
assert!(DarkResolver::is_valid_dark_domain("node123.dark"));
assert!(DarkResolver::is_valid_dark_domain("a2b.dark"));
assert!(!DarkResolver::is_valid_dark_domain("invalid"));
assert!(!DarkResolver::is_valid_dark_domain(".dark"));
assert!(!DarkResolver::is_valid_dark_domain("test.darknet"));
assert!(!DarkResolver::is_valid_dark_domain("-test.dark"));
assert!(!DarkResolver::is_valid_dark_domain("test-.dark"));
assert!(!DarkResolver::is_valid_dark_domain("test--domain.dark"));
assert!(!DarkResolver::is_valid_dark_domain("ab.dark")); assert!(!DarkResolver::is_valid_dark_domain(&format!(
"{}.dark",
"a".repeat(64)
))); }
#[test]
fn test_dark_address_generation() {
let public_key = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let addr1 = DarkResolver::generate_dark_address(&public_key, None).unwrap();
assert!(addr1.address.len() > 0);
assert!(addr1.domain.ends_with(".dark"));
let addr2 = DarkResolver::generate_dark_address(&public_key, Some("mynode")).unwrap();
assert_eq!(addr2.domain, "mynode.dark");
let addr3 = DarkResolver::generate_dark_address(&public_key, None).unwrap();
assert_eq!(addr1.address, addr3.address);
}
#[test]
fn test_domain_registration_and_resolution() {
let mut rng = thread_rng();
let resolver = DarkResolver::with_dht(Arc::new(MockDhtClient::new()));
let owner_id = PeerId::random();
let addresses = vec![
NetworkAddress::new([1, 2, 3, 4], 8080),
NetworkAddress::new([5, 6, 7, 8], 9090),
];
let dark_addr = resolver
.register_domain(
Some("testnode"),
addresses.clone(),
Some("Test Node".to_string()),
3600, owner_id.clone(),
&mut rng,
)
.unwrap();
assert_eq!(dark_addr.domain, "testnode.dark");
let record = resolver.lookup_domain(&dark_addr.domain).unwrap();
assert_eq!(record.addresses, addresses);
assert_eq!(record.alias, Some("Test Node".to_string()));
assert_eq!(record.owner_id, owner_id);
assert_eq!(record.ttl, 3600);
let resolved = resolver.resolve_addresses(&dark_addr.domain).unwrap();
assert_eq!(resolved, addresses);
let result = resolver.register_domain(
Some("testnode"),
vec![],
None,
3600,
PeerId::random(),
&mut rng,
);
assert!(matches!(result, Err(DarkResolverError::DomainExists)));
}
#[test]
fn test_address_book() {
let resolver = DarkResolver::new();
let dark_addr = DarkAddress {
address: "3HGvnkH2VwR3cD8r7shs7V".to_string(),
domain: "mynode.dark".to_string(),
};
resolver
.add_to_address_book(
"Alice's Node".to_string(),
dark_addr.clone(),
Some("Primary node".to_string()),
)
.unwrap();
let entry = resolver.lookup_address_book("Alice's Node").unwrap();
assert_eq!(entry.dark_address, dark_addr);
assert_eq!(entry.notes, Some("Primary node".to_string()));
let entries = resolver.list_address_book().unwrap();
assert_eq!(entries.len(), 1);
}
#[test]
fn test_domain_expiration() {
let mut rng = thread_rng();
let resolver = DarkResolver::new();
let owner_id = PeerId::random();
let signing_keypair = MlDsaKeyPair::generate(&mut rng).unwrap();
let (kem_public, _) = MlKem768::keygen().unwrap();
let mut record = DarkDomainRecord {
signing_public_key: signing_keypair.public_key().to_vec(),
encryption_public_key: kem_public.as_bytes().to_vec(),
addresses: vec![NetworkAddress::new([1, 2, 3, 4], 8080)],
alias: None,
ttl: 60,
registered_at: 1000,
expires_at: 1060, owner_id,
signature: vec![],
metadata: HashMap::new(),
};
record.sign(&signing_keypair).unwrap();
{
let mut domains = resolver.domains.write().unwrap();
domains.insert("expired.dark".to_string(), record);
}
let result = resolver.lookup_domain("expired.dark");
assert!(matches!(result, Err(DarkResolverError::DomainExpired)));
let removed = resolver.cleanup_expired().unwrap();
assert_eq!(removed, 1);
let result = resolver.lookup_domain("expired.dark");
assert!(matches!(result, Err(DarkResolverError::DomainNotFound)));
}
#[test]
fn test_signature_verification() {
let mut rng = thread_rng();
let signing_keypair = MlDsaKeyPair::generate(&mut rng).unwrap();
let (kem_public, _) = MlKem768::keygen().unwrap();
let owner_id = PeerId::random();
let record = DarkDomainRecord::new(
&signing_keypair,
kem_public.as_bytes().to_vec(),
vec![NetworkAddress::new([1, 2, 3, 4], 8080)],
None,
3600,
owner_id,
)
.unwrap();
assert!(record.verify_signature().is_ok());
let mut tampered = record.clone();
tampered.ttl = 7200;
assert!(tampered.verify_signature().is_err());
}
}