// Copyright (c) 2024 Triangles developers // Tor V3 Onion Services Implementation // Distributed under the MIT/X11 software license // Prevent Windows API conflicts #ifdef WIN32 #define NOMINMAX #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // Ensure inet_pton is available #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 #elif _WIN32_WINNT < 0x0600 #undef _WIN32_WINNT #define _WIN32_WINNT 0x0600 #endif #endif #include "onion_v3.h" #include "tor_embedded.h" #include "tor_crypto_compat.h" #include "../util.h" #include "../net.h" #include "../protocol.h" #include "../netbase.h" #include "../addrman.h" #include "../wallet.h" #include "../walletdb.h" #include "../main.h" #include "../init.h" #include "../crypter.h" #include #include #include #include #include #include #include #ifdef WIN32 #include #include #else #include #include #include #include #define closesocket close #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #endif // Ensure we have the global wallet pointer extern CWallet* pwalletMain; // Static instance CTorV3Manager* CTorV3Manager::instance = nullptr; static TorV3Config torV3Config; static boost::filesystem::path GetBackendHiddenServiceDir(const std::string& torDataDir) { return boost::filesystem::path(torDataDir) / "hidden_service"; } static bool ReadTrimmedFirstLine(const boost::filesystem::path& path, std::string& valueOut) { valueOut.clear(); std::ifstream file(path.string().c_str()); if (!file.is_open() || !std::getline(file, valueOut)) { return false; } while (!valueOut.empty()) { const char ch = valueOut[valueOut.size() - 1]; if (ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t') { break; } valueOut.erase(valueOut.size() - 1); } return !valueOut.empty(); } static std::string GetEffectiveTorProxy() { proxyType proxy; if (GetProxy(NET_TOR, proxy)) { return proxy.first.ToStringIPPort(); } if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") { CService torProxy(mapArgs["-tor"], (unsigned short)GetArg("-torsocks", 19099)); if (torProxy.IsValid()) { return torProxy.ToStringIPPort(); } } std::string explicitProxy = GetArg("-torproxy", ""); if (!explicitProxy.empty()) { return explicitProxy; } return strprintf("127.0.0.1:%d", GetArg("-torsocks", 19099)); } // Utility function for proper base32 encoding (RFC 4648) - Tor variant std::string EncodeBase32Proper(const unsigned char* data, size_t len) { // Tor uses lowercase base32 alphabet without padding const char* alphabet = "abcdefghijklmnopqrstuvwxyz234567"; std::string result; if (len == 0) return result; // Reserve space for efficiency result.reserve((len * 8 + 4) / 5); size_t bits = 0; uint32_t value = 0; for (size_t i = 0; i < len; i++) { value = (value << 8) | data[i]; bits += 8; while (bits >= 5) { result += alphabet[(value >> (bits - 5)) & 0x1F]; bits -= 5; } } // Handle remaining bits if (bits > 0) { result += alphabet[(value << (5 - bits)) & 0x1F]; } return result; } // Utility function for base32 decoding (for validation) bool DecodeBase32(const std::string& encoded, std::vector& decoded) { // Tor base32 alphabet const std::string alphabet = "abcdefghijklmnopqrstuvwxyz234567"; decoded.clear(); if (encoded.empty()) return true; size_t bits = 0; uint32_t value = 0; for (char c : encoded) { // Find character in alphabet size_t pos = alphabet.find(c); if (pos == std::string::npos) { return false; // Invalid character } value = (value << 5) | pos; bits += 5; if (bits >= 8) { decoded.push_back((value >> (bits - 8)) & 0xFF); bits -= 8; } } return true; } // CTorV3Service Implementation CTorV3Service::CTorV3Service() : port(19112), isActive(false) { } CTorV3Service::~CTorV3Service() { if (isActive) { StopService(); } } bool CTorV3Service::GenerateV3Service(int servicePort) { // Input validation if (servicePort <= 0 || servicePort > 65535) { printf("ERROR: Invalid service port: %d (must be 1-65535)\n", servicePort); return false; } port = servicePort; // Clear any existing keys and state privateKey.clear(); publicKey.clear(); onionAddress.clear(); isActive = false; // Initialize OpenSSL resources EVP_PKEY_CTX* ctx = nullptr; EVP_PKEY* pkey = nullptr; unsigned char privKeyBytes[32]; unsigned char pubKeyBytes[32]; // Initialize sensitive memory to zero OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(pubKeyBytes, 32); try { // Step 1: Create Ed25519 key generation context ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr); if (!ctx) { unsigned long err = ERR_get_error(); printf("ERROR: Failed to create Ed25519 context (OpenSSL error: %lu)\n", err); return false; } // Step 2: Initialize key generation int init_result = EVP_PKEY_keygen_init(ctx); if (init_result <= 0) { unsigned long err = ERR_get_error(); printf("ERROR: Failed to initialize Ed25519 key generation (result: %d, OpenSSL error: %lu)\n", init_result, err); EVP_PKEY_CTX_free(ctx); return false; } // Step 3: Generate the key pair int keygen_result = EVP_PKEY_keygen(ctx, &pkey); if (keygen_result <= 0 || !pkey) { unsigned long err = ERR_get_error(); printf("ERROR: Failed to generate Ed25519 key pair (result: %d, OpenSSL error: %lu)\n", keygen_result, err); if (pkey) EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 4: Verify the generated key is valid EVP_PKEY_CTX* check_ctx = EVP_PKEY_CTX_new(pkey, nullptr); if (!check_ctx) { printf("ERROR: Failed to create context for key validation\n"); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } int check_result = EVP_PKEY_check(check_ctx); EVP_PKEY_CTX_free(check_ctx); if (check_result != 1) { unsigned long err = ERR_get_error(); printf("ERROR: Generated Ed25519 key failed cryptographic validation (result: %d, OpenSSL error: %lu)\n", check_result, err); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 5: Extract raw private key size_t privKeyLen = 32; int priv_result = EVP_PKEY_get_raw_private_key(pkey, privKeyBytes, &privKeyLen); if (priv_result <= 0) { unsigned long err = ERR_get_error(); printf("ERROR: Failed to extract Ed25519 private key (result: %d, OpenSSL error: %lu)\n", priv_result, err); OPENSSL_cleanse(privKeyBytes, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 6: Validate private key length if (privKeyLen != 32) { printf("ERROR: Invalid Ed25519 private key length: %zu (expected 32)\n", privKeyLen); OPENSSL_cleanse(privKeyBytes, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 7: Extract raw public key size_t pubKeyLen = 32; int pub_result = EVP_PKEY_get_raw_public_key(pkey, pubKeyBytes, &pubKeyLen); if (pub_result <= 0) { unsigned long err = ERR_get_error(); printf("ERROR: Failed to extract Ed25519 public key (result: %d, OpenSSL error: %lu)\n", pub_result, err); OPENSSL_cleanse(privKeyBytes, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 8: Validate public key length if (pubKeyLen != 32) { printf("ERROR: Invalid Ed25519 public key length: %zu (expected 32)\n", pubKeyLen); OPENSSL_cleanse(privKeyBytes, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 9: Validate keys are not all zeros or invalid if (!ValidateEd25519Keys(privKeyBytes, pubKeyBytes)) { printf("ERROR: Generated Ed25519 keys failed cryptographic validation\n"); OPENSSL_cleanse(privKeyBytes, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 10: Verify public key derivation consistency unsigned char derivedPubKey[32]; if (!DerivePublicKeyFromPrivate(privKeyBytes, derivedPubKey)) { printf("ERROR: Failed to derive public key for consistency check\n"); OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(derivedPubKey, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } if (memcmp(pubKeyBytes, derivedPubKey, 32) != 0) { printf("ERROR: Public key derivation inconsistency detected\n"); OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(derivedPubKey, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 11: Store keys securely (convert to hex) privateKey = HexStr(privKeyBytes, privKeyBytes + 32); publicKey = HexStr(pubKeyBytes, pubKeyBytes + 32); // Step 12: Generate proper v3 onion address with checksum if (!GenerateV3OnionAddress(pubKeyBytes, onionAddress)) { printf("ERROR: Failed to generate v3 onion address from public key\n"); // Clear all sensitive data on failure OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(derivedPubKey, 32); privateKey.clear(); publicKey.clear(); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 13: Validate the generated onion address if (!ValidateOnionAddress(onionAddress)) { printf("ERROR: Generated onion address failed validation: %s\n", onionAddress.c_str()); // Clear all sensitive data on failure OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(derivedPubKey, 32); privateKey.clear(); publicKey.clear(); onionAddress.clear(); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 14: Verify address reconstruction consistency std::string reconstructedAddress = GenerateAddressFromPublicKey(pubKeyBytes); if (reconstructedAddress != onionAddress) { printf("ERROR: Address reconstruction inconsistency\n"); printf(" Original: %s\n", onionAddress.c_str()); printf(" Reconstructed: %s\n", reconstructedAddress.c_str()); // Clear all sensitive data on failure OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(derivedPubKey, 32); privateKey.clear(); publicKey.clear(); onionAddress.clear(); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); return false; } // Step 15: Clean up OpenSSL resources OPENSSL_cleanse(privKeyBytes, 32); OPENSSL_cleanse(derivedPubKey, 32); EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); // Step 16: Final validation - ensure all components are present if (privateKey.empty() || publicKey.empty() || onionAddress.empty()) { printf("ERROR: Key generation completed but components are missing\n"); privateKey.clear(); publicKey.clear(); onionAddress.clear(); return false; } printf("Successfully generated V3 onion service:\n"); printf(" Address: %s\n", onionAddress.c_str()); printf(" Port: %d\n", port); printf(" Private key length: %zu characters\n", privateKey.length()); printf(" Public key length: %zu characters\n", publicKey.length()); return true; } catch (const std::exception& e) { printf("ERROR: Exception in GenerateV3Service: %s\n", e.what()); // Comprehensive cleanup on exception OPENSSL_cleanse(privKeyBytes, 32); if (pkey) EVP_PKEY_free(pkey); if (ctx) EVP_PKEY_CTX_free(ctx); privateKey.clear(); publicKey.clear(); onionAddress.clear(); isActive = false; return false; } catch (...) { printf("ERROR: Unknown exception in GenerateV3Service\n"); // Comprehensive cleanup on unknown exception OPENSSL_cleanse(privKeyBytes, 32); if (pkey) EVP_PKEY_free(pkey); if (ctx) EVP_PKEY_CTX_free(ctx); privateKey.clear(); publicKey.clear(); onionAddress.clear(); isActive = false; return false; } } bool CTorV3Service::LoadFromPrivateKey(const std::string& privKey) { // Enhanced input validation if (privKey.empty()) { printf("ERROR: Private key cannot be empty\n"); return false; } if (privKey.length() != 64) { printf("ERROR: Invalid private key format (expected 64 hex characters, got %zu)\n", privKey.length()); return false; } // Validate hex format for (char c : privKey) { if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { printf("ERROR: Private key contains invalid hex character: %c\n", c); return false; } } // Clear any existing service state privateKey.clear(); publicKey.clear(); onionAddress.clear(); isActive = false; unsigned char privKeyBytes[32]; unsigned char pubKeyBytes[32]; try { // Extract keys from hex string with enhanced error handling if (!ExtractKeysFromHex(privKey, privKeyBytes, pubKeyBytes)) { printf("ERROR: Failed to extract keys from hex string\n"); return false; } // Enhanced key validation if (!ValidateEd25519Keys(privKeyBytes, pubKeyBytes)) { printf("ERROR: Invalid Ed25519 key pair - keys failed cryptographic validation\n"); OPENSSL_cleanse(privKeyBytes, 32); return false; } // Verify public key derivation is consistent unsigned char derivedPubKey[32]; if (!DerivePublicKeyFromPrivate(privKeyBytes, derivedPubKey)) { printf("ERROR: Failed to derive public key from private key\n"); OPENSSL_cleanse(privKeyBytes, 32); return false; } if (memcmp(pubKeyBytes, derivedPubKey, 32) != 0) { printf("ERROR: Public key derivation inconsistency detected\n"); OPENSSL_cleanse(privKeyBytes, 32); return false; } // Store the validated keys privateKey = privKey; publicKey = HexStr(pubKeyBytes, pubKeyBytes + 32); // Generate and validate onion address from public key if (!GenerateV3OnionAddress(pubKeyBytes, onionAddress)) { printf("ERROR: Failed to generate onion address from public key\n"); privateKey.clear(); publicKey.clear(); OPENSSL_cleanse(privKeyBytes, 32); return false; } // Validate the generated address if (!ValidateOnionAddress(onionAddress)) { printf("ERROR: Generated onion address failed validation\n"); privateKey.clear(); publicKey.clear(); onionAddress.clear(); OPENSSL_cleanse(privKeyBytes, 32); return false; } // Verify address can be reconstructed from the same public key std::string reconstructedAddress = GenerateAddressFromPublicKey(pubKeyBytes); if (reconstructedAddress != onionAddress) { printf("ERROR: Address reconstruction inconsistency\n"); privateKey.clear(); publicKey.clear(); onionAddress.clear(); OPENSSL_cleanse(privKeyBytes, 32); return false; } // Clean up sensitive data OPENSSL_cleanse(privKeyBytes, 32); printf("Successfully loaded V3 onion service from private key: %s\n", onionAddress.c_str()); printf(" Private key length: %zu characters\n", privateKey.length()); printf(" Public key: %s\n", publicKey.substr(0, 16).c_str()); printf(" Onion address: %s\n", onionAddress.c_str()); return true; } catch (const std::exception& e) { printf("ERROR: Exception in LoadFromPrivateKey: %s\n", e.what()); // Clean up on exception privateKey.clear(); publicKey.clear(); onionAddress.clear(); isActive = false; OPENSSL_cleanse(privKeyBytes, 32); return false; } } bool CTorV3Service::StartOnionService() { if (!torV3Config.enableTor || !torV3Config.enableHiddenService) { printf("ERROR: Tor hidden service backend is disabled\n"); return false; } return AttachToBackendService(torV3Config.torDataDirectory, port); } bool CTorV3Service::AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds) { if (servicePort <= 0 || servicePort > 65535) { printf("ERROR: Invalid backend hidden service port: %d\n", servicePort); return false; } if (torDataDir.empty()) { printf("ERROR: Tor data directory is empty\n"); return false; } port = servicePort; const boost::filesystem::path serviceDir = GetBackendHiddenServiceDir(torDataDir); const boost::filesystem::path hostnamePath = serviceDir / "hostname"; std::string backendOnion; for (int waited = 0; waited <= waitSeconds; ++waited) { if (boost::filesystem::exists(hostnamePath) && ReadTrimmedFirstLine(hostnamePath, backendOnion)) { break; } if (waited == waitSeconds) { printf("ERROR: Timed out waiting for Tor hidden service hostname at %s\n", hostnamePath.string().c_str()); return false; } if (fShutdown) { printf("ERROR: Shutdown requested while waiting for Tor hidden service hostname\n"); return false; } MilliSleep(1000); } if (!ValidateOnionAddress(backendOnion)) { printf("ERROR: Tor backend produced invalid onion address: %s\n", backendOnion.c_str()); return false; } if (!onionAddress.empty() && onionAddress != backendOnion) { printf("WARNING: Replacing wallet-managed onion address %s with Tor backend address %s\n", onionAddress.c_str(), backendOnion.c_str()); } onionAddress = backendOnion; isActive = true; if (pwalletMain) { CWalletDB walletdb(pwalletMain->strWalletFile); walletdb.WriteSetting("tor_v3_onion_address", onionAddress); // Back up the Tor-generated secret key to wallet.dat so the onion // identity survives deletion of the tor_data directory. boost::filesystem::path secretKeyPath = serviceDir / "hs_ed25519_secret_key"; if (boost::filesystem::exists(secretKeyPath)) { std::ifstream keyFile(secretKeyPath.string().c_str(), std::ios::binary); if (keyFile.is_open()) { std::vector keyData( (std::istreambuf_iterator(keyFile)), std::istreambuf_iterator()); keyFile.close(); if (keyData.size() == 96) { walletdb.WriteSetting("tor_v3_hs_secret_key_backup", keyData); printf("Backed up Tor hidden service secret key to wallet (%d bytes)\n", (int)keyData.size()); } else { printf("WARNING: hs_ed25519_secret_key has unexpected size %d (expected 96), not backing up\n", (int)keyData.size()); } OPENSSL_cleanse(keyData.data(), keyData.size()); } } } printf("Attached V3 onion service to Tor backend at %s:%d\n", onionAddress.c_str(), port); return true; } bool CTorV3Service::StopService() { isActive = false; printf("Stopped V3 onion service\n"); return true; } bool CTorV3Service::SaveToWallet() { if (!pwalletMain) return false; // Use encrypted storage if wallet is encrypted if (pwalletMain->IsCrypted()) { return SaveEncryptedToWallet(); } // Save to wallet database (unencrypted) CWalletDB walletdb(pwalletMain->strWalletFile); return walletdb.WriteSetting("tor_v3_private_key", privateKey) && walletdb.WriteSetting("tor_v3_onion_address", onionAddress) && walletdb.WriteSetting("tor_v3_key_encrypted", false); } bool CTorV3Service::LoadFromWallet() { if (!pwalletMain) return false; // Try encrypted storage first if wallet is encrypted if (pwalletMain->IsCrypted()) { if (LoadEncryptedFromWallet()) { return true; } } // Try unencrypted storage CWalletDB walletdb(pwalletMain->strWalletFile); if (walletdb.ReadSetting("tor_v3_private_key", privateKey) && walletdb.ReadSetting("tor_v3_onion_address", onionAddress)) { // Derive public key from private key unsigned char privKeyBytes[32]; unsigned char pubKeyBytes[32]; if (ExtractKeysFromHex(privateKey, privKeyBytes, pubKeyBytes)) { publicKey = HexStr(pubKeyBytes, pubKeyBytes + 32); OPENSSL_cleanse(privKeyBytes, 32); return true; } } return false; } // Save encrypted Tor keys to wallet bool CTorV3Service::SaveEncryptedToWallet() { if (!pwalletMain) { printf("ERROR: No wallet available for encrypted Tor key storage\n"); return false; } // Check if wallet is encrypted if (!pwalletMain->IsCrypted()) { printf("WARNING: Wallet not encrypted, falling back to unencrypted storage\n"); return SaveToWallet(); } // Check if wallet is locked if (pwalletMain->IsLocked()) { printf("ERROR: Wallet is locked, cannot encrypt Tor keys\n"); return false; } // Encrypt the private key using a simple approach std::vector vchCryptedPrivateKey; if (!EncryptTorKeys(vchCryptedPrivateKey)) { printf("ERROR: Failed to encrypt Tor private key\n"); return false; } // Save encrypted data to wallet CWalletDB walletdb(pwalletMain->strWalletFile); bool success = walletdb.WriteSetting("tor_v3_encrypted_private_key", vchCryptedPrivateKey) && walletdb.WriteSetting("tor_v3_onion_address", onionAddress) && walletdb.WriteSetting("tor_v3_key_encrypted", true); if (success) { // Remove unencrypted key if it exists walletdb.WriteSetting("tor_v3_private_key", std::string("")); printf("Tor v3 private key encrypted and saved to wallet\n"); } return success; } // Load encrypted Tor keys from wallet bool CTorV3Service::LoadEncryptedFromWallet() { if (!pwalletMain) { printf("ERROR: No wallet available for encrypted Tor key loading\n"); return false; } CWalletDB walletdb(pwalletMain->strWalletFile); // Check if we have encrypted keys bool isEncrypted = false; if (!walletdb.ReadSetting("tor_v3_key_encrypted", isEncrypted) || !isEncrypted) { // Try loading unencrypted keys return LoadFromWallet(); } // Check if wallet is encrypted and unlocked if (!pwalletMain->IsCrypted()) { printf("ERROR: Tor keys are encrypted but wallet is not encrypted\n"); return false; } // Check if wallet is locked if (pwalletMain->IsLocked()) { printf("ERROR: Wallet is locked, cannot decrypt Tor keys\n"); return false; } // Load encrypted private key std::vector vchCryptedPrivateKey; if (!walletdb.ReadSetting("tor_v3_encrypted_private_key", vchCryptedPrivateKey)) { printf("ERROR: Failed to load encrypted Tor private key from wallet\n"); return false; } // Decrypt the private key if (!DecryptTorKeys(vchCryptedPrivateKey, privateKey)) { printf("ERROR: Failed to decrypt Tor private key\n"); return false; } // Load onion address if (!walletdb.ReadSetting("tor_v3_onion_address", onionAddress)) { printf("ERROR: Failed to load Tor onion address from wallet\n"); return false; } // Derive public key from decrypted private key unsigned char privKeyBytes[32]; unsigned char pubKeyBytes[32]; if (ExtractKeysFromHex(privateKey, privKeyBytes, pubKeyBytes)) { publicKey = HexStr(pubKeyBytes, pubKeyBytes + 32); OPENSSL_cleanse(privKeyBytes, 32); printf("Tor v3 keys decrypted and loaded from wallet: %s\n", onionAddress.c_str()); return true; } else { printf("ERROR: Failed to derive public key from decrypted private key\n"); privateKey.clear(); return false; } } // Encrypt Tor keys using a simplified approach bool CTorV3Service::EncryptTorKeys(std::vector& vchCryptedPrivateKey) { if (privateKey.empty()) { printf("ERROR: No private key to encrypt\n"); return false; } // For now, use a simple XOR encryption with the onion address hash // This provides basic obfuscation while maintaining compatibility // In a production system, this should use proper AES encryption // Convert hex private key to bytes if (privateKey.length() != 64) { printf("ERROR: Invalid private key format for encryption\n"); return false; } std::vector vchPrivateKey(32); for (int i = 0; i < 32; i++) { std::string byteString = privateKey.substr(i * 2, 2); vchPrivateKey[i] = (unsigned char)strtol(byteString.c_str(), nullptr, 16); } // Create encryption key from onion address hash unsigned char hash[32]; SHA256((const unsigned char*)onionAddress.c_str(), onionAddress.length(), hash); // Simple XOR encryption (for demonstration - use proper AES in production) vchCryptedPrivateKey.resize(32); for (int i = 0; i < 32; i++) { vchCryptedPrivateKey[i] = vchPrivateKey[i] ^ hash[i]; } // Clear sensitive data OPENSSL_cleanse(vchPrivateKey.data(), vchPrivateKey.size()); return true; } // Decrypt Tor keys using simplified approach bool CTorV3Service::DecryptTorKeys(const std::vector& vchCryptedPrivateKey, std::string& decryptedPrivateKey) { if (vchCryptedPrivateKey.empty() || vchCryptedPrivateKey.size() != 32) { printf("ERROR: Invalid encrypted private key\n"); return false; } // Create decryption key from onion address hash unsigned char hash[32]; SHA256((const unsigned char*)onionAddress.c_str(), onionAddress.length(), hash); // Simple XOR decryption (matches encryption) std::vector vchPrivateKey(32); for (int i = 0; i < 32; i++) { vchPrivateKey[i] = vchCryptedPrivateKey[i] ^ hash[i]; } // Convert bytes to hex string decryptedPrivateKey = HexStr(vchPrivateKey.begin(), vchPrivateKey.end()); // Clear sensitive data OPENSSL_cleanse(vchPrivateKey.data(), vchPrivateKey.size()); return true; } // Ed25519 key validation function bool CTorV3Service::ValidateEd25519Keys(const unsigned char* privateKey, const unsigned char* publicKey) { if (!privateKey || !publicKey) { return false; } // Check that keys are not all zeros bool privKeyAllZeros = true; bool pubKeyAllZeros = true; for (int i = 0; i < 32; i++) { if (privateKey[i] != 0) privKeyAllZeros = false; if (publicKey[i] != 0) pubKeyAllZeros = false; } if (privKeyAllZeros || pubKeyAllZeros) { return false; } // Verify that the public key can be derived from the private key unsigned char derivedPubKey[32]; if (!DerivePublicKeyFromPrivate(privateKey, derivedPubKey)) { return false; } // Compare derived public key with provided public key return memcmp(publicKey, derivedPubKey, 32) == 0; } // Generate proper v3 onion address with SHA3-256 checksum bool CTorV3Service::GenerateV3OnionAddress(const unsigned char* publicKey, std::string& address) { if (!publicKey) { return false; } // V3 onion address format: base32(pubkey || checksum || version) + ".onion" // Where checksum = SHA3-256(".onion checksum" || pubkey || version)[:2] const char* checksum_prefix = ".onion checksum"; const unsigned char version = 0x03; // Prepare data for checksum calculation std::vector checksum_data; checksum_data.insert(checksum_data.end(), checksum_prefix, checksum_prefix + strlen(checksum_prefix)); checksum_data.insert(checksum_data.end(), publicKey, publicKey + 32); checksum_data.push_back(version); // Calculate SHA3-256 checksum unsigned char hash[32]; if (!SHA3_256_compat(checksum_data.data(), checksum_data.size(), hash)) { return false; } // Prepare address bytes: pubkey (32) + checksum (2) + version (1) unsigned char addressBytes[35]; memcpy(addressBytes, publicKey, 32); memcpy(addressBytes + 32, hash, 2); // First 2 bytes of hash as checksum addressBytes[34] = version; // Encode to base32 address = EncodeBase32Proper(addressBytes, 35) + ".onion"; return true; } // Validate onion address format and checksum bool CTorV3Service::ValidateOnionAddress(const std::string& address) { // V3 onion addresses should be 56 characters + ".onion" = 62 characters total if (address.length() != 62) { return false; } // Check .onion suffix if (address.substr(56) != ".onion") { return false; } // Extract base32 part std::string base32part = address.substr(0, 56); // Decode base32 std::vector decoded; if (!DecodeBase32(base32part, decoded)) { return false; } // Should decode to exactly 35 bytes (32 pubkey + 2 checksum + 1 version) if (decoded.size() != 35) { return false; } // Extract components unsigned char publicKey[32]; unsigned char checksum[2]; unsigned char version; memcpy(publicKey, decoded.data(), 32); memcpy(checksum, decoded.data() + 32, 2); version = decoded[34]; // Verify version if (version != 0x03) { return false; } // Verify checksum const char* checksum_prefix = ".onion checksum"; std::vector checksum_data; checksum_data.insert(checksum_data.end(), checksum_prefix, checksum_prefix + strlen(checksum_prefix)); checksum_data.insert(checksum_data.end(), publicKey, publicKey + 32); checksum_data.push_back(version); // Calculate expected checksum unsigned char expected_hash[32]; if (!SHA3_256_compat(checksum_data.data(), checksum_data.size(), expected_hash)) { return false; } // Compare first 2 bytes of hash with provided checksum return (memcmp(checksum, expected_hash, 2) == 0); } // Enhanced key extraction from hex strings with comprehensive validation bool CTorV3Service::ExtractKeysFromHex(const std::string& privKeyHex, unsigned char* privKey, unsigned char* pubKey) { if (!privKey || !pubKey) { printf("ERROR: NULL pointers passed to ExtractKeysFromHex\n"); return false; } if (privKeyHex.length() != 64) { // 32 bytes = 64 hex chars printf("ERROR: Invalid private key hex length: %zu (expected 64)\n", privKeyHex.length()); return false; } // Validate hex format for (size_t i = 0; i < privKeyHex.length(); i++) { char c = privKeyHex[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { printf("ERROR: Invalid hex character at position %zu: %c\n", i, c); return false; } } try { // Convert hex string to bytes with error checking for (int i = 0; i < 32; i++) { std::string byteString = privKeyHex.substr(i * 2, 2); char* endPtr = nullptr; long byteValue = strtol(byteString.c_str(), &endPtr, 16); // Check for conversion errors if (endPtr != byteString.c_str() + 2 || byteValue < 0 || byteValue > 255) { printf("ERROR: Invalid hex byte at position %d: %s\n", i, byteString.c_str()); return false; } privKey[i] = (unsigned char)byteValue; } // Validate private key is not all zeros bool allZeros = true; for (int i = 0; i < 32; i++) { if (privKey[i] != 0) { allZeros = false; break; } } if (allZeros) { printf("ERROR: Private key cannot be all zeros\n"); return false; } // Derive public key from private key with enhanced validation if (!DerivePublicKeyFromPrivate(privKey, pubKey)) { printf("ERROR: Failed to derive public key from private key\n"); return false; } return true; } catch (const std::exception& e) { printf("ERROR: Exception in ExtractKeysFromHex: %s\n", e.what()); return false; } } // Enhanced public key derivation from private key using Ed25519 bool CTorV3Service::DerivePublicKeyFromPrivate(const unsigned char* privateKey, unsigned char* publicKey) { if (!privateKey || !publicKey) { printf("ERROR: NULL pointer passed to DerivePublicKeyFromPrivate\n"); return false; } // Validate private key is not all zeros bool allZeros = true; for (int i = 0; i < 32; i++) { if (privateKey[i] != 0) { allZeros = false; break; } } if (allZeros) { printf("ERROR: Private key cannot be all zeros\n"); return false; } EVP_PKEY* pkey = nullptr; try { // Create EVP_PKEY from raw private key pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, privateKey, 32); if (!pkey) { printf("ERROR: Failed to create EVP_PKEY from private key\n"); return false; } // Verify the key is valid EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr); if (!ctx) { printf("ERROR: Failed to create EVP_PKEY_CTX for validation\n"); EVP_PKEY_free(pkey); return false; } // Check if the key is valid (this will fail for invalid Ed25519 keys) int check_result = EVP_PKEY_check(ctx); EVP_PKEY_CTX_free(ctx); if (check_result != 1) { printf("ERROR: Private key failed cryptographic validation\n"); EVP_PKEY_free(pkey); return false; } // Extract public key size_t pubKeyLen = 32; int result = EVP_PKEY_get_raw_public_key(pkey, publicKey, &pubKeyLen); if (result <= 0) { printf("ERROR: Failed to extract public key (result: %d)\n", result); EVP_PKEY_free(pkey); return false; } if (pubKeyLen != 32) { printf("ERROR: Invalid public key length: %zu (expected 32)\n", pubKeyLen); EVP_PKEY_free(pkey); return false; } // Validate derived public key is not all zeros bool pubKeyAllZeros = true; for (int i = 0; i < 32; i++) { if (publicKey[i] != 0) { pubKeyAllZeros = false; break; } } if (pubKeyAllZeros) { printf("ERROR: Derived public key is all zeros\n"); EVP_PKEY_free(pkey); return false; } EVP_PKEY_free(pkey); return true; } catch (const std::exception& e) { printf("ERROR: Exception in DerivePublicKeyFromPrivate: %s\n", e.what()); if (pkey) EVP_PKEY_free(pkey); return false; } catch (...) { printf("ERROR: Unknown exception in DerivePublicKeyFromPrivate\n"); if (pkey) EVP_PKEY_free(pkey); return false; } } // Extract public key from onion address bool CTorV3Service::ExtractPublicKeyFromAddress(const std::string& address, unsigned char* publicKey) { if (!publicKey || !ValidateOnionAddress(address)) { return false; } // Extract base32 part std::string base32part = address.substr(0, 56); // Decode base32 std::vector decoded; if (!DecodeBase32(base32part, decoded) || decoded.size() != 35) { return false; } // Extract public key (first 32 bytes) memcpy(publicKey, decoded.data(), 32); return true; } // Verify address checksum independently bool CTorV3Service::VerifyAddressChecksum(const std::string& address) { return ValidateOnionAddress(address); } // Generate address from public key (utility wrapper) std::string CTorV3Service::GenerateAddressFromPublicKey(const unsigned char* publicKey) { std::string address; if (GenerateV3OnionAddress(publicKey, address)) { return address; } return ""; } // CTorV3Manager Implementation CTorV3Manager* CTorV3Manager::GetInstance() { if (!instance) { instance = new CTorV3Manager(); } return instance; } CTorV3Manager::CTorV3Manager() : torEnabled(false), torDataDir("tor_data") { // Config is loaded via LoadTorV3Config() before InitTorV3() is called } CTorV3Manager::~CTorV3Manager() { ShutdownTor(); } bool CTorV3Manager::InitializeTor() { printf("Initializing Tor V3 support...\n"); // Use configured data directory (set by init.cpp to GetDataDir()/tor_data) torDataDir = torV3Config.torDataDirectory; // Create tor data directory boost::filesystem::create_directories(torDataDir); torEnabled = true; if (torV3Config.enableHiddenService) { return CreateWalletHiddenService(torV3Config.hiddenServicePort); } return true; } void CTorV3Manager::ShutdownTor() { for (auto& service : services) { if (service.second) { service.second->StopService(); delete service.second; } } services.clear(); torEnabled = false; printf("Tor V3 services shutdown\n"); } bool CTorV3Manager::CreateWalletHiddenService(int port) { CTorV3Service* service = new CTorV3Service(); if (service->AttachToBackendService(torDataDir, port)) { services[port] = service; printf("Wallet hidden service attached: %s\n", service->GetOnionAddress().c_str()); // If seeder mode is enabled, automatically register as seeder if (torV3Config.enableSeederMode) { RegisterAsSeederNode(service->GetOnionAddress(), port); } return true; } delete service; return false; } bool CTorV3Manager::RegisterAsSeederNode(const std::string& onionAddress, int port) { // Enhanced input validation if (onionAddress.empty()) { printf("ERROR: Cannot register empty onion address as seeder\n"); return false; } if (!CTorV3Service::ValidateOnionAddress(onionAddress)) { printf("ERROR: Invalid onion address format for seeder registration: %s\n", onionAddress.c_str()); return false; } if (port <= 0 || port > 65535) { printf("ERROR: Invalid port for seeder registration: %d (must be 1-65535)\n", port); return false; } printf("Registering as seeder node: %s:%d\n", onionAddress.c_str(), port); // Create seeder entry std::string seederEntry = onionAddress + ":" + std::to_string(port); // Save to wallet database with enhanced metadata if (pwalletMain) { CWalletDB walletdb(pwalletMain->strWalletFile); // Save primary seeder information if (!walletdb.WriteSetting("seeder_onion_address", onionAddress) || !walletdb.WriteSetting("seeder_port", port) || !walletdb.WriteSetting("seeder_active", true)) { printf("ERROR: Failed to save seeder information to wallet\n"); return false; } // Save registration timestamp int64_t registrationTime = GetTime(); walletdb.WriteSetting("seeder_registration_time", registrationTime); // Initialize seeder statistics walletdb.WriteSetting("seeder_connections_served", 0); walletdb.WriteSetting("seeder_last_announcement", registrationTime); // Add ourselves to the known seeders list std::string knownSeeders; if (walletdb.ReadSetting("known_seeders", knownSeeders)) { // Check if we're already in the list if (knownSeeders.find(seederEntry) == std::string::npos) { if (!knownSeeders.empty()) { knownSeeders += ","; } knownSeeders += seederEntry; walletdb.WriteSetting("known_seeders", knownSeeders); } } else { // First seeder entry walletdb.WriteSetting("known_seeders", seederEntry); } // Set high reputation for ourselves std::string reputationKey = "seeder_reputation_" + seederEntry; walletdb.WriteSetting(reputationKey, (uint32_t)100); // Set last seen to now std::string lastSeenKey = "seeder_last_seen_" + seederEntry; walletdb.WriteSetting(lastSeenKey, registrationTime); } // Broadcast seeder announcement to network int announcementsSent = 0; { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { try { // Send seeder announcement to connected peers pnode->PushMessage("seeder", onionAddress, port); announcementsSent++; } catch (const std::exception& e) { printf("WARNING: Failed to send seeder announcement to peer %s: %s\n", pnode->addr.ToString().c_str(), e.what()); } } } printf("Successfully registered as seeder node and announced to %d peers\n", announcementsSent); // Schedule periodic re-announcements ScheduleSeederReannouncement(); return true; } void CTorV3Manager::SetTorEnabled(bool enabled) { torEnabled = enabled; torV3Config.enableTor = enabled; SaveTorV3Config(); if (enabled) { InitializeTor(); } else { ShutdownTor(); } } std::string CTorV3Manager::GetWalletOnionAddress() { auto it = services.find(torV3Config.hiddenServicePort); if (it != services.end() && it->second) { return it->second->GetOnionAddress(); } return ""; } bool CTorV3Manager::ConnectToOnionPeer(const std::string& onionAddr, int port) { if (!torEnabled) { printf("ERROR: Tor not enabled, cannot connect to onion peer\n"); return false; } // Validate onion address format if (!CTorV3Service::ValidateOnionAddress(onionAddr)) { printf("ERROR: Invalid onion address format: %s\n", onionAddr.c_str()); return false; } printf("Connecting to onion peer: %s:%d\n", onionAddr.c_str(), port); // Check if already connected to this peer { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { std::string nodeAddr = pnode->addr.ToString(); if (nodeAddr.find(onionAddr) != std::string::npos) { printf("Already connected to onion peer %s\n", onionAddr.c_str()); return true; } } } // Check connection limits if (GetOnionPeers().size() >= (size_t)torV3Config.maxConnections) { printf("WARNING: Maximum Tor connections reached (%d)\n", torV3Config.maxConnections); return false; } try { // Create service address for onion peer CService service; if (!Lookup(onionAddr.c_str(), service, port, false)) { printf("ERROR: Failed to resolve onion address: %s\n", onionAddr.c_str()); return false; } // Create address object CAddress addr(service); addr.nTime = GetTime(); // Add to address manager for future connections CNetAddr sourceAddr("127.0.0.1"); addrman.Add(addr, sourceAddr); // Attempt connection through SOCKS5 proxy if (ConnectThroughSocks5Proxy(onionAddr, port)) { printf("Successfully connected to onion peer: %s:%d\n", onionAddr.c_str(), port); // Update peer reputation if tracking UpdatePeerReputation(onionAddr, true); return true; } else { printf("ERROR: Failed to connect through SOCKS5 proxy to %s:%d\n", onionAddr.c_str(), port); UpdatePeerReputation(onionAddr, false); return false; } } catch (const std::exception& e) { printf("ERROR: Exception connecting to onion peer %s:%d - %s\n", onionAddr.c_str(), port, e.what()); return false; } } std::vector CTorV3Manager::GetOnionPeers() { std::vector onionPeers; if (!torEnabled) { return onionPeers; } try { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { std::string addr = pnode->addr.ToString(); if (addr.find(".onion") != std::string::npos) { // Include connection status and timing information std::string peerInfo = addr; // Add connection quality indicators if (pnode->fSuccessfullyConnected) { peerInfo += " (connected)"; } else { peerInfo += " (connecting)"; } // Add last activity time int64_t lastActivity = GetTime() - pnode->nLastRecv; if (lastActivity < 60) { peerInfo += " [active]"; } else if (lastActivity < 300) { peerInfo += " [recent]"; } else { peerInfo += " [idle]"; } onionPeers.push_back(peerInfo); } } } catch (const std::exception& e) { printf("ERROR: Exception in GetOnionPeers: %s\n", e.what()); } printf("Current onion peers: %d connected\n", (int)onionPeers.size()); return onionPeers; } bool CTorV3Manager::DiscoverOnionPeers() { if (!torEnabled) { printf("ERROR: Tor not enabled, cannot discover onion peers\n"); return false; } printf("Starting automatic onion peer discovery...\n"); try { // 1. Get list of known seeder nodes from network std::vector seederNodes = GetKnownSeederNodes(); printf("Found %d known seeder nodes\n", (int)seederNodes.size()); // 2. Connect to available seeders with connection limits int connected = 0; int maxSeeders = std::min((int)seederNodes.size(), torV3Config.maxConnections / 2); for (int i = 0; i < maxSeeders && i < (int)seederNodes.size(); i++) { const std::string& seeder = seederNodes[i]; // Add connection retry logic with exponential backoff if (ConnectToSeederNodeWithRetry(seeder, 3)) { connected++; // Small delay between connections to avoid overwhelming MilliSleep(100); } } printf("Successfully connected to %d seeder nodes\n", connected); // 3. Request seeder lists from connected peers if (connected > 0) { RequestSeederListFromPeers(); // Wait a bit for responses MilliSleep(2000); // 4. Attempt to connect to newly discovered peers DiscoverAdditionalPeers(); } // 5. Update peer discovery statistics UpdateDiscoveryStats(connected, seederNodes.size()); return connected > 0; } catch (const std::exception& e) { printf("ERROR: Exception in DiscoverOnionPeers: %s\n", e.what()); return false; } } std::vector CTorV3Manager::GetKnownSeederNodes() { std::vector seeders; std::set uniqueSeeders; // Prevent duplicates // Load from wallet database with enhanced error handling if (pwalletMain) { CWalletDB walletdb(pwalletMain->strWalletFile); // Get stored seeder list std::string seederList; if (walletdb.ReadSetting("known_seeders", seederList)) { // Parse comma-separated list with validation std::stringstream ss(seederList); std::string seeder; while (std::getline(ss, seeder, ',')) { if (!seeder.empty()) { // Validate seeder format (address:port) size_t colonPos = seeder.find(':'); if (colonPos != std::string::npos) { std::string onionAddr = seeder.substr(0, colonPos); std::string portStr = seeder.substr(colonPos + 1); // Validate onion address format if (CTorV3Service::ValidateOnionAddress(onionAddr)) { // Validate port number try { int port = std::stoi(portStr); if (port > 0 && port <= 65535) { uniqueSeeders.insert(seeder); } } catch (const std::exception& e) { printf("WARNING: Invalid port in seeder entry: %s\n", seeder.c_str()); } } else { printf("WARNING: Invalid onion address in seeder entry: %s\n", seeder.c_str()); } } else { printf("WARNING: Invalid seeder format (missing port): %s\n", seeder.c_str()); } } } } // Load individual seeder reputation data for filtering for (const std::string& seeder : uniqueSeeders) { int64_t lastSeen = 0; uint32_t reputation = 0; std::string lastSeenKey = "seeder_last_seen_" + seeder; std::string reputationKey = "seeder_reputation_" + seeder; walletdb.ReadSetting(lastSeenKey, lastSeen); walletdb.ReadSetting(reputationKey, reputation); // Only include seeders seen within the last 7 days and with good reputation int64_t currentTime = GetTime(); int64_t sevenDaysAgo = currentTime - (7 * 24 * 60 * 60); if (lastSeen > sevenDaysAgo && reputation >= 50) { // Minimum reputation threshold seeders.push_back(seeder); } } } // Add hardcoded bootstrap seeders (for initial network bootstrap) std::vector bootstrapSeeders = { "gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion:24112", "futmtrvh6j34t7s6yjdxfia6iwuyfzwh4k5eqfof5kfhoqk3xmi3qoqd.onion:24112", }; // Add bootstrap seeders if we don't have enough known seeders if (seeders.size() < 3) { for (const std::string& bootstrapSeeder : bootstrapSeeders) { if (uniqueSeeders.find(bootstrapSeeder) == uniqueSeeders.end()) { seeders.push_back(bootstrapSeeder); } } } printf("Found %d known seeder nodes (filtered by reputation and recency)\n", (int)seeders.size()); return seeders; } bool CTorV3Manager::ConnectToSeederNode(const std::string& seederAddress) { // Enhanced input validation if (seederAddress.empty()) { printf("ERROR: Empty seeder address provided\n"); return false; } // Parse address:port with enhanced validation size_t colonPos = seederAddress.find(':'); if (colonPos == std::string::npos) { printf("ERROR: Invalid seeder address format (missing port): %s\n", seederAddress.c_str()); return false; } std::string onionAddr = seederAddress.substr(0, colonPos); std::string portStr = seederAddress.substr(colonPos + 1); // Validate onion address format if (!CTorV3Service::ValidateOnionAddress(onionAddr)) { printf("ERROR: Invalid onion address format: %s\n", onionAddr.c_str()); return false; } // Validate and parse port int port; try { port = std::stoi(portStr); if (port <= 0 || port > 65535) { printf("ERROR: Invalid port number: %d (must be 1-65535)\n", port); return false; } } catch (const std::exception& e) { printf("ERROR: Failed to parse port from seeder address: %s\n", seederAddress.c_str()); return false; } printf("Connecting to seeder: %s:%d\n", onionAddr.c_str(), port); // Check if already connected { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { if (pnode->addr.ToString().find(onionAddr) != std::string::npos) { printf("Already connected to seeder %s\n", onionAddr.c_str()); // Update last seen time for this seeder UpdateSeederLastSeen(seederAddress); return true; } } } // Check connection limits std::vector currentOnionPeers = GetOnionPeers(); if (currentOnionPeers.size() >= (size_t)torV3Config.maxConnections) { printf("WARNING: Maximum onion connections reached (%d), cannot connect to seeder\n", torV3Config.maxConnections); return false; } // Attempt connection through SOCKS5 proxy if configured bool connected = false; if (!torV3Config.socksProxy.empty()) { printf("Connecting to seeder through SOCKS5 proxy: %s\n", torV3Config.socksProxy.c_str()); connected = ConnectThroughSocks5Proxy(onionAddr, port); } else { printf("WARNING: No SOCKS5 proxy configured, attempting direct connection\n"); connected = ConnectToOnionPeer(onionAddr, port); } if (connected) { printf("Successfully connected to seeder: %s:%d\n", onionAddr.c_str(), port); // Update seeder statistics UpdateSeederLastSeen(seederAddress); UpdateSeederReputation(seederAddress, true); // Request seeder list from the newly connected seeder RequestSeederListFromPeer(seederAddress); return true; } else { printf("Failed to connect to seeder: %s:%d\n", onionAddr.c_str(), port); // Update reputation negatively for failed connection UpdateSeederReputation(seederAddress, false); return false; } } void CTorV3Manager::HandleSeederListMessage(CNode* pfrom, const std::vector& seederList) { printf("Received seeder list from %s with %d entries\n", pfrom->addr.ToString().c_str(), (int)seederList.size()); // Add new seeders to our known list std::set newSeeders; if (pwalletMain) { CWalletDB walletdb(pwalletMain->strWalletFile); // Load existing known seeders std::string existingSeeders; if (walletdb.ReadSetting("known_seeders", existingSeeders)) { std::set existingSet; std::stringstream ss(existingSeeders); std::string seeder; while (std::getline(ss, seeder, ',')) { if (!seeder.empty()) { existingSet.insert(seeder); } } // Add new seeders for (const std::string& newSeeder : seederList) { if (CTorV3Service::ValidateOnionAddress(newSeeder.substr(0, newSeeder.find(':')))) { existingSet.insert(newSeeder); newSeeders.insert(newSeeder); } } // Save updated list std::string updatedList; for (const std::string& seeder : existingSet) { if (!updatedList.empty()) updatedList += ","; updatedList += seeder; } walletdb.WriteSetting("known_seeders", updatedList); } } printf("Added %d new seeders to known list\n", (int)newSeeders.size()); // Attempt to connect to new seeders if we have capacity int currentConnections = GetOnionPeers().size(); int availableSlots = torV3Config.maxConnections - currentConnections; if (availableSlots > 0) { int connected = 0; for (const std::string& seeder : newSeeders) { if (connected >= availableSlots) break; if (ConnectToSeederNode(seeder)) { connected++; } } printf("Connected to %d new seeders\n", connected); } } // SOCKS5 proxy connection implementation bool CTorV3Manager::ConnectThroughSocks5Proxy(const std::string& onionAddr, int port) { if (torV3Config.socksProxy.empty()) { printf("ERROR: No SOCKS5 proxy configured for Tor connections\n"); return false; } // Parse proxy address size_t colonPos = torV3Config.socksProxy.find(':'); if (colonPos == std::string::npos) { printf("ERROR: Invalid SOCKS5 proxy format: %s\n", torV3Config.socksProxy.c_str()); return false; } std::string proxyHost = torV3Config.socksProxy.substr(0, colonPos); int proxyPort = std::stoi(torV3Config.socksProxy.substr(colonPos + 1)); printf("Connecting through SOCKS5 proxy %s:%d to %s:%d\n", proxyHost.c_str(), proxyPort, onionAddr.c_str(), port); try { // Create connection to SOCKS5 proxy SOCKET hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) { printf("ERROR: Failed to create socket for SOCKS5 connection\n"); return false; } // Set socket options int nOne = 1; if (setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&nOne, sizeof(int)) == SOCKET_ERROR) { printf("WARNING: Failed to set SO_REUSEADDR on SOCKS5 socket\n"); } // Connect to SOCKS5 proxy struct sockaddr_in proxyAddr; memset(&proxyAddr, 0, sizeof(proxyAddr)); proxyAddr.sin_family = AF_INET; proxyAddr.sin_port = htons(proxyPort); proxyAddr.sin_addr.s_addr = inet_addr(proxyHost.c_str()); if (proxyAddr.sin_addr.s_addr == INADDR_NONE) { printf("ERROR: Invalid SOCKS5 proxy IP address: %s\n", proxyHost.c_str()); closesocket(hSocket); return false; } if (connect(hSocket, (struct sockaddr*)&proxyAddr, sizeof(proxyAddr)) == SOCKET_ERROR) { printf("ERROR: Failed to connect to SOCKS5 proxy %s:%d\n", proxyHost.c_str(), proxyPort); closesocket(hSocket); return false; } // Perform SOCKS5 handshake if (!PerformSocks5Handshake(hSocket, onionAddr, port)) { printf("ERROR: SOCKS5 handshake failed for %s:%d\n", onionAddr.c_str(), port); closesocket(hSocket); return false; } // Create CNode for the connection CAddress addr(CService(onionAddr, port)); CNode* pnode = new CNode(hSocket, addr, "", false); if (pnode) { pnode->fNetworkNode = true; pnode->fSuccessfullyConnected = true; { LOCK(cs_vNodes); vNodes.push_back(pnode); } printf("Successfully established SOCKS5 connection to %s:%d\n", onionAddr.c_str(), port); return true; } else { printf("ERROR: Failed to create CNode for SOCKS5 connection\n"); closesocket(hSocket); return false; } } catch (const std::exception& e) { printf("ERROR: Exception in SOCKS5 connection: %s\n", e.what()); return false; } } // SOCKS5 handshake implementation bool CTorV3Manager::PerformSocks5Handshake(int hSocket, const std::string& onionAddr, int port) { try { // SOCKS5 authentication request (no authentication) unsigned char authRequest[3] = {0x05, 0x01, 0x00}; // Version 5, 1 method, no auth if (send(hSocket, (const char*)authRequest, 3, 0) != 3) { printf("ERROR: Failed to send SOCKS5 auth request\n"); return false; } // Read authentication response unsigned char authResponse[2]; if (recv(hSocket, (char*)authResponse, 2, 0) != 2) { printf("ERROR: Failed to receive SOCKS5 auth response\n"); return false; } if (authResponse[0] != 0x05 || authResponse[1] != 0x00) { printf("ERROR: SOCKS5 authentication failed: version=%d, method=%d\n", authResponse[0], authResponse[1]); return false; } // SOCKS5 connection request std::vector connectRequest; connectRequest.push_back(0x05); // Version connectRequest.push_back(0x01); // Connect command connectRequest.push_back(0x00); // Reserved connectRequest.push_back(0x03); // Domain name address type // Add domain name length and domain name connectRequest.push_back((unsigned char)onionAddr.length()); for (char c : onionAddr) { connectRequest.push_back((unsigned char)c); } // Add port (big-endian) connectRequest.push_back((port >> 8) & 0xFF); connectRequest.push_back(port & 0xFF); if (send(hSocket, (const char*)connectRequest.data(), connectRequest.size(), 0) != (int)connectRequest.size()) { printf("ERROR: Failed to send SOCKS5 connect request\n"); return false; } // Read connection response unsigned char connectResponse[10]; // Minimum response size int received = recv(hSocket, (char*)connectResponse, 10, 0); if (received < 10) { printf("ERROR: Failed to receive complete SOCKS5 connect response\n"); return false; } if (connectResponse[0] != 0x05) { printf("ERROR: Invalid SOCKS5 response version: %d\n", connectResponse[0]); return false; } if (connectResponse[1] != 0x00) { printf("ERROR: SOCKS5 connection failed with error code: %d\n", connectResponse[1]); return false; } printf("SOCKS5 handshake completed successfully\n"); return true; } catch (const std::exception& e) { printf("ERROR: Exception in SOCKS5 handshake: %s\n", e.what()); return false; } } // Connect to seeder with retry logic bool CTorV3Manager::ConnectToSeederNodeWithRetry(const std::string& seederAddress, int maxRetries) { for (int attempt = 1; attempt <= maxRetries; attempt++) { printf("Connecting to seeder %s (attempt %d/%d)\n", seederAddress.c_str(), attempt, maxRetries); if (ConnectToSeederNode(seederAddress)) { return true; } if (attempt < maxRetries) { // Exponential backoff: 1s, 2s, 4s, etc. int delayMs = 1000 * (1 << (attempt - 1)); printf("Connection failed, retrying in %dms...\n", delayMs); MilliSleep(delayMs); } } printf("Failed to connect to seeder %s after %d attempts\n", seederAddress.c_str(), maxRetries); return false; } // Discover additional peers from connected nodes void CTorV3Manager::DiscoverAdditionalPeers() { printf("Discovering additional peers from connected nodes...\n"); // Get current onion peers std::vector currentPeers = GetOnionPeers(); int availableSlots = torV3Config.maxConnections - currentPeers.size(); if (availableSlots <= 0) { printf("No available connection slots for additional peers\n"); return; } // Request peer lists from connected onion nodes { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { std::string addr = pnode->addr.ToString(); if (addr.find(".onion") != std::string::npos && pnode->fSuccessfullyConnected) { // Request additional peer information pnode->PushMessage("getaddr"); } } } printf("Requested peer information from %d connected onion nodes\n", (int)currentPeers.size()); } // Update peer reputation tracking void CTorV3Manager::UpdatePeerReputation(const std::string& onionAddr, bool success) { if (!pwalletMain) return; try { CWalletDB walletdb(pwalletMain->strWalletFile); std::string reputationKey = "peer_reputation_" + onionAddr; std::string lastSeenKey = "peer_last_seen_" + onionAddr; // Update reputation score int reputation = 0; walletdb.ReadSetting(reputationKey, reputation); if (success) { reputation = std::min(reputation + 10, 1000); // Cap at 1000 } else { reputation = std::max(reputation - 5, 0); // Floor at 0 } walletdb.WriteSetting(reputationKey, reputation); walletdb.WriteSetting(lastSeenKey, (int64_t)GetTime()); } catch (const std::exception& e) { printf("ERROR: Exception updating peer reputation: %s\n", e.what()); } } // Update discovery statistics void CTorV3Manager::UpdateDiscoveryStats(int connected, int attempted) { if (!pwalletMain) return; try { CWalletDB walletdb(pwalletMain->strWalletFile); walletdb.WriteSetting("discovery_last_run", (int64_t)GetTime()); walletdb.WriteSetting("discovery_connected", connected); walletdb.WriteSetting("discovery_attempted", attempted); // Update success rate int totalAttempts = 0; int totalConnected = 0; walletdb.ReadSetting("discovery_total_attempts", totalAttempts); walletdb.ReadSetting("discovery_total_connected", totalConnected); totalAttempts += attempted; totalConnected += connected; walletdb.WriteSetting("discovery_total_attempts", totalAttempts); walletdb.WriteSetting("discovery_total_connected", totalConnected); if (totalAttempts > 0) { double successRate = (double)totalConnected / totalAttempts * 100.0; printf("Peer discovery success rate: %.1f%% (%d/%d)\n", successRate, totalConnected, totalAttempts); } } catch (const std::exception& e) { printf("ERROR: Exception updating discovery stats: %s\n", e.what()); } } // Load Tor V3 configuration bool LoadTorV3Config() { // Tor V3 identity is innate to Triangles — enabled by default torV3Config.enableTor = !GetBoolArg("-notor", false); torV3Config.enableHiddenService = torV3Config.enableTor && GetBoolArg("-torhiddenservice", true); torV3Config.enableSeederMode = GetBoolArg("-torseeder", false); torV3Config.hiddenServicePort = GetArg("-torhsport", GetListenPort()); torV3Config.torDataDirectory = GetArg("-tordatadir", (GetDataDir() / "tor_data").string()); torV3Config.socksProxy = GetEffectiveTorProxy(); torV3Config.maxConnections = GetArg("-tormaxconnections", 8); printf("Loaded Tor V3 configuration: enabled=%s, hidden_service=%s, seeder=%s, proxy=%s\n", torV3Config.enableTor ? "true" : "false", torV3Config.enableHiddenService ? "true" : "false", torV3Config.enableSeederMode ? "true" : "false", torV3Config.socksProxy.c_str()); return true; } // Save Tor V3 configuration bool SaveTorV3Config() { // Configuration is typically saved through command line args or config file // This is a placeholder for future configuration persistence return true; } // Get Tor V3 configuration reference TorV3Config& GetTorV3Config() { return torV3Config; } void CTorV3Manager::RequestSeederListFromPeers() { printf("Requesting seeder lists from connected peers...\n"); LOCK(cs_vNodes); for (CNode* pnode : vNodes) { // Request seeder list from each connected peer pnode->PushMessage("getseederlist"); } } void CTorV3Manager::BroadcastSeederList() { if (!torV3Config.enableSeederMode) return; std::vector seederList = GetKnownSeederNodes(); LOCK(cs_vNodes); for (CNode* pnode : vNodes) { // Send our seeder list to requesting peers pnode->PushMessage("seederlist", seederList); } printf("Broadcasted seeder list with %d entries\n", (int)seederList.size()); } // Helper methods for enhanced seeder functionality // Update seeder last seen timestamp void CTorV3Manager::UpdateSeederLastSeen(const std::string& seederAddress) { if (!pwalletMain) return; try { CWalletDB walletdb(pwalletMain->strWalletFile); std::string lastSeenKey = "seeder_last_seen_" + seederAddress; walletdb.WriteSetting(lastSeenKey, (int64_t)GetTime()); } catch (const std::exception& e) { printf("ERROR: Failed to update seeder last seen time: %s\n", e.what()); } } // Update seeder reputation based on connection success/failure void CTorV3Manager::UpdateSeederReputation(const std::string& seederAddress, bool success) { if (!pwalletMain) return; try { CWalletDB walletdb(pwalletMain->strWalletFile); std::string reputationKey = "seeder_reputation_" + seederAddress; uint32_t currentReputation = 50; // Default starting reputation walletdb.ReadSetting(reputationKey, currentReputation); if (success) { // Increase reputation for successful connections (max 100) currentReputation = std::min((uint32_t)100, currentReputation + 5); } else { // Decrease reputation for failed connections (min 0) currentReputation = (currentReputation >= 10) ? currentReputation - 10 : 0; } walletdb.WriteSetting(reputationKey, currentReputation); printf("Updated seeder %s reputation to %d (success: %s)\n", seederAddress.c_str(), currentReputation, success ? "true" : "false"); } catch (const std::exception& e) { printf("ERROR: Failed to update seeder reputation: %s\n", e.what()); } } // Request seeder list from a specific peer void CTorV3Manager::RequestSeederListFromPeer(const std::string& peerAddress) { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { if (pnode->addr.ToString().find(peerAddress) != std::string::npos) { try { pnode->PushMessage("getseederlist"); printf("Requested seeder list from peer: %s\n", peerAddress.c_str()); return; } catch (const std::exception& e) { printf("ERROR: Failed to request seeder list from peer %s: %s\n", peerAddress.c_str(), e.what()); } } } printf("WARNING: Peer %s not found for seeder list request\n", peerAddress.c_str()); } // Schedule periodic seeder re-announcements void CTorV3Manager::ScheduleSeederReannouncement() { if (pwalletMain) { CWalletDB walletdb(pwalletMain->strWalletFile); walletdb.WriteSetting("seeder_last_announcement", (int64_t)GetTime()); } } // --------------------------------------------------------------------------- // Background thread: Tor health monitoring + seeder maintenance // --------------------------------------------------------------------------- void ThreadTorMaintenance(void* parg) { RenameThread("Triangles-tormaint"); printf("Tor maintenance thread started\n"); int restartBackoffSec = 30; int64_t lastSeederMaint = GetTime(); static const int SEEDER_INTERVAL = 1800; // 30 minutes while (!fShutdown) { // Sleep in short intervals so the thread exits promptly on shutdown for (int i = 0; i < 60 && !fShutdown; i++) MilliSleep(500); if (fShutdown) break; // --- Tor health check & auto-restart --- if (!CTorEmbedded::GetInstance()->IsRunning()) { printf("WARNING: Tor process is no longer running, attempting restart...\n"); if (StartEmbeddedTor()) { printf("Tor restarted successfully\n"); restartBackoffSec = 30; // Re-attach the hidden service identity TorV3Config& torConfig = GetTorV3Config(); std::string torDataPath = CTorEmbedded::GetInstance()->GetDataDir(); if (torDataPath.empty()) torDataPath = torConfig.torDataDirectory; torConfig.enableTor = true; torConfig.enableHiddenService = CTorEmbedded::GetInstance()->IsHiddenServiceEnabled(); torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort(); torConfig.torDataDirectory = torDataPath; if (torConfig.enableHiddenService && InitTorV3()) { std::string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress(); if (!onionAddr.empty()) { AddLocal(CService(onionAddr, torConfig.hiddenServicePort), LOCAL_MANUAL); printf("Re-registered Tor V3 identity after restart: %s\n", onionAddr.c_str()); } } } else { printf("WARNING: Tor restart failed, retrying in %d seconds\n", restartBackoffSec); for (int i = 0; i < restartBackoffSec * 2 && !fShutdown; i++) MilliSleep(500); if (restartBackoffSec < 300) restartBackoffSec *= 2; } continue; } // --- Seeder maintenance (every 30 minutes) --- TorV3Config& cfg = GetTorV3Config(); if (cfg.enableSeederMode && (GetTime() - lastSeederMaint) >= SEEDER_INTERVAL) { CTorV3Manager* mgr = CTorV3Manager::GetInstance(); std::string ownAddr = mgr->GetWalletOnionAddress(); if (!ownAddr.empty()) { // Re-announce ourselves as a seeder to all peers { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { try { pnode->PushMessage("seeder", ownAddr, cfg.hiddenServicePort); } catch (...) {} } } printf("Seeder re-announcement sent to %d peers\n", (int)vNodes.size()); } // Refresh our knowledge of other seeders mgr->RequestSeederListFromPeers(); mgr->ScheduleSeederReannouncement(); lastSeederMaint = GetTime(); } } printf("Tor maintenance thread exited\n"); } // Global functions bool InitTorV3() { return CTorV3Manager::GetInstance()->InitializeTor(); } void ShutdownTorV3() { CTorV3Manager::GetInstance()->ShutdownTor(); }