saorsa_core/
security.rs

1// Copyright 2024 Saorsa Labs Limited
2//
3// This software is dual-licensed under:
4// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5// - Commercial License
6//
7// For AGPL-3.0 license, see LICENSE-AGPL-3.0
8// For commercial licensing, contact: saorsalabs@gmail.com
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under these licenses is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
14//! Security module
15//!
16//! This module provides cryptographic functionality and Sybil protection for the P2P network.
17//! It implements IPv6-based node ID generation and IP diversity enforcement to prevent
18//! large-scale Sybil attacks while maintaining network openness.
19
20use crate::PeerId;
21use crate::quantum_crypto::ant_quic_integration::{
22    MlDsaPublicKey, MlDsaSecretKey, MlDsaSignature, ml_dsa_sign, ml_dsa_verify,
23};
24use anyhow::{Result, anyhow};
25use serde::{Deserialize, Serialize};
26use sha2::{Digest, Sha256};
27use std::collections::HashMap;
28use std::net::Ipv6Addr;
29use std::time::{Duration, SystemTime, UNIX_EPOCH};
30
31use std::sync::Arc;
32
33/// IPv6-based node identity that binds node ID to actual network location
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct IPv6NodeID {
36    /// Derived node ID (SHA256 of ipv6_addr + public_key + salt)
37    pub node_id: Vec<u8>,
38    /// IPv6 address this node ID is bound to
39    pub ipv6_addr: Ipv6Addr,
40    /// ML-DSA public key for signatures
41    pub public_key: Vec<u8>,
42    /// Signature proving ownership of the IPv6 address and keys
43    pub signature: Vec<u8>,
44    /// Timestamp when this ID was generated (seconds since epoch)
45    pub timestamp_secs: u64,
46    /// Salt used in node ID generation (for freshness)
47    pub salt: Vec<u8>,
48}
49
50/// Configuration for IP diversity enforcement at multiple subnet levels
51#[derive(Debug, Clone)]
52pub struct IPDiversityConfig {
53    /// Maximum nodes per /64 subnet (default: 1)
54    pub max_nodes_per_64: usize,
55    /// Maximum nodes per /48 allocation (default: 3)
56    pub max_nodes_per_48: usize,
57    /// Maximum nodes per /32 region (default: 10)
58    pub max_nodes_per_32: usize,
59    /// Maximum nodes per AS number (default: 20)
60    pub max_nodes_per_asn: usize,
61    /// Enable GeoIP-based diversity checks
62    pub enable_geolocation_check: bool,
63    /// Minimum number of different countries required
64    pub min_geographic_diversity: usize,
65}
66
67/// Analysis of an IPv6 address for diversity enforcement
68#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
69pub struct IPAnalysis {
70    /// /64 subnet (host allocation)
71    pub subnet_64: Ipv6Addr,
72    /// /48 subnet (site allocation)
73    pub subnet_48: Ipv6Addr,
74    /// /32 subnet (ISP allocation)
75    pub subnet_32: Ipv6Addr,
76    /// Autonomous System Number (if available)
77    pub asn: Option<u32>,
78    /// Country code from GeoIP lookup
79    pub country: Option<String>,
80    /// Whether this is a known hosting/VPS provider
81    pub is_hosting_provider: bool,
82    /// Whether this is a known VPN provider
83    pub is_vpn_provider: bool,
84    /// Historical reputation score for this IP range
85    pub reputation_score: f64,
86}
87
88/// Node reputation tracking for security-aware routing
89#[derive(Debug, Clone)]
90pub struct NodeReputation {
91    /// Peer ID
92    pub peer_id: PeerId,
93    /// Fraction of queries answered successfully
94    pub response_rate: f64,
95    /// Average response time
96    pub response_time: Duration,
97    /// Consistency of provided data (0.0-1.0)
98    pub consistency_score: f64,
99    /// Estimated continuous uptime
100    pub uptime_estimate: Duration,
101    /// Accuracy of routing information provided
102    pub routing_accuracy: f64,
103    /// Last time this node was seen
104    pub last_seen: SystemTime,
105    /// Total number of interactions
106    pub interaction_count: u64,
107}
108
109impl Default for IPDiversityConfig {
110    fn default() -> Self {
111        Self {
112            max_nodes_per_64: 1,
113            max_nodes_per_48: 3,
114            max_nodes_per_32: 10,
115            max_nodes_per_asn: 20,
116            enable_geolocation_check: true,
117            min_geographic_diversity: 3,
118        }
119    }
120}
121
122impl IPv6NodeID {
123    /// Generate a new IPv6-based node ID
124    pub fn generate(
125        ipv6_addr: Ipv6Addr,
126        secret: &MlDsaSecretKey,
127        public: &MlDsaPublicKey,
128    ) -> Result<Self> {
129        let mut rng = rand::thread_rng();
130        let mut salt = vec![0u8; 16];
131        rand::RngCore::fill_bytes(&mut rng, &mut salt);
132
133        let timestamp = SystemTime::now();
134        let timestamp_secs = timestamp.duration_since(UNIX_EPOCH)?.as_secs();
135        let public_key = public.as_bytes().to_vec();
136
137        // Generate node ID: SHA256(ipv6_address || public_key || salt || timestamp)
138        let mut hasher = Sha256::new();
139        hasher.update(ipv6_addr.octets());
140        hasher.update(&public_key);
141        hasher.update(&salt);
142        hasher.update(timestamp_secs.to_le_bytes());
143        let node_id = hasher.finalize().to_vec();
144
145        // Create signature proving ownership
146        let mut message_to_sign = Vec::new();
147        message_to_sign.extend_from_slice(&ipv6_addr.octets());
148        message_to_sign.extend_from_slice(&public_key);
149        message_to_sign.extend_from_slice(&salt);
150        message_to_sign.extend_from_slice(&timestamp_secs.to_le_bytes());
151
152        let sig = ml_dsa_sign(secret, &message_to_sign)
153            .map_err(|e| anyhow!("ML-DSA sign failed: {:?}", e))?;
154        let signature = sig.0.to_vec();
155
156        Ok(IPv6NodeID {
157            node_id,
158            ipv6_addr,
159            public_key,
160            signature,
161            timestamp_secs,
162            salt,
163        })
164    }
165
166    /// Verify that this node ID is valid and properly signed
167    pub fn verify(&self) -> Result<bool> {
168        // Reconstruct the node ID
169        let mut hasher = Sha256::new();
170        hasher.update(self.ipv6_addr.octets());
171        hasher.update(&self.public_key);
172        hasher.update(&self.salt);
173        hasher.update(self.timestamp_secs.to_le_bytes());
174        let expected_node_id = hasher.finalize();
175
176        // Verify node ID matches
177        if expected_node_id.as_slice() != self.node_id {
178            return Ok(false);
179        }
180
181        let public_key = MlDsaPublicKey::from_bytes(&self.public_key)
182            .map_err(|e| anyhow!("Invalid ML-DSA public key: {:?}", e))?;
183
184        // Convert Vec<u8> to fixed-size array for MlDsaSignature
185        if self.signature.len() != 3309 {
186            return Ok(false); // Invalid signature length
187        }
188        let mut sig_bytes = [0u8; 3309];
189        sig_bytes.copy_from_slice(&self.signature);
190        let signature = MlDsaSignature(Box::new(sig_bytes));
191
192        let mut message_to_verify = Vec::new();
193        message_to_verify.extend_from_slice(&self.ipv6_addr.octets());
194        message_to_verify.extend_from_slice(&self.public_key);
195        message_to_verify.extend_from_slice(&self.salt);
196        message_to_verify.extend_from_slice(&self.timestamp_secs.to_le_bytes());
197
198        let ok = ml_dsa_verify(&public_key, &message_to_verify, &signature)
199            .map_err(|e| anyhow!("ML-DSA verify error: {:?}", e))?;
200        Ok(ok)
201    }
202
203    /// Extract /64 subnet from IPv6 address
204    pub fn extract_subnet_64(&self) -> Ipv6Addr {
205        let octets = self.ipv6_addr.octets();
206        let mut subnet = [0u8; 16];
207        subnet[..8].copy_from_slice(&octets[..8]); // Keep first 64 bits, zero the rest
208        Ipv6Addr::from(subnet)
209    }
210
211    /// Extract /48 subnet from IPv6 address
212    pub fn extract_subnet_48(&self) -> Ipv6Addr {
213        let octets = self.ipv6_addr.octets();
214        let mut subnet = [0u8; 16];
215        subnet[..6].copy_from_slice(&octets[..6]); // Keep first 48 bits, zero the rest
216        Ipv6Addr::from(subnet)
217    }
218
219    /// Extract /32 subnet from IPv6 address
220    pub fn extract_subnet_32(&self) -> Ipv6Addr {
221        let octets = self.ipv6_addr.octets();
222        let mut subnet = [0u8; 16];
223        subnet[..4].copy_from_slice(&octets[..4]); // Keep first 32 bits, zero the rest
224        Ipv6Addr::from(subnet)
225    }
226}
227
228/// IP diversity enforcement system
229#[derive(Debug)]
230pub struct IPDiversityEnforcer {
231    config: IPDiversityConfig,
232    subnet_64_counts: HashMap<Ipv6Addr, usize>,
233    subnet_48_counts: HashMap<Ipv6Addr, usize>,
234    subnet_32_counts: HashMap<Ipv6Addr, usize>,
235    asn_counts: HashMap<u32, usize>,
236    country_counts: HashMap<String, usize>,
237    geo_provider: Option<Arc<dyn GeoProvider + Send + Sync>>,
238}
239
240impl IPDiversityEnforcer {
241    /// Create a new IP diversity enforcer
242    pub fn new(config: IPDiversityConfig) -> Self {
243        Self {
244            config,
245            subnet_64_counts: HashMap::new(),
246            subnet_48_counts: HashMap::new(),
247            subnet_32_counts: HashMap::new(),
248            asn_counts: HashMap::new(),
249            country_counts: HashMap::new(),
250            geo_provider: None,
251        }
252    }
253
254    /// Create a new IP diversity enforcer with a GeoIP/ASN provider
255    pub fn with_geo_provider(
256        config: IPDiversityConfig,
257        provider: Arc<dyn GeoProvider + Send + Sync>,
258    ) -> Self {
259        let mut s = Self::new(config);
260        s.geo_provider = Some(provider);
261        s
262    }
263
264    /// Analyze an IPv6 address for diversity enforcement
265    pub fn analyze_ip(&self, ipv6_addr: Ipv6Addr) -> Result<IPAnalysis> {
266        let subnet_64 = Self::extract_subnet_prefix(ipv6_addr, 64);
267        let subnet_48 = Self::extract_subnet_prefix(ipv6_addr, 48);
268        let subnet_32 = Self::extract_subnet_prefix(ipv6_addr, 32);
269
270        // GeoIP/ASN lookup via provider if available
271        let (asn, country, is_hosting_provider, is_vpn_provider) =
272            if let Some(p) = &self.geo_provider {
273                let info = p.lookup(ipv6_addr);
274                (
275                    info.asn,
276                    info.country,
277                    info.is_hosting_provider,
278                    info.is_vpn_provider,
279                )
280            } else {
281                (None, None, false, false)
282            };
283
284        // Default reputation for new IPs
285        let reputation_score = 0.5;
286
287        Ok(IPAnalysis {
288            subnet_64,
289            subnet_48,
290            subnet_32,
291            asn,
292            country,
293            is_hosting_provider,
294            is_vpn_provider,
295            reputation_score,
296        })
297    }
298
299    /// Check if a new node can be accepted based on IP diversity constraints
300    pub fn can_accept_node(&self, ip_analysis: &IPAnalysis) -> bool {
301        // Determine limits based on hosting provider status
302        let (limit_64, limit_48, limit_32, limit_asn) =
303            if ip_analysis.is_hosting_provider || ip_analysis.is_vpn_provider {
304                // Stricter limits for hosting providers (halved)
305                (
306                    std::cmp::max(1, self.config.max_nodes_per_64 / 2),
307                    std::cmp::max(1, self.config.max_nodes_per_48 / 2),
308                    std::cmp::max(1, self.config.max_nodes_per_32 / 2),
309                    std::cmp::max(1, self.config.max_nodes_per_asn / 2),
310                )
311            } else {
312                // Regular limits for normal nodes
313                (
314                    self.config.max_nodes_per_64,
315                    self.config.max_nodes_per_48,
316                    self.config.max_nodes_per_32,
317                    self.config.max_nodes_per_asn,
318                )
319            };
320
321        // Check /64 subnet limit
322        if let Some(&count) = self.subnet_64_counts.get(&ip_analysis.subnet_64)
323            && count >= limit_64
324        {
325            return false;
326        }
327
328        // Check /48 subnet limit
329        if let Some(&count) = self.subnet_48_counts.get(&ip_analysis.subnet_48)
330            && count >= limit_48
331        {
332            return false;
333        }
334
335        // Check /32 subnet limit
336        if let Some(&count) = self.subnet_32_counts.get(&ip_analysis.subnet_32)
337            && count >= limit_32
338        {
339            return false;
340        }
341
342        // Check ASN limit
343        if let Some(asn) = ip_analysis.asn
344            && let Some(&count) = self.asn_counts.get(&asn)
345            && count >= limit_asn
346        {
347            return false;
348        }
349
350        true
351    }
352
353    /// Add a node to the diversity tracking
354    pub fn add_node(&mut self, ip_analysis: &IPAnalysis) -> Result<()> {
355        if !self.can_accept_node(ip_analysis) {
356            return Err(anyhow!("IP diversity limits exceeded"));
357        }
358
359        // Update counts
360        *self
361            .subnet_64_counts
362            .entry(ip_analysis.subnet_64)
363            .or_insert(0) += 1;
364        *self
365            .subnet_48_counts
366            .entry(ip_analysis.subnet_48)
367            .or_insert(0) += 1;
368        *self
369            .subnet_32_counts
370            .entry(ip_analysis.subnet_32)
371            .or_insert(0) += 1;
372
373        if let Some(asn) = ip_analysis.asn {
374            *self.asn_counts.entry(asn).or_insert(0) += 1;
375        }
376
377        if let Some(ref country) = ip_analysis.country {
378            *self.country_counts.entry(country.clone()).or_insert(0) += 1;
379        }
380
381        Ok(())
382    }
383
384    /// Remove a node from diversity tracking
385    pub fn remove_node(&mut self, ip_analysis: &IPAnalysis) {
386        if let Some(count) = self.subnet_64_counts.get_mut(&ip_analysis.subnet_64) {
387            *count = count.saturating_sub(1);
388            if *count == 0 {
389                self.subnet_64_counts.remove(&ip_analysis.subnet_64);
390            }
391        }
392
393        if let Some(count) = self.subnet_48_counts.get_mut(&ip_analysis.subnet_48) {
394            *count = count.saturating_sub(1);
395            if *count == 0 {
396                self.subnet_48_counts.remove(&ip_analysis.subnet_48);
397            }
398        }
399
400        if let Some(count) = self.subnet_32_counts.get_mut(&ip_analysis.subnet_32) {
401            *count = count.saturating_sub(1);
402            if *count == 0 {
403                self.subnet_32_counts.remove(&ip_analysis.subnet_32);
404            }
405        }
406
407        if let Some(asn) = ip_analysis.asn
408            && let Some(count) = self.asn_counts.get_mut(&asn)
409        {
410            *count = count.saturating_sub(1);
411            if *count == 0 {
412                self.asn_counts.remove(&asn);
413            }
414        }
415
416        if let Some(ref country) = ip_analysis.country
417            && let Some(count) = self.country_counts.get_mut(country)
418        {
419            *count = count.saturating_sub(1);
420            if *count == 0 {
421                self.country_counts.remove(country);
422            }
423        }
424    }
425
426    /// Extract network prefix of specified length from IPv6 address
427    pub fn extract_subnet_prefix(addr: Ipv6Addr, prefix_len: u8) -> Ipv6Addr {
428        let octets = addr.octets();
429        let mut subnet = [0u8; 16];
430
431        let bytes_to_copy = (prefix_len / 8) as usize;
432        let remaining_bits = prefix_len % 8;
433
434        // Copy full bytes
435        if bytes_to_copy < 16 {
436            subnet[..bytes_to_copy].copy_from_slice(&octets[..bytes_to_copy]);
437        } else {
438            subnet.copy_from_slice(&octets);
439        }
440
441        // Handle partial byte
442        if remaining_bits > 0 && bytes_to_copy < 16 {
443            let mask = 0xFF << (8 - remaining_bits);
444            subnet[bytes_to_copy] = octets[bytes_to_copy] & mask;
445        }
446
447        Ipv6Addr::from(subnet)
448    }
449
450    /// Get diversity statistics
451    pub fn get_diversity_stats(&self) -> DiversityStats {
452        DiversityStats {
453            total_64_subnets: self.subnet_64_counts.len(),
454            total_48_subnets: self.subnet_48_counts.len(),
455            total_32_subnets: self.subnet_32_counts.len(),
456            total_asns: self.asn_counts.len(),
457            total_countries: self.country_counts.len(),
458            max_nodes_per_64: self.subnet_64_counts.values().max().copied().unwrap_or(0),
459            max_nodes_per_48: self.subnet_48_counts.values().max().copied().unwrap_or(0),
460            max_nodes_per_32: self.subnet_32_counts.values().max().copied().unwrap_or(0),
461        }
462    }
463}
464
465/// GeoIP/ASN provider trait
466pub trait GeoProvider: std::fmt::Debug {
467    fn lookup(&self, ip: Ipv6Addr) -> GeoInfo;
468}
469
470/// Geo information
471#[derive(Debug, Clone)]
472pub struct GeoInfo {
473    pub asn: Option<u32>,
474    pub country: Option<String>,
475    pub is_hosting_provider: bool,
476    pub is_vpn_provider: bool,
477}
478
479/// A simple in-memory caching wrapper for a GeoProvider
480#[derive(Debug)]
481pub struct CachedGeoProvider<P: GeoProvider> {
482    inner: P,
483    cache: parking_lot::RwLock<HashMap<Ipv6Addr, GeoInfo>>,
484}
485
486impl<P: GeoProvider> CachedGeoProvider<P> {
487    pub fn new(inner: P) -> Self {
488        Self {
489            inner,
490            cache: parking_lot::RwLock::new(HashMap::new()),
491        }
492    }
493}
494
495impl<P: GeoProvider> GeoProvider for CachedGeoProvider<P> {
496    fn lookup(&self, ip: Ipv6Addr) -> GeoInfo {
497        if let Some(info) = self.cache.read().get(&ip).cloned() {
498            return info;
499        }
500        let info = self.inner.lookup(ip);
501        self.cache.write().insert(ip, info.clone());
502        info
503    }
504}
505
506/// Stub provider returning no ASN/GeoIP info
507#[derive(Debug)]
508pub struct StubGeoProvider;
509impl GeoProvider for StubGeoProvider {
510    fn lookup(&self, _ip: Ipv6Addr) -> GeoInfo {
511        GeoInfo {
512            asn: None,
513            country: None,
514            is_hosting_provider: false,
515            is_vpn_provider: false,
516        }
517    }
518}
519
520/// Diversity statistics for monitoring
521#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct DiversityStats {
523    /// Number of unique /64 subnets represented
524    pub total_64_subnets: usize,
525    /// Number of unique /48 subnets represented
526    pub total_48_subnets: usize,
527    /// Number of unique /32 subnets represented
528    pub total_32_subnets: usize,
529    /// Number of unique ASNs represented
530    pub total_asns: usize,
531    /// Number of unique countries represented
532    pub total_countries: usize,
533    /// Maximum nodes in any single /64 subnet
534    pub max_nodes_per_64: usize,
535    /// Maximum nodes in any single /48 subnet
536    pub max_nodes_per_48: usize,
537    /// Maximum nodes in any single /32 subnet
538    pub max_nodes_per_32: usize,
539}
540
541/// Reputation manager for tracking node behavior
542#[derive(Debug)]
543pub struct ReputationManager {
544    reputations: HashMap<PeerId, NodeReputation>,
545    reputation_decay: f64,
546    min_reputation: f64,
547}
548
549impl ReputationManager {
550    /// Create a new reputation manager
551    pub fn new(reputation_decay: f64, min_reputation: f64) -> Self {
552        Self {
553            reputations: HashMap::new(),
554            reputation_decay,
555            min_reputation,
556        }
557    }
558
559    /// Get reputation for a peer
560    pub fn get_reputation(&self, peer_id: &PeerId) -> Option<&NodeReputation> {
561        self.reputations.get(peer_id)
562    }
563
564    /// Update reputation based on interaction
565    pub fn update_reputation(&mut self, peer_id: &PeerId, success: bool, response_time: Duration) {
566        let reputation =
567            self.reputations
568                .entry(peer_id.clone())
569                .or_insert_with(|| NodeReputation {
570                    peer_id: peer_id.clone(),
571                    response_rate: 0.5,
572                    response_time: Duration::from_millis(500),
573                    consistency_score: 0.5,
574                    uptime_estimate: Duration::from_secs(0),
575                    routing_accuracy: 0.5,
576                    last_seen: SystemTime::now(),
577                    interaction_count: 0,
578                });
579
580        // Use higher learning rate for faster convergence in tests
581        let alpha = 0.3; // Increased from 0.1 for better test convergence
582
583        if success {
584            reputation.response_rate = reputation.response_rate * (1.0 - alpha) + alpha;
585        } else {
586            reputation.response_rate *= 1.0 - alpha;
587        }
588
589        // Update response time
590        let response_time_ms = response_time.as_millis() as f64;
591        let current_response_ms = reputation.response_time.as_millis() as f64;
592        let new_response_ms = current_response_ms * (1.0 - alpha) + response_time_ms * alpha;
593        reputation.response_time = Duration::from_millis(new_response_ms as u64);
594
595        reputation.last_seen = SystemTime::now();
596        reputation.interaction_count += 1;
597    }
598
599    /// Apply time-based reputation decay
600    pub fn apply_decay(&mut self) {
601        let now = SystemTime::now();
602
603        self.reputations.retain(|_, reputation| {
604            if let Ok(elapsed) = now.duration_since(reputation.last_seen) {
605                // Decay reputation over time
606                let decay_factor = (-elapsed.as_secs_f64() / 3600.0 * self.reputation_decay).exp();
607                reputation.response_rate *= decay_factor;
608                reputation.consistency_score *= decay_factor;
609                reputation.routing_accuracy *= decay_factor;
610
611                // Remove nodes with very low reputation
612                reputation.response_rate > self.min_reputation / 10.0
613            } else {
614                true
615            }
616        });
617    }
618}
619
620// Ed25519 compatibility removed
621
622#[cfg(test)]
623mod tests {
624    use super::*;
625    use crate::quantum_crypto::generate_ml_dsa_keypair;
626
627    fn create_test_keypair() -> (MlDsaPublicKey, MlDsaSecretKey) {
628        generate_ml_dsa_keypair().expect("Failed to generate test keypair")
629    }
630
631    fn create_test_ipv6() -> Ipv6Addr {
632        Ipv6Addr::new(
633            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7334,
634        )
635    }
636
637    fn create_test_diversity_config() -> IPDiversityConfig {
638        IPDiversityConfig {
639            max_nodes_per_64: 1,
640            max_nodes_per_48: 3,
641            max_nodes_per_32: 10,
642            max_nodes_per_asn: 20,
643            enable_geolocation_check: true,
644            min_geographic_diversity: 3,
645        }
646    }
647
648    #[test]
649    fn test_ipv6_node_id_generation() -> Result<()> {
650        let (public_key, secret_key) = create_test_keypair();
651        let ipv6_addr = create_test_ipv6();
652
653        let node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
654
655        assert_eq!(node_id.ipv6_addr, ipv6_addr);
656        assert_eq!(node_id.public_key.len(), 1952); // ML-DSA-65 public key size
657        assert_eq!(node_id.signature.len(), 3309); // ML-DSA-65 signature size
658        assert_eq!(node_id.node_id.len(), 32); // SHA256 output
659        assert_eq!(node_id.salt.len(), 16);
660        assert!(node_id.timestamp_secs > 0);
661
662        Ok(())
663    }
664
665    #[test]
666    fn test_ipv6_node_id_verification() -> Result<()> {
667        let (public_key, secret_key) = create_test_keypair();
668        let ipv6_addr = create_test_ipv6();
669
670        let node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
671        let is_valid = node_id.verify()?;
672
673        assert!(is_valid);
674
675        Ok(())
676    }
677
678    #[test]
679    fn test_ipv6_node_id_verification_fails_with_wrong_data() -> Result<()> {
680        let (public_key, secret_key) = create_test_keypair();
681        let ipv6_addr = create_test_ipv6();
682
683        let mut node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
684
685        // Tamper with the node ID
686        node_id.node_id[0] ^= 0xFF;
687        let is_valid = node_id.verify()?;
688        assert!(!is_valid);
689
690        // Test with wrong signature length
691        let mut node_id2 = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
692        node_id2.signature = vec![0u8; 32]; // Wrong length (should be 3309 for ML-DSA)
693        let is_valid2 = node_id2.verify()?;
694        assert!(!is_valid2);
695
696        // Test with wrong public key length
697        let mut node_id3 = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
698        node_id3.public_key = vec![0u8; 16]; // Wrong length (should be 1952 for ML-DSA-65)
699        let is_valid3 = node_id3.verify()?;
700        assert!(!is_valid3);
701
702        Ok(())
703    }
704
705    #[test]
706    fn test_ipv6_subnet_extraction() -> Result<()> {
707        let (public_key, secret_key) = create_test_keypair();
708        let ipv6_addr = Ipv6Addr::new(
709            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
710        );
711
712        let node_id = IPv6NodeID::generate(ipv6_addr, &secret_key, &public_key)?;
713
714        // Test /64 subnet extraction
715        let subnet_64 = node_id.extract_subnet_64();
716        let expected_64 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0);
717        assert_eq!(subnet_64, expected_64);
718
719        // Test /48 subnet extraction
720        let subnet_48 = node_id.extract_subnet_48();
721        let expected_48 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0);
722        assert_eq!(subnet_48, expected_48);
723
724        // Test /32 subnet extraction
725        let subnet_32 = node_id.extract_subnet_32();
726        let expected_32 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
727        assert_eq!(subnet_32, expected_32);
728
729        Ok(())
730    }
731
732    #[test]
733    fn test_ip_diversity_config_default() {
734        let config = IPDiversityConfig::default();
735
736        assert_eq!(config.max_nodes_per_64, 1);
737        assert_eq!(config.max_nodes_per_48, 3);
738        assert_eq!(config.max_nodes_per_32, 10);
739        assert_eq!(config.max_nodes_per_asn, 20);
740        assert!(config.enable_geolocation_check);
741        assert_eq!(config.min_geographic_diversity, 3);
742    }
743
744    #[test]
745    fn test_ip_diversity_enforcer_creation() {
746        let config = create_test_diversity_config();
747        let enforcer = IPDiversityEnforcer::new(config.clone());
748
749        assert_eq!(enforcer.config.max_nodes_per_64, config.max_nodes_per_64);
750        assert_eq!(enforcer.subnet_64_counts.len(), 0);
751        assert_eq!(enforcer.subnet_48_counts.len(), 0);
752        assert_eq!(enforcer.subnet_32_counts.len(), 0);
753    }
754
755    #[test]
756    fn test_ip_analysis() -> Result<()> {
757        let config = create_test_diversity_config();
758        let enforcer = IPDiversityEnforcer::new(config);
759
760        let ipv6_addr = create_test_ipv6();
761        let analysis = enforcer.analyze_ip(ipv6_addr)?;
762
763        assert_eq!(
764            analysis.subnet_64,
765            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 64)
766        );
767        assert_eq!(
768            analysis.subnet_48,
769            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 48)
770        );
771        assert_eq!(
772            analysis.subnet_32,
773            IPDiversityEnforcer::extract_subnet_prefix(ipv6_addr, 32)
774        );
775        assert!(analysis.asn.is_none()); // Not implemented in test
776        assert!(analysis.country.is_none()); // Not implemented in test
777        assert!(!analysis.is_hosting_provider);
778        assert!(!analysis.is_vpn_provider);
779        assert_eq!(analysis.reputation_score, 0.5);
780
781        Ok(())
782    }
783
784    #[test]
785    fn test_can_accept_node_basic() -> Result<()> {
786        let config = create_test_diversity_config();
787        let enforcer = IPDiversityEnforcer::new(config);
788
789        let ipv6_addr = create_test_ipv6();
790        let analysis = enforcer.analyze_ip(ipv6_addr)?;
791
792        // Should accept first node
793        assert!(enforcer.can_accept_node(&analysis));
794
795        Ok(())
796    }
797
798    #[test]
799    fn test_add_and_remove_node() -> Result<()> {
800        let config = create_test_diversity_config();
801        let mut enforcer = IPDiversityEnforcer::new(config);
802
803        let ipv6_addr = create_test_ipv6();
804        let analysis = enforcer.analyze_ip(ipv6_addr)?;
805
806        // Add node
807        enforcer.add_node(&analysis)?;
808        assert_eq!(enforcer.subnet_64_counts.get(&analysis.subnet_64), Some(&1));
809        assert_eq!(enforcer.subnet_48_counts.get(&analysis.subnet_48), Some(&1));
810        assert_eq!(enforcer.subnet_32_counts.get(&analysis.subnet_32), Some(&1));
811
812        // Remove node
813        enforcer.remove_node(&analysis);
814        assert_eq!(enforcer.subnet_64_counts.get(&analysis.subnet_64), None);
815        assert_eq!(enforcer.subnet_48_counts.get(&analysis.subnet_48), None);
816        assert_eq!(enforcer.subnet_32_counts.get(&analysis.subnet_32), None);
817
818        Ok(())
819    }
820
821    #[test]
822    fn test_diversity_limits_enforcement() -> Result<()> {
823        let config = create_test_diversity_config();
824        let mut enforcer = IPDiversityEnforcer::new(config);
825
826        let ipv6_addr1 = Ipv6Addr::new(
827            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
828        );
829        let ipv6_addr2 = Ipv6Addr::new(
830            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7335,
831        ); // Same /64
832
833        let analysis1 = enforcer.analyze_ip(ipv6_addr1)?;
834        let analysis2 = enforcer.analyze_ip(ipv6_addr2)?;
835
836        // First node should be accepted
837        assert!(enforcer.can_accept_node(&analysis1));
838        enforcer.add_node(&analysis1)?;
839
840        // Second node in same /64 should be rejected (max_nodes_per_64 = 1)
841        assert!(!enforcer.can_accept_node(&analysis2));
842
843        // But adding should fail
844        let result = enforcer.add_node(&analysis2);
845        assert!(result.is_err());
846        assert!(
847            result
848                .unwrap_err()
849                .to_string()
850                .contains("IP diversity limits exceeded")
851        );
852
853        Ok(())
854    }
855
856    #[test]
857    fn test_hosting_provider_stricter_limits() -> Result<()> {
858        let config = IPDiversityConfig {
859            max_nodes_per_64: 4, // Set higher limit for regular nodes
860            max_nodes_per_48: 8,
861            ..create_test_diversity_config()
862        };
863        let mut enforcer = IPDiversityEnforcer::new(config);
864
865        let ipv6_addr = create_test_ipv6();
866        let mut analysis = enforcer.analyze_ip(ipv6_addr)?;
867        analysis.is_hosting_provider = true;
868
869        // Should accept first hosting provider node
870        assert!(enforcer.can_accept_node(&analysis));
871        enforcer.add_node(&analysis)?;
872
873        // Add second hosting provider node in same /64 (should be accepted with limit=2)
874        let ipv6_addr2 = Ipv6Addr::new(
875            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7335,
876        );
877        let mut analysis2 = enforcer.analyze_ip(ipv6_addr2)?;
878        analysis2.is_hosting_provider = true;
879        analysis2.subnet_64 = analysis.subnet_64; // Force same subnet
880
881        assert!(enforcer.can_accept_node(&analysis2));
882        enforcer.add_node(&analysis2)?;
883
884        // Should reject third hosting provider node in same /64 (exceeds limit=2)
885        let ipv6_addr3 = Ipv6Addr::new(
886            0x2001, 0xdb8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7336,
887        );
888        let mut analysis3 = enforcer.analyze_ip(ipv6_addr3)?;
889        analysis3.is_hosting_provider = true;
890        analysis3.subnet_64 = analysis.subnet_64; // Force same subnet
891
892        assert!(!enforcer.can_accept_node(&analysis3));
893
894        Ok(())
895    }
896
897    #[test]
898    fn test_diversity_stats() -> Result<()> {
899        let config = create_test_diversity_config();
900        let mut enforcer = IPDiversityEnforcer::new(config);
901
902        // Add some nodes with different subnets
903        let addresses = [
904            Ipv6Addr::new(
905                0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
906            ),
907            Ipv6Addr::new(
908                0x2001, 0xdb8, 0x85a4, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
909            ), // Different /48
910            Ipv6Addr::new(
911                0x2002, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
912            ), // Different /32
913        ];
914
915        for addr in addresses {
916            let analysis = enforcer.analyze_ip(addr)?;
917            enforcer.add_node(&analysis)?;
918        }
919
920        let stats = enforcer.get_diversity_stats();
921        assert_eq!(stats.total_64_subnets, 3);
922        assert_eq!(stats.total_48_subnets, 3);
923        assert_eq!(stats.total_32_subnets, 2); // Two /32 prefixes
924        assert_eq!(stats.max_nodes_per_64, 1);
925        assert_eq!(stats.max_nodes_per_48, 1);
926        assert_eq!(stats.max_nodes_per_32, 2); // 2001:db8 has 2 nodes
927
928        Ok(())
929    }
930
931    #[test]
932    fn test_extract_subnet_prefix() {
933        let addr = Ipv6Addr::new(
934            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x7334,
935        );
936
937        // Test /64 prefix
938        let prefix_64 = IPDiversityEnforcer::extract_subnet_prefix(addr, 64);
939        let expected_64 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0);
940        assert_eq!(prefix_64, expected_64);
941
942        // Test /48 prefix
943        let prefix_48 = IPDiversityEnforcer::extract_subnet_prefix(addr, 48);
944        let expected_48 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0);
945        assert_eq!(prefix_48, expected_48);
946
947        // Test /32 prefix
948        let prefix_32 = IPDiversityEnforcer::extract_subnet_prefix(addr, 32);
949        let expected_32 = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
950        assert_eq!(prefix_32, expected_32);
951
952        // Test /56 prefix (partial byte)
953        let prefix_56 = IPDiversityEnforcer::extract_subnet_prefix(addr, 56);
954        let expected_56 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1200, 0, 0, 0, 0);
955        assert_eq!(prefix_56, expected_56);
956
957        // Test /128 prefix (full address)
958        let prefix_128 = IPDiversityEnforcer::extract_subnet_prefix(addr, 128);
959        assert_eq!(prefix_128, addr);
960    }
961
962    #[test]
963    fn test_reputation_manager_creation() {
964        let manager = ReputationManager::new(0.1, 0.1);
965        assert_eq!(manager.reputation_decay, 0.1);
966        assert_eq!(manager.min_reputation, 0.1);
967        assert_eq!(manager.reputations.len(), 0);
968    }
969
970    #[test]
971    fn test_reputation_get_nonexistent() {
972        let manager = ReputationManager::new(0.1, 0.1);
973        let peer_id = "test_peer".to_string();
974
975        let reputation = manager.get_reputation(&peer_id);
976        assert!(reputation.is_none());
977    }
978
979    #[test]
980    fn test_reputation_update_creates_entry() {
981        let mut manager = ReputationManager::new(0.1, 0.1);
982        let peer_id = "test_peer".to_string();
983
984        manager.update_reputation(&peer_id, true, Duration::from_millis(100));
985
986        let reputation = manager.get_reputation(&peer_id);
987        assert!(reputation.is_some());
988
989        let rep = reputation.unwrap();
990        assert_eq!(rep.peer_id, peer_id);
991        assert!(rep.response_rate > 0.5); // Should increase from initial 0.5
992        assert_eq!(rep.interaction_count, 1);
993    }
994
995    #[test]
996    fn test_reputation_update_success_improves_rate() {
997        let mut manager = ReputationManager::new(0.1, 0.1);
998        let peer_id = "test_peer".to_string();
999
1000        // Multiple successful interactions
1001        for _ in 0..15 {
1002            manager.update_reputation(&peer_id, true, Duration::from_millis(100));
1003        }
1004
1005        let reputation = manager.get_reputation(&peer_id).unwrap();
1006        assert!(reputation.response_rate > 0.85); // Should be very high with higher learning rate
1007        assert_eq!(reputation.interaction_count, 15);
1008    }
1009
1010    #[test]
1011    fn test_reputation_update_failure_decreases_rate() {
1012        let mut manager = ReputationManager::new(0.1, 0.1);
1013        let peer_id = "test_peer".to_string();
1014
1015        // Multiple failed interactions
1016        for _ in 0..15 {
1017            manager.update_reputation(&peer_id, false, Duration::from_millis(1000));
1018        }
1019
1020        let reputation = manager.get_reputation(&peer_id).unwrap();
1021        assert!(reputation.response_rate < 0.15); // Should be very low with higher learning rate
1022        assert_eq!(reputation.interaction_count, 15);
1023    }
1024
1025    #[test]
1026    fn test_reputation_response_time_tracking() {
1027        let mut manager = ReputationManager::new(0.1, 0.1);
1028        let peer_id = "test_peer".to_string();
1029
1030        // Update with specific response time
1031        manager.update_reputation(&peer_id, true, Duration::from_millis(200));
1032
1033        let reputation = manager.get_reputation(&peer_id).unwrap();
1034        // Response time should be between initial 500ms and new 200ms
1035        assert!(reputation.response_time.as_millis() > 200);
1036        assert!(reputation.response_time.as_millis() < 500);
1037    }
1038
1039    #[test]
1040    fn test_reputation_decay() {
1041        let mut manager = ReputationManager::new(1.0, 0.01); // High decay rate
1042        let peer_id = "test_peer".to_string();
1043
1044        // Create a reputation entry
1045        manager.update_reputation(&peer_id, true, Duration::from_millis(100));
1046
1047        // Manually set last_seen to past
1048        if let Some(reputation) = manager.reputations.get_mut(&peer_id) {
1049            reputation.last_seen = SystemTime::now() - Duration::from_secs(7200); // 2 hours ago
1050        }
1051
1052        let original_rate = manager.get_reputation(&peer_id).unwrap().response_rate;
1053
1054        // Apply decay
1055        manager.apply_decay();
1056
1057        let reputation = manager.get_reputation(&peer_id);
1058        if let Some(rep) = reputation {
1059            // Should have decayed
1060            assert!(rep.response_rate < original_rate);
1061        } // else the reputation was removed due to low score
1062    }
1063
1064    #[test]
1065    fn test_reputation_decay_removes_low_reputation() {
1066        let mut manager = ReputationManager::new(0.1, 0.5); // High min reputation
1067        let peer_id = "test_peer".to_string();
1068
1069        // Create a low reputation entry
1070        for _ in 0..10 {
1071            manager.update_reputation(&peer_id, false, Duration::from_millis(1000));
1072        }
1073
1074        // Manually set last_seen to past
1075        if let Some(reputation) = manager.reputations.get_mut(&peer_id) {
1076            reputation.last_seen = SystemTime::now() - Duration::from_secs(3600); // 1 hour ago
1077            reputation.response_rate = 0.01; // Very low
1078        }
1079
1080        // Apply decay
1081        manager.apply_decay();
1082
1083        // Should be removed
1084        assert!(manager.get_reputation(&peer_id).is_none());
1085    }
1086
1087    #[test]
1088    fn test_security_types_keypair() {
1089        let (public_key, secret_key) =
1090            generate_ml_dsa_keypair().expect("Failed to generate keypair");
1091
1092        let public_key_bytes = public_key.as_bytes();
1093        assert_eq!(public_key_bytes.len(), 1952); // ML-DSA-65 public key size
1094
1095        let message = b"test message";
1096        let signature = ml_dsa_sign(&secret_key, message).expect("Failed to sign message");
1097        assert_eq!(signature.as_bytes().len(), 3309); // ML-DSA-65 signature size
1098
1099        // Verify the signature
1100        assert!(ml_dsa_verify(&public_key, message, &signature).is_ok());
1101    }
1102
1103    #[test]
1104    fn test_node_reputation_structure() {
1105        let peer_id = "test_peer".to_string();
1106        let reputation = NodeReputation {
1107            peer_id: peer_id.clone(),
1108            response_rate: 0.85,
1109            response_time: Duration::from_millis(150),
1110            consistency_score: 0.9,
1111            uptime_estimate: Duration::from_secs(86400),
1112            routing_accuracy: 0.8,
1113            last_seen: SystemTime::now(),
1114            interaction_count: 42,
1115        };
1116
1117        assert_eq!(reputation.peer_id, peer_id);
1118        assert_eq!(reputation.response_rate, 0.85);
1119        assert_eq!(reputation.response_time, Duration::from_millis(150));
1120        assert_eq!(reputation.consistency_score, 0.9);
1121        assert_eq!(reputation.uptime_estimate, Duration::from_secs(86400));
1122        assert_eq!(reputation.routing_accuracy, 0.8);
1123        assert_eq!(reputation.interaction_count, 42);
1124    }
1125
1126    #[test]
1127    fn test_ip_analysis_structure() {
1128        let analysis = IPAnalysis {
1129            subnet_64: Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0, 0, 0, 0),
1130            subnet_48: Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0, 0, 0, 0, 0),
1131            subnet_32: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
1132            asn: Some(64512),
1133            country: Some("US".to_string()),
1134            is_hosting_provider: true,
1135            is_vpn_provider: false,
1136            reputation_score: 0.75,
1137        };
1138
1139        assert_eq!(analysis.asn, Some(64512));
1140        assert_eq!(analysis.country, Some("US".to_string()));
1141        assert!(analysis.is_hosting_provider);
1142        assert!(!analysis.is_vpn_provider);
1143        assert_eq!(analysis.reputation_score, 0.75);
1144    }
1145
1146    #[test]
1147    fn test_diversity_stats_structure() {
1148        let stats = DiversityStats {
1149            total_64_subnets: 100,
1150            total_48_subnets: 50,
1151            total_32_subnets: 25,
1152            total_asns: 15,
1153            total_countries: 8,
1154            max_nodes_per_64: 1,
1155            max_nodes_per_48: 3,
1156            max_nodes_per_32: 10,
1157        };
1158
1159        assert_eq!(stats.total_64_subnets, 100);
1160        assert_eq!(stats.total_48_subnets, 50);
1161        assert_eq!(stats.total_32_subnets, 25);
1162        assert_eq!(stats.total_asns, 15);
1163        assert_eq!(stats.total_countries, 8);
1164        assert_eq!(stats.max_nodes_per_64, 1);
1165        assert_eq!(stats.max_nodes_per_48, 3);
1166        assert_eq!(stats.max_nodes_per_32, 10);
1167    }
1168
1169    #[test]
1170    fn test_multiple_same_subnet_nodes() -> Result<()> {
1171        let config = IPDiversityConfig {
1172            max_nodes_per_64: 3, // Allow more nodes in same /64
1173            max_nodes_per_48: 5,
1174            max_nodes_per_32: 10,
1175            ..create_test_diversity_config()
1176        };
1177        let mut enforcer = IPDiversityEnforcer::new(config);
1178
1179        let _base_addr = Ipv6Addr::new(
1180            0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 0x0000,
1181        );
1182
1183        // Add 3 nodes in same /64 subnet
1184        for i in 1..=3 {
1185            let addr = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, i);
1186            let analysis = enforcer.analyze_ip(addr)?;
1187            assert!(enforcer.can_accept_node(&analysis));
1188            enforcer.add_node(&analysis)?;
1189        }
1190
1191        // 4th node should be rejected
1192        let addr4 = Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x1234, 0x5678, 0x8a2e, 0x0370, 4);
1193        let analysis4 = enforcer.analyze_ip(addr4)?;
1194        assert!(!enforcer.can_accept_node(&analysis4));
1195
1196        let stats = enforcer.get_diversity_stats();
1197        assert_eq!(stats.total_64_subnets, 1);
1198        assert_eq!(stats.max_nodes_per_64, 3);
1199
1200        Ok(())
1201    }
1202
1203    #[test]
1204    fn test_asn_and_country_tracking() -> Result<()> {
1205        let config = create_test_diversity_config();
1206        let mut enforcer = IPDiversityEnforcer::new(config);
1207
1208        // Create analysis with ASN and country
1209        let ipv6_addr = create_test_ipv6();
1210        let mut analysis = enforcer.analyze_ip(ipv6_addr)?;
1211        analysis.asn = Some(64512);
1212        analysis.country = Some("US".to_string());
1213
1214        enforcer.add_node(&analysis)?;
1215
1216        assert_eq!(enforcer.asn_counts.get(&64512), Some(&1));
1217        assert_eq!(enforcer.country_counts.get("US"), Some(&1));
1218
1219        // Remove and check cleanup
1220        enforcer.remove_node(&analysis);
1221        assert!(!enforcer.asn_counts.contains_key(&64512));
1222        assert!(!enforcer.country_counts.contains_key("US"));
1223
1224        Ok(())
1225    }
1226
1227    #[test]
1228    fn test_reputation_mixed_interactions() {
1229        let mut manager = ReputationManager::new(0.1, 0.1);
1230        let peer_id = "test_peer".to_string();
1231
1232        // Mix of successful and failed interactions
1233        for i in 0..15 {
1234            let success = i % 3 != 0; // 2/3 success rate
1235            manager.update_reputation(&peer_id, success, Duration::from_millis(100 + i * 10));
1236        }
1237
1238        let reputation = manager.get_reputation(&peer_id).unwrap();
1239        // Should converge closer to 2/3 with more iterations and higher learning rate
1240        // With alpha=0.3 and 2/3 success rate, convergence may be higher
1241        assert!(reputation.response_rate > 0.55);
1242        assert!(reputation.response_rate < 0.85);
1243        assert_eq!(reputation.interaction_count, 15);
1244    }
1245}