#!/usr/bin/env python3 """ Network Configuration Validator Comprehensive validation for Bitcoin network configurations """ import json import re from typing import Dict, List, Any, Optional from dataclasses import dataclass from multi_network_wallet import BitcoinNetwork, NetworkConfig, NETWORK_CONFIGS @dataclass class ValidationResult: """Validation result data structure""" is_valid: bool errors: List[str] warnings: List[str] security_score: float # 0-100 recommendations: List[str] class NetworkConfigValidator: """Advanced network configuration validator""" def __init__(self): self.validation_rules = { "port_security": self._validate_port_security, "magic_bytes": self._validate_magic_bytes, "address_formats": self._validate_address_formats, "seed_nodes": self._validate_seed_nodes, "security_checks": self._validate_security_checks } def validate_complete_config(self, network: BitcoinNetwork) -> ValidationResult: """Perform comprehensive configuration validation""" config = NETWORK_CONFIGS[network] errors = [] warnings = [] security_score = 100.0 recommendations = [] # Run all validation rules for rule_name, rule_func in self.validation_rules.items(): rule_result = rule_func(config, network) errors.extend(rule_result.get("errors", [])) warnings.extend(rule_result.get("warnings", [])) security_score -= rule_result.get("score_deduction", 0) recommendations.extend(rule_result.get("recommendations", [])) # Network-specific validations if network == BitcoinNetwork.MAINNET: mainnet_checks = self._validate_mainnet_specific(config) errors.extend(mainnet_checks["errors"]) security_score -= mainnet_checks["score_deduction"] elif network == BitcoinNetwork.TESTNET: testnet_checks = self._validate_testnet_specific(config) warnings.extend(testnet_checks["warnings"]) elif network == BitcoinNetwork.REGTEST: regtest_checks = self._validate_regtest_specific(config) errors.extend(regtest_checks["errors"]) # Ensure security score doesn't go negative security_score = max(0, security_score) return ValidationResult( is_valid=len(errors) == 0, errors=errors, warnings=warnings, security_score=security_score, recommendations=recommendations ) def _validate_port_security(self, config: NetworkConfig, network: BitcoinNetwork) -> Dict[str, Any]: """Validate port security settings""" result = {"errors": [], "warnings": [], "score_deduction": 0, "recommendations": []} # Check RPC port range if config.rpc_port < 1024: result["warnings"].append(f"RPC port {config.rpc_port} is in privileged range") result["score_deduction"] += 5 result["recommendations"].append("Use RPC port > 1024 for better security") if config.rpc_port == config.default_port: result["warnings"].append("RPC port same as default peer port") result["score_deduction"] += 2 result["recommendations"].append("Use different ports for RPC and peer connections") # Check port conflicts port_conflicts = self._check_port_conflicts(config) if port_conflicts: result["errors"].append(f"Port conflicts detected: {port_conflicts}") result["score_deduction"] += 20 # Check for commonly targeted ports targeted_ports = [22, 23, 80, 443, 3389, 5900] if config.rpc_port in targeted_ports: result["warnings"].append(f"RPC port {config.rpc_port} is commonly targeted") result["score_deduction"] += 10 return result def _validate_magic_bytes(self, config: NetworkConfig, network: BitcoinNetwork) -> Dict[str, Any]: """Validate network magic bytes""" result = {"errors": [], "warnings": [], "score_deduction": 0, "recommendations": []} if not config.magic_bytes: result["errors"].append("Magic bytes not specified") result["score_deduction"] += 25 return result # Validate hex format if not re.match(r'^[0-9a-fA-F]{8}$', config.magic_bytes): result["errors"].append("Magic bytes must be 8-character hex string") result["score_deduction"] += 15 # Check for duplicate magic bytes magic_duplicates = self._check_magic_byte_duplicates(config.magic_bytes) if magic_duplicates: result["errors"].append(f"Magic bytes conflict with: {magic_duplicates}") result["score_deduction"] += 30 return result def _validate_address_formats(self, config: NetworkConfig, network: BitcoinNetwork) -> Dict[str, Any]: """Validate address format prefixes""" result = {"errors": [], "warnings": [], "score_deduction": 0, "recommendations": []} # Validate address prefix if not config.address_prefix or len(config.address_prefix) != 2: result["errors"].append("Invalid address prefix format") result["score_deduction"] += 15 elif not re.match(r'^[0-9a-fA-F]{2}$', config.address_prefix): result["errors"].append("Address prefix must be 2-character hex") result["score_deduction"] += 15 # Validate script prefix if not config.script_prefix or len(config.script_prefix) != 2: result["errors"].append("Invalid script prefix format") result["score_deduction"] += 15 elif not re.match(r'^[0-9a-fA-F]{2}$', config.script_prefix): result["errors"].append("Script prefix must be 2-character hex") result["score_deduction"] += 15 # Check for reasonable prefixes if config.address_prefix == config.script_prefix: result["warnings"].append("Address and script prefixes should differ") result["score_deduction"] += 5 result["recommendations"].append("Use distinct prefixes for address types") return result def _validate_seed_nodes(self, config: NetworkConfig, network: BitcoinNetwork) -> Dict[str, Any]: """Validate seed node configuration""" result = {"errors": [], "warnings": [], "score_deduction": 0, "recommendations": []} if network == BitcoinNetwork.REGTEST: # Regtest shouldn't have seed nodes if config.seed_nodes: result["warnings"].append("Regtest networks typically don't use seed nodes") result["score_deduction"] += 3 else: # Mainnet and testnet should have seed nodes if not config.seed_nodes: result["warnings"].append("No seed nodes configured") result["score_deduction"] += 8 result["recommendations"].append("Add reliable seed nodes for better connectivity") elif len(config.seed_nodes) < 3: result["warnings"].append("Consider adding more seed nodes for redundancy") result["score_deduction"] += 4 # Validate seed node formats for i, seed in enumerate(config.seed_nodes): if not self._is_valid_hostname(seed): result["errors"].append(f"Invalid seed node format: {seed}") result["score_deduction"] += 10 return result def _validate_security_checks(self, config: NetworkConfig, network: BitcoinNetwork) -> Dict[str, Any]: """Perform security-specific validations""" result = {"errors": [], "warnings": [], "score_deduction": 0, "recommendations": []} # Network-specific security considerations if network == BitcoinNetwork.MAINNET: if config.rpc_port < 8332: result["warnings"].append("Mainnet RPC port should be 8332 or higher") result["score_deduction"] += 5 elif network == BitcoinNetwork.TESTNET: if config.rpc_port < 18332: result["warnings"].append("Testnet RPC port should be 18332 or higher") result["score_deduction"] += 3 elif network == BitcoinNetwork.REGTEST: if config.rpc_port != 18443: result["warnings"].append("Standard regtest RPC port is 18443") result["score_deduction"] += 2 # Check for security best practices if config.rpc_port == 8332 and network != BitcoinNetwork.MAINNET: result["warnings"].append("Using mainnet RPC port on non-mainnet network") result["score_deduction"] += 8 return result def _validate_mainnet_specific(self, config: NetworkConfig) -> Dict[str, Any]: """Mainnet-specific validations""" result = {"errors": [], "warnings": [], "score_deduction": 0} # Mainnet should use specific ports if config.rpc_port != 8332: result["warnings"].append("Mainnet standard RPC port is 8332") result["score_deduction"] += 5 if config.default_port != 8333: result["warnings"].append("Mainnet standard default port is 8333") result["score_deduction"] += 5 return result def _validate_testnet_specific(self, config: NetworkConfig) -> Dict[str, Any]: """Testnet-specific validations""" result = {"warnings": [], "score_deduction": 0} if config.rpc_port != 18332: result["warnings"].append("Testnet standard RPC port is 18332") result["score_deduction"] += 3 return result def _validate_regtest_specific(self, config: NetworkConfig) -> Dict[str, Any]: """Regtest-specific validations""" result = {"errors": [], "warnings": [], "score_deduction": 0} if config.rpc_port != 18443: result["warnings"].append("Standard regtest RPC port is 18443") result["score_deduction"] += 3 if config.default_port != 18444: result["warnings"].append("Standard regtest default port is 18444") result["score_deduction"] += 3 return result def _check_port_conflicts(self, config: NetworkConfig) -> List[str]: """Check for port conflicts with other networks""" conflicts = [] for network, network_config in NETWORK_CONFIGS.items(): if config.rpc_port == network_config.rpc_port and network_config != config: conflicts.append(network.value) return conflicts def _check_magic_byte_duplicates(self, magic_bytes: str) -> List[str]: """Check for magic byte conflicts""" conflicts = [] for network, network_config in NETWORK_CONFIGS.items(): if network_config.magic_bytes == magic_bytes and network_config.magic_bytes != magic_bytes: pass # Skip self-comparison return conflicts def _is_valid_hostname(self, hostname: str) -> bool: """Validate hostname format""" if not hostname or len(hostname) > 253: return False # Basic hostname validation pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$' return bool(re.match(pattern, hostname)) def demo_validation(): """Demonstrate configuration validation""" print("=== Network Configuration Validation Demo ===\n") validator = NetworkConfigValidator() # Test all networks for network in BitcoinNetwork: print(f"--- Validating {network.value.upper()} Configuration ---") result = validator.validate_complete_config(network) print(f"Valid: {'✅' if result.is_valid else '❌'}") print(f"Security Score: {result.security_score:.1f}/100") if result.errors: print("Errors:") for error in result.errors: print(f" ❌ {error}") if result.warnings: print("Warnings:") for warning in result.warnings: print(f" ⚠️ {warning}") if result.recommendations: print("Recommendations:") for rec in result.recommendations: print(f" 💡 {rec}") print() if __name__ == "__main__": demo_validation()