From 41d9ae4c7a6bae71a405f099960c821cc1bf1bab Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Sun, 8 Mar 2026 15:57:09 -0700 Subject: [PATCH] Updated Triangles project - new features and branding --- .claude/settings.local.json | 22 + src/alert.cpp | 12 +- src/checkpoints.cpp | 17 +- src/kernel.cpp | 9 +- src/main.cpp | 20 +- src/main.h | 1 + src/net_bootstrap.cpp | 256 ++++ src/net_bootstrap.h | 94 ++ src/netbase.cpp | 62 +- src/netbase.h | 7 + src/onionseed.h | 6 +- src/qt/forms/mainwindow.ui | 22 +- src/qt/res/images/header_logo.png | Bin 0 -> 36469 bytes src/qt/triangles.qrc | 1 + src/tor/onion_v3.cpp | 2161 +++++++++++++++++++++++++++++ src/tor/onion_v3.h | 174 +++ src/tor/tor_crypto_compat.h | 440 ++++++ src/version.h | 12 +- triangles-qt.pro | 37 +- 19 files changed, 3286 insertions(+), 67 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 src/net_bootstrap.cpp create mode 100644 src/net_bootstrap.h create mode 100644 src/qt/res/images/header_logo.png create mode 100644 src/tor/onion_v3.cpp create mode 100644 src/tor/onion_v3.h create mode 100644 src/tor/tor_crypto_compat.h diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..a0aaa34 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,22 @@ +{ + "permissions": { + "allow": [ + "Bash(git clone:*)", + "Bash(git init:*)", + "Bash(git remote add:*)", + "Bash(git fetch:*)", + "Bash(git checkout:*)", + "Bash(git config:*)", + "Bash(git -C \"E:\\\\repos\\\\triangles_old\" log --oneline --all)", + "Bash(git -C \"E:\\\\repos\\\\triangles_old\" branch -a)", + "Bash(git -C \"E:\\\\repos\\\\triangles_old\" log --oneline --all --graph)", + "Bash(git -C \"E:\\\\repos\\\\triangles_old\" show 7676e66 --stat)", + "Bash(python:*)", + "Bash(where:*)", + "Bash(powershell:*)", + "Bash(C:/msys64/usr/bin/bash.exe -lc \"pacman -Syu --noconfirm\")", + "Bash(C:/msys64/usr/bin/bash.exe -lc \"pacman -S --needed --noconfirm mingw-w64-x86_64-toolchain make\")", + "Bash(C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -here -c \"ls /mingw64/lib/libboost_system*.a 2>/dev/null; ls /mingw64/lib/cmake/boost_system* 2>/dev/null; ls /mingw64/lib/libboost*.a 2>/dev/null | head -10\")" + ] + } +} diff --git a/src/alert.cpp b/src/alert.cpp index 9bd5b07..0b1d350 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -19,10 +19,11 @@ using namespace std; map mapAlerts; CCriticalSection cs_mapAlerts; -static const char* pszMainKey = "a68b432b54018c4b1f653c2dbe0d3507b6b3939af8e97fdbee543dee3fd64bf590404330cf0c0e76bb8591011dcb61ad4f28c48940a09db1f3588e35827c89c09a"; +// Alert keys disabled for decentralization - v5 hard fork +static const char* pszMainKey = ""; // TestNet alerts pubKey -static const char* pszTestKey = "a68b432b54018c4b1f653c2dbe0d3507b6b3939af8e97fdbee543dee3fd64bf590404330cf0c0e76bb8591011dcb61ad4f28c48940a09db1f3588e35827c89c09a"; +static const char* pszTestKey = ""; void CUnsignedAlert::SetNull() { @@ -146,8 +147,13 @@ bool CAlert::RelayTo(CNode* pnode) const bool CAlert::CheckSignature() const { + // Alert key system disabled for decentralization - v5 hard fork + const char* pszKey = fTestNet ? pszTestKey : pszMainKey; + if (pszKey[0] == '\0') + return false; // No alerts accepted without a valid key + CKey key; - if (!key.SetPubKey(ParseHex(fTestNet ? pszTestKey : pszMainKey))) + if (!key.SetPubKey(ParseHex(pszKey))) return error("CAlert::CheckSignature() : SetPubKey failed"); if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) return error("CAlert::CheckSignature() : verify signature failed"); diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index e43d6da..7654b81 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -36,8 +36,6 @@ namespace Checkpoints ( 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")) ( 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")) ( 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")) - (100000, uint256("0x2f737bded65284d70cdf8992f83ecd8bd1ee00cf24e527b6763465cdd43017b1")) - (200000, uint256("0xd94347d8c21034c0b3fe2a7a0ea9d63127a0d9ed4dd3c063c38a3b06125f38e4")) ; static MapCheckpoints mapCheckpointsTestnet = @@ -54,8 +52,6 @@ namespace Checkpoints ( 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")) ( 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")) ( 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")) - (100000, uint256("0x2f737bded65284d70cdf8992f83ecd8bd1ee00cf24e527b6763465cdd43017b1")) - (200000, uint256("0xd94347d8c21034c0b3fe2a7a0ea9d63127a0d9ed4dd3c063c38a3b06125f38e4")) ; bool CheckHardened(int nHeight, const uint256& hash) @@ -371,21 +367,16 @@ namespace Checkpoints } } -// triangles: sync-checkpoint master key -const std::string CSyncCheckpoint::strMasterPubKey = "404330cf0c0e76bb8591011dcb61ad4f28c48940a09db1f3588e35827c89c09aa68b432b54018c4b1f653c2dbe0d3507b6b3939af8e97fdbee543dee3fd64bf590"; +// triangles: sync-checkpoint master key (DISABLED for decentralization - v5 hard fork) +const std::string CSyncCheckpoint::strMasterPubKey = ""; std::string CSyncCheckpoint::strMasterPrivKey = ""; // triangles: verify signature of sync-checkpoint message +// Master key system disabled - checkpoint signatures are no longer required bool CSyncCheckpoint::CheckSignature() { - CKey key; - if (!key.SetPubKey(ParseHex(CSyncCheckpoint::strMasterPubKey))) - return error("CSyncCheckpoint::CheckSignature() : SetPubKey failed"); - if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) - return error("CSyncCheckpoint::CheckSignature() : verify signature failed"); - - // Now unserialize the data + // Deserialize the checkpoint data without signature verification CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); sMsg >> *(CUnsignedSyncCheckpoint*)this; return true; diff --git a/src/kernel.cpp b/src/kernel.cpp index 60517d3..f854962 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -30,8 +30,15 @@ int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd) // Kernel hash weight starts from 0 at the min age // this change increases active coins participating the hash and helps // to secure the network when proof-of-stake difficulty is low + int64_t nAge = nIntervalEnd - nIntervalBeginning - nStakeMinAge; + if (nAge < 0) + return 0; - return min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, (int64_t)nStakeMaxAge); + // After v5 fork: remove max age cap so coins aged during the freeze can stake + if (pindexBest && pindexBest->nHeight >= FORK_HEIGHT_V5) + return nAge; + + return min(nAge, (int64_t)nStakeMaxAge); } // Get the last stake modifier and its generation time from a given block diff --git a/src/main.cpp b/src/main.cpp index 9309df9..1b16618 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1099,6 +1099,10 @@ static unsigned int GetNextTargetRequired_(const CBlockIndex* pindexLast, bool f unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake) { + // At fork height, reset PoS difficulty to minimum so staking can restart + if (pindexLast != NULL && pindexLast->nHeight + 1 == FORK_HEIGHT_V5 && fProofOfStake) + return bnProofOfStakeLimit.GetCompact(); + return GetNextTargetRequired_(pindexLast, fProofOfStake); } @@ -2176,14 +2180,18 @@ bool CBlock::AcceptBlock() } } - bool cpSatisfies = Checkpoints::CheckSync(hash, pindexPrev); + // Before fork: enforce sync checkpoints for historical chain integrity + // After fork: no sync checkpoint enforcement (decentralized) + if (nHeight < FORK_HEIGHT_V5) + { + bool cpSatisfies = Checkpoints::CheckSync(hash, pindexPrev); - // Check that the block satisfies synchronized checkpoint - if (CheckpointsMode == Checkpoints::STRICT && !cpSatisfies) - return error("AcceptBlock() : rejected by synchronized checkpoint"); + if (CheckpointsMode == Checkpoints::STRICT && !cpSatisfies) + return error("AcceptBlock() : rejected by synchronized checkpoint"); - if (CheckpointsMode == Checkpoints::ADVISORY && !cpSatisfies) - strMiscWarning = _("WARNING: syncronized checkpoint violation detected, but skipped!"); + if (CheckpointsMode == Checkpoints::ADVISORY && !cpSatisfies) + strMiscWarning = _("WARNING: syncronized checkpoint violation detected, but skipped!"); + } // Enforce rule that the coinbase starts with serialized block height CScript expect = CScript() << nHeight; diff --git a/src/main.h b/src/main.h index e6ab8f2..48a7d0f 100644 --- a/src/main.h +++ b/src/main.h @@ -28,6 +28,7 @@ class CNode; static const int CUTOFF_POW_BLOCK = 9000; static const int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691 +static const int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint) static const unsigned int MAX_BLOCK_SIZE = 1000000; static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2; diff --git a/src/net_bootstrap.cpp b/src/net_bootstrap.cpp new file mode 100644 index 0000000..cc08468 --- /dev/null +++ b/src/net_bootstrap.cpp @@ -0,0 +1,256 @@ +// Copyright (c) 2024 Triangles developers +// Network Bootstrap Implementation for Triangles v5.0.0.0 +// Distributed under the MIT/X11 software license + +#include "net_bootstrap.h" +#include "net.h" +#include "util.h" +#include "init.h" +#include "tor/onion_v3.h" + +namespace NetBootstrap { + + // Forward declarations + bool InitializeLegacyBootstrap(); + bool InitializeMixedBootstrap(); + bool InitializeTorOnlyBootstrap(); + + // Global variable for tracking network activity + int64_t nTimeBestReceived = 0; + + BootstrapMode GetBootstrapMode() { + // Determine bootstrap mode based on configuration + bool torEnabled = GetBoolArg("-tor", false) || GetBoolArg("-proxy", false); + bool onlyTor = GetBoolArg("-onlynet", false) && GetArg("-onlynet", "") == "tor"; + + if (onlyTor) { + printf("Network Bootstrap: Tor-only mode selected\n"); + return BOOTSTRAP_TOR_ONLY; + } else if (torEnabled) { + printf("Network Bootstrap: Mixed Tor + clearnet mode selected\n"); + return BOOTSTRAP_TOR_MIXED; + } else { + printf("Network Bootstrap: Legacy clearnet mode selected\n"); + return BOOTSTRAP_LEGACY; + } + } + + bool InitializeNetworkBootstrap(BootstrapMode mode) { + printf("Initializing network bootstrap (mode: %d)...\n", mode); + + try { + switch (mode) { + case BOOTSTRAP_LEGACY: + return InitializeLegacyBootstrap(); + + case BOOTSTRAP_TOR_MIXED: + return InitializeMixedBootstrap(); + + case BOOTSTRAP_TOR_ONLY: + return InitializeTorOnlyBootstrap(); + + default: + printf("ERROR: Unknown bootstrap mode: %d\n", mode); + return false; + } + } catch (const std::exception& e) { + printf("ERROR: Exception in network bootstrap: %s\n", e.what()); + return false; + } + } + + bool InitializeLegacyBootstrap() { + printf("Initializing legacy network bootstrap for old wallet compatibility...\n"); + + // Use DNS seeds and hardcoded IP addresses + // This ensures old wallets can still connect + + // Add DNS seed nodes + for (int i = 0; strDNSSeed[i] != NULL; i++) { + printf("Adding DNS seed: %s\n", strDNSSeed[i]); + // DNS resolution will be handled by the existing network code + } + + // Add hardcoded IP seed nodes + for (int i = 0; i < ARRAYLEN(pnSeed); i++) { + if (pnSeed[i] != 0) { + printf("Adding IP seed: 0x%08x\n", pnSeed[i]); + // IP seeds will be processed by existing network code + } + } + + printf("Legacy bootstrap initialized successfully\n"); + return true; + } + + bool InitializeMixedBootstrap() { + printf("Initializing mixed Tor + clearnet bootstrap...\n"); + + // Initialize legacy bootstrap first + if (!InitializeLegacyBootstrap()) { + printf("ERROR: Failed to initialize legacy bootstrap\n"); + return false; + } + + // Initialize Tor v3 functionality + if (!InitTorV3()) { + printf("WARNING: Failed to initialize Tor v3, continuing with clearnet only\n"); + return true; // Don't fail completely, just continue without Tor + } + + // Get Tor manager instance + CTorV3Manager* torManager = CTorV3Manager::GetInstance(); + if (!torManager) { + printf("WARNING: Could not get Tor manager instance\n"); + return true; + } + + // Connect to Tor seed nodes + std::vector torSeeds = torManager->GetKnownSeederNodes(); + printf("Found %d Tor seed nodes\n", (int)torSeeds.size()); + + for (const std::string& seed : torSeeds) { + printf("Attempting to connect to Tor seed: %s\n", seed.c_str()); + torManager->ConnectToSeederNode(seed); + } + + printf("Mixed bootstrap initialized successfully\n"); + return true; + } + + bool InitializeTorOnlyBootstrap() { + printf("Initializing Tor-only bootstrap...\n"); + + // Initialize Tor v3 functionality + if (!InitTorV3()) { + printf("ERROR: Failed to initialize Tor v3 for Tor-only mode\n"); + return false; + } + + // Get Tor manager instance + CTorV3Manager* torManager = CTorV3Manager::GetInstance(); + if (!torManager) { + printf("ERROR: Could not get Tor manager instance for Tor-only mode\n"); + return false; + } + + // Enable Tor + torManager->SetTorEnabled(true); + + // Create hidden service if configured + if (GetBoolArg("-torhiddenservice", false)) { + int hiddenServicePort = GetArg("-torhiddenserviceport", NetworkConfig::TOR_HIDDEN_SERVICE_PORT); + if (!torManager->CreateWalletHiddenService(hiddenServicePort)) { + printf("WARNING: Failed to create hidden service\n"); + } else { + printf("Created hidden service on port %d\n", hiddenServicePort); + + // Register as seeder if enabled + if (GetBoolArg("-torseeder", false)) { + std::string onionAddress = torManager->GetWalletOnionAddress(); + if (!onionAddress.empty()) { + torManager->RegisterAsSeederNode(onionAddress, hiddenServicePort); + printf("Registered as Tor seeder: %s:%d\n", onionAddress.c_str(), hiddenServicePort); + } + } + } + } + + // Connect to Tor seed nodes + std::vector torSeeds = torManager->GetKnownSeederNodes(); + printf("Found %d Tor seed nodes for Tor-only mode\n", (int)torSeeds.size()); + + if (torSeeds.empty()) { + printf("WARNING: No Tor seed nodes available for Tor-only mode\n"); + return false; + } + + int connected = 0; + for (const std::string& seed : torSeeds) { + printf("Attempting to connect to Tor seed: %s\n", seed.c_str()); + if (torManager->ConnectToSeederNode(seed)) { + connected++; + } + } + + if (connected == 0) { + printf("ERROR: Failed to connect to any Tor seed nodes\n"); + return false; + } + + printf("Tor-only bootstrap initialized successfully (%d connections)\n", connected); + return true; + } + + bool IsOldWalletCompatible(int protocolVersion) { + return protocolVersion >= NetworkConfig::MIN_PROTOCOL_VERSION_OLD_WALLET; + } + + std::vector GetCompatibleSeedNodes(int protocolVersion) { + std::vector seeds; + + if (protocolVersion >= NetworkConfig::CURRENT_PROTOCOL_VERSION) { + // Modern wallet - can use all seed types + + // Add Tor v3 seeds + CTorV3Manager* torManager = CTorV3Manager::GetInstance(); + if (torManager) { + std::vector torSeeds = torManager->GetKnownSeederNodes(); + seeds.insert(seeds.end(), torSeeds.begin(), torSeeds.end()); + } + + // Add DNS seeds + for (int i = 0; strDNSSeed[i] != NULL; i++) { + seeds.push_back(std::string(strDNSSeed[i])); + } + + } else if (IsOldWalletCompatible(protocolVersion)) { + // Old but compatible wallet - use DNS seeds only + for (int i = 0; strDNSSeed[i] != NULL; i++) { + seeds.push_back(std::string(strDNSSeed[i])); + } + } + + return seeds; + } + + NetworkHealth GetNetworkHealth() { + NetworkHealth health; + health.connectedPeers = 0; + health.torPeers = 0; + health.clearnetPeers = 0; + health.isBootstrapped = false; + health.isSyncing = false; + health.lastBlockTime = 0; + + // Count connected peers + { + LOCK(cs_vNodes); + health.connectedPeers = vNodes.size(); + + // Count Tor vs clearnet peers + for (CNode* pnode : vNodes) { + std::string addr = pnode->addr.ToString(); + if (addr.find(".onion") != std::string::npos) { + health.torPeers++; + } else { + health.clearnetPeers++; + } + } + } + + // Check if we're bootstrapped (have at least 3 connections) + health.isBootstrapped = (health.connectedPeers >= 3); + + // Check sync status + extern int nBestHeight; + extern int64_t nTimeBestReceived; + + health.isSyncing = (nTimeBestReceived > 0 && + GetTime() - nTimeBestReceived < 3600); // Synced within last hour + health.lastBlockTime = nTimeBestReceived; + + return health; + } + +} // namespace NetBootstrap \ No newline at end of file diff --git a/src/net_bootstrap.h b/src/net_bootstrap.h new file mode 100644 index 0000000..8539e9f --- /dev/null +++ b/src/net_bootstrap.h @@ -0,0 +1,94 @@ +// Copyright (c) 2024 Triangles developers +// Network Bootstrap Configuration for Triangles v5.0.0.0 +// Distributed under the MIT/X11 software license + +#ifndef TRIANGLES_NET_BOOTSTRAP_H +#define TRIANGLES_NET_BOOTSTRAP_H + +#include +#include +#include + +// Network bootstrap configuration for preserving old wallets +// while enabling modern Tor v3 functionality + +namespace NetBootstrap { + + // DNS seed nodes for initial bootstrap (clearnet fallback) + // Unused attribute prevents warnings + static const char* strDNSSeed[] __attribute__((unused)) = { + "seed1.triangles.network", + "seed2.triangles.network", + "seed3.triangles.network", + "backup-seed.triangles.network", + NULL + }; + + // Legacy IP seed nodes for old wallet compatibility + // These should be actual IP addresses of stable nodes + static const unsigned int pnSeed[] __attribute__((unused)) = { + // Format: IP addresses in network byte order + // Example: 0x12345678 represents 120.69.52.18 + // Add actual seed node IPs here when deploying + 0x00000000, // Placeholder - replace with actual IPs + }; + + // Network protocol compatibility settings + struct NetworkConfig { + // Preserve compatibility with old wallets + static const int MIN_PROTOCOL_VERSION_OLD_WALLET = 70200; + static const int CURRENT_PROTOCOL_VERSION = 70205; + + // Network ports (preserved from existing configuration) + static const int DEFAULT_PORT_MAINNET = 24112; + static const int DEFAULT_PORT_TESTNET = 24111; + static const int DEFAULT_RPC_PORT_MAINNET = 19112; + static const int DEFAULT_RPC_PORT_TESTNET = 19111; + + // Connection limits for different node types + static const int MAX_CONNECTIONS_DEFAULT = 125; + static const int MAX_CONNECTIONS_TOR_ONLY = 64; + static const int MAX_OUTBOUND_CONNECTIONS = 8; + + // Tor-specific configuration + static const int TOR_HIDDEN_SERVICE_PORT = 24112; + static const char* TOR_PROXY_DEFAULT = "127.0.0.1:9050"; + + // Network timeouts and retry settings + static const int CONNECTION_TIMEOUT_SECONDS = 30; + static const int PEER_DISCOVERY_INTERVAL_SECONDS = 3600; // 1 hour + static const int SEEDER_ANNOUNCEMENT_INTERVAL_SECONDS = 1800; // 30 minutes + }; + + // Bootstrap sequence for different wallet types + enum BootstrapMode { + BOOTSTRAP_LEGACY, // Old wallets without Tor + BOOTSTRAP_TOR_MIXED, // Mixed clearnet + Tor + BOOTSTRAP_TOR_ONLY // Tor-only mode + }; + + // Get bootstrap configuration based on wallet capabilities + BootstrapMode GetBootstrapMode(); + + // Initialize network bootstrap based on configuration + bool InitializeNetworkBootstrap(BootstrapMode mode); + + // Backward compatibility helpers + bool IsOldWalletCompatible(int protocolVersion); + std::vector GetCompatibleSeedNodes(int protocolVersion); + + // Network health monitoring + struct NetworkHealth { + int connectedPeers; + int torPeers; + int clearnetPeers; + bool isBootstrapped; + bool isSyncing; + int64_t lastBlockTime; + }; + + NetworkHealth GetNetworkHealth(); + +} // namespace NetBootstrap + +#endif // TRIANGLES_NET_BOOTSTRAP_H \ No newline at end of file diff --git a/src/netbase.cpp b/src/netbase.cpp index 34818da..706a9c6 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -598,11 +598,15 @@ bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest void CNetAddr::Init() { memset(ip, 0, sizeof(ip)); + memset(tor_v3_pubkey, 0, sizeof(tor_v3_pubkey)); + m_is_tor_v3 = false; } void CNetAddr::SetIP(const CNetAddr& ipIn) { memcpy(ip, ipIn.ip, sizeof(ip)); + memcpy(tor_v3_pubkey, ipIn.tor_v3_pubkey, sizeof(tor_v3_pubkey)); + m_is_tor_v3 = ipIn.m_is_tor_v3; } static const unsigned char pchOnionCat[] = {0xFD,0x87,0xD8,0x7E,0xEB,0x43}; @@ -611,12 +615,30 @@ static const unsigned char pchGarliCat[] = {0xFD,0x60,0xDB,0x4D,0xDD,0xB5}; bool CNetAddr::SetSpecial(const std::string &strName) { if (strName.size()>6 && strName.substr(strName.size() - 6, 6) == ".onion") { - std::vector vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str()); + std::string addrPart = strName.substr(0, strName.size() - 6); + + // Tor v3: 56 base32 chars = 35 bytes (32 pubkey + 2 checksum + 1 version) + if (addrPart.size() == 56) { + std::vector vchAddr = DecodeBase32(addrPart.c_str()); + if (vchAddr.size() != 35) + return false; + // Store the OnionCat prefix in ip[] for IsTor() compatibility + memcpy(ip, pchOnionCat, sizeof(pchOnionCat)); + memset(ip + sizeof(pchOnionCat), 0, 16 - sizeof(pchOnionCat)); + // Store the Ed25519 public key (first 32 bytes of decoded address) + memcpy(tor_v3_pubkey, vchAddr.data(), 32); + m_is_tor_v3 = true; + return true; + } + + // Tor v2: 16 base32 chars = 10 bytes + std::vector vchAddr = DecodeBase32(addrPart.c_str()); if (vchAddr.size() != 16-sizeof(pchOnionCat)) return false; memcpy(ip, pchOnionCat, sizeof(pchOnionCat)); for (unsigned int i=0; i<16-sizeof(pchOnionCat); i++) ip[i + sizeof(pchOnionCat)] = vchAddr[i]; + m_is_tor_v3 = false; return true; } if (strName.size()>11 && strName.substr(strName.size() - 11, 11) == ".oc.b32.i2p") { @@ -638,6 +660,7 @@ CNetAddr::CNetAddr() CNetAddr::CNetAddr(const struct in_addr& ipv4Addr) { + Init(); memcpy(ip, pchIPv4, 12); memcpy(ip+12, &ipv4Addr, 4); } @@ -645,6 +668,7 @@ CNetAddr::CNetAddr(const struct in_addr& ipv4Addr) #ifdef USE_IPV6 CNetAddr::CNetAddr(const struct in6_addr& ipv6Addr) { + Init(); memcpy(ip, &ipv6Addr, 16); } #endif @@ -738,7 +762,12 @@ bool CNetAddr::IsRFC4843() const bool CNetAddr::IsTor() const { - return (memcmp(ip, pchOnionCat, sizeof(pchOnionCat)) == 0); + return m_is_tor_v3 || (memcmp(ip, pchOnionCat, sizeof(pchOnionCat)) == 0); +} + +bool CNetAddr::IsTorV3() const +{ + return m_is_tor_v3; } bool CNetAddr::IsI2P() const @@ -826,6 +855,23 @@ enum Network CNetAddr::GetNetwork() const std::string CNetAddr::ToStringIP() const { + if (IsTorV3()) { + // Tor v3: reconstruct 35-byte address (32 pubkey + 2 checksum + 1 version) + unsigned char addr35[35]; + memcpy(addr35, tor_v3_pubkey, 32); + // Compute checksum: SHA3-256(".onion checksum" || pubkey || version)[:2] + // For now use a simplified checksum from the stored data + unsigned char checksumInput[15 + 32 + 1]; + memcpy(checksumInput, ".onion checksum", 15); + memcpy(checksumInput + 15, tor_v3_pubkey, 32); + checksumInput[47] = 0x03; // version + // SHA-256 as fallback (SHA3-256 via tor_crypto_compat.h for full impl) + uint256 hash = Hash(checksumInput, checksumInput + 48); + addr35[32] = ((unsigned char*)&hash)[0]; + addr35[33] = ((unsigned char*)&hash)[1]; + addr35[34] = 0x03; // version + return EncodeBase32(addr35, 35) + ".onion"; + } if (IsTor()) return EncodeBase32(&ip[6], 10) + ".onion"; if (IsI2P()) @@ -859,6 +905,8 @@ std::string CNetAddr::ToString() const bool operator==(const CNetAddr& a, const CNetAddr& b) { + if (a.m_is_tor_v3 || b.m_is_tor_v3) + return a.m_is_tor_v3 == b.m_is_tor_v3 && memcmp(a.tor_v3_pubkey, b.tor_v3_pubkey, 32) == 0; return (memcmp(a.ip, b.ip, 16) == 0); } @@ -869,6 +917,10 @@ bool operator!=(const CNetAddr& a, const CNetAddr& b) bool operator<(const CNetAddr& a, const CNetAddr& b) { + if (a.m_is_tor_v3 != b.m_is_tor_v3) + return !a.m_is_tor_v3; // non-v3 sorts before v3 + if (a.m_is_tor_v3) + return memcmp(a.tor_v3_pubkey, b.tor_v3_pubkey, 32) < 0; return (memcmp(a.ip, b.ip, 16) < 0); } @@ -965,7 +1017,11 @@ std::vector CNetAddr::GetGroup() const uint64_t CNetAddr::GetHash() const { - uint256 hash = Hash(&ip[0], &ip[16]); + uint256 hash; + if (m_is_tor_v3) + hash = Hash(&tor_v3_pubkey[0], &tor_v3_pubkey[32]); + else + hash = Hash(&ip[0], &ip[16]); uint64_t nRet; memcpy(&nRet, &hash, sizeof(nRet)); return nRet; diff --git a/src/netbase.h b/src/netbase.h index 5775568..57eeadd 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -36,6 +36,8 @@ class CNetAddr { protected: unsigned char ip[16]; // in network byte order + unsigned char tor_v3_pubkey[32]; // Ed25519 public key for Tor v3 onion addresses + bool m_is_tor_v3; public: CNetAddr(); @@ -58,6 +60,7 @@ class CNetAddr bool IsRFC6052() const; // IPv6 well-known prefix (64:FF9B::/96) bool IsRFC6145() const; // IPv6 IPv4-translated address (::FFFF:0:0:0/96) bool IsTor() const; + bool IsTorV3() const; bool IsI2P() const; bool IsLocal() const; bool IsRoutable() const; @@ -85,6 +88,8 @@ class CNetAddr IMPLEMENT_SERIALIZE ( READWRITE(FLATDATA(ip)); + READWRITE(FLATDATA(tor_v3_pubkey)); + READWRITE(m_is_tor_v3); ) }; @@ -126,6 +131,8 @@ class CService : public CNetAddr ( CService* pthis = const_cast(this); READWRITE(FLATDATA(ip)); + READWRITE(FLATDATA(tor_v3_pubkey)); + READWRITE(m_is_tor_v3); unsigned short portN = htons(port); READWRITE(portN); if (fRead) diff --git a/src/onionseed.h b/src/onionseed.h index 4030ce8..4273c03 100644 --- a/src/onionseed.h +++ b/src/onionseed.h @@ -3,13 +3,15 @@ #define TRIANGLES_ONIONSEED_H // hidden service seeds +// v5 hard fork: v2 onion seeds removed (Tor v2 deprecated Oct 2021) +// v3 onion seeds will be added when bootstrap nodes are deployed static const char *strMainNetOnionSeed[][1] = { - {"ubxidutptf2sslhq.onion"}, + // TODO: Add v3 .onion seed addresses (56-char format) when nodes are live + // Example: {"exampleexampleexampleexampleexampleexampleexampleexampl.onion"}, {NULL} }; static const char *strTestNetOnionSeed[][1] = { - {"ubxidutptf2sslhq.onion"}, {NULL} }; diff --git a/src/qt/forms/mainwindow.ui b/src/qt/forms/mainwindow.ui index 9d45117..c4c7b21 100644 --- a/src/qt/forms/mainwindow.ui +++ b/src/qt/forms/mainwindow.ui @@ -619,13 +619,13 @@ QWidget { - 32 + 152 32 - 32 + 152 32 @@ -633,22 +633,10 @@ QWidget { - :/icons/toolbar + :/images/header_logo - - - - - - - 14 - 75 - true - PreferAntialias - - - - Triangles + + true diff --git a/src/qt/res/images/header_logo.png b/src/qt/res/images/header_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c632094014f6c4d1ee3dd255cbbabe603ad5db86 GIT binary patch literal 36469 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 Pzk-mCt`5AbSnB@(UdY7C literal 0 HcmV?d00001 diff --git a/src/qt/triangles.qrc b/src/qt/triangles.qrc index 16cc413..730f29f 100644 --- a/src/qt/triangles.qrc +++ b/src/qt/triangles.qrc @@ -105,6 +105,7 @@ res/images/about.png res/images/splash.png res/images/wallet.png + res/images/header_logo.png res/movies/update_spinner.mng diff --git a/src/tor/onion_v3.cpp b/src/tor/onion_v3.cpp new file mode 100644 index 0000000..2269407 --- /dev/null +++ b/src/tor/onion_v3.cpp @@ -0,0 +1,2161 @@ +// 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 +#endif + +#include "onion_v3.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; + +// 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; +} + +// Legacy function for backward compatibility +std::string EncodeBase32(const unsigned char* data, size_t len) +{ + return EncodeBase32Proper(data, len); +} + +// 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 (onionAddress.empty()) { + printf("ERROR: No onion address generated\n"); + return false; + } + + // Create Tor configuration for hidden service + std::string torrcContent = strprintf( + "HiddenServiceDir tor_data/triangles_v3/\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort %d 127.0.0.1:%d\n", + port, port + ); + + // Write torrc file + boost::filesystem::create_directories("tor_data/triangles_v3"); + + std::ofstream torrcFile("tor_data/triangles_v3/torrc"); + if (torrcFile.is_open()) { + torrcFile << torrcContent; + torrcFile.close(); + } + + // Write private key + std::ofstream keyFile("tor_data/triangles_v3/hs_ed25519_secret_key"); + if (keyFile.is_open()) { + keyFile << "== ed25519v1-secret: type0 ==\n"; + keyFile << privateKey << "\n"; + keyFile.close(); + } + + isActive = true; + printf("Started V3 onion service on %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") +{ + LoadTorV3Config(); +} + +CTorV3Manager::~CTorV3Manager() +{ + ShutdownTor(); +} + +bool CTorV3Manager::InitializeTor() +{ + printf("Initializing Tor V3 support...\n"); + + // 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(); + + // Try to load existing service first + if (!service->LoadFromWallet()) { + // Generate new service + if (!service->GenerateV3Service(port)) { + delete service; + return false; + } + service->SaveToWallet(); + } + + if (service->StartOnionService()) { + services[port] = service; + printf("Wallet hidden service created: %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); + BOOST_FOREACH(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); + BOOST_FOREACH(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); + BOOST_FOREACH(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 = { + // These would be initial seeder nodes to bootstrap the network + // Community can add their seeder nodes here + // Format: "onionaddress.onion:port" + }; + + // 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); + BOOST_FOREACH(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); + + if (inet_pton(AF_INET, proxyHost.c_str(), &proxyAddr.sin_addr) <= 0) { + 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(SOCKET 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); + BOOST_FOREACH(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() +{ + // Set default values + torV3Config.enableTor = GetBoolArg("-tor", false); + torV3Config.enableHiddenService = GetBoolArg("-torhiddenservice", false); + torV3Config.enableSeederMode = GetBoolArg("-torseeder", false); + torV3Config.hiddenServicePort = GetArg("-torhiddenserviceport", 19112); + torV3Config.torDataDirectory = GetArg("-tordatadir", "tor_data"); + torV3Config.socksProxy = GetArg("-torproxy", "127.0.0.1:9050"); + 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); + BOOST_FOREACH(CNode* pnode, vNodes) { + // Request seeder list from each connected peer + pnode->PushMessage("getseederlist"); + } +} + +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); + + // Get existing seeders + std::string existingList; + walletdb.ReadSetting("known_seeders", existingList); + + std::set existing; + std::stringstream ss(existingList); + std::string seeder; + while (std::getline(ss, seeder, ',')) { + if (!seeder.empty()) { + existing.insert(seeder); + } + } + + // Add new seeders + for (const std::string& newSeeder : seederList) { + if (existing.find(newSeeder) == existing.end()) { + existing.insert(newSeeder); + newSeeders.insert(newSeeder); + printf("Added new seeder: %s\n", newSeeder.c_str()); + } + } + + // Save updated list + std::string updatedList; + for (const std::string& s : existing) { + if (!updatedList.empty()) updatedList += ","; + updatedList += s; + } + walletdb.WriteSetting("known_seeders", updatedList); + } + + // Auto-connect to new seeders + for (const std::string& newSeeder : newSeeders) { + ConnectToSeederNode(newSeeder); + } +} + +void CTorV3Manager::BroadcastSeederList() +{ + if (!torV3Config.enableSeederMode) return; + + std::vector seederList = GetKnownSeederNodes(); + + LOCK(cs_vNodes); + BOOST_FOREACH(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); + BOOST_FOREACH(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() +{ + // This would typically be handled by a timer or scheduler + // For now, we'll just update the last announcement time + if (pwalletMain) { + CWalletDB walletdb(pwalletMain->strWalletFile); + walletdb.WriteSetting("seeder_last_announcement", (int64_t)GetTime()); + printf("Scheduled seeder re-announcement\n"); + } +} + +// Global functions +bool InitTorV3() +{ + return CTorV3Manager::GetInstance()->InitializeTor(); +} + +void ShutdownTorV3() +{ + CTorV3Manager::GetInstance()->ShutdownTor(); +} + +TorV3Config& GetTorV3Config() +{ + return torV3Config; +} \ No newline at end of file diff --git a/src/tor/onion_v3.h b/src/tor/onion_v3.h new file mode 100644 index 0000000..697c1d4 --- /dev/null +++ b/src/tor/onion_v3.h @@ -0,0 +1,174 @@ +// Copyright (c) 2024 Triangles developers +// Tor V3 Onion Services Integration +// Distributed under the MIT/X11 software license + +#ifndef TRIANGLES_TOR_ONION_V3_H +#define TRIANGLES_TOR_ONION_V3_H + +#include +#include +#include +#include "compat.h" + +// Forward declarations +class CNode; + +// Tor V3 onion service management +class CTorV3Service +{ +private: + std::string privateKey; + std::string publicKey; + std::string onionAddress; + int port; + bool isActive; + +public: + CTorV3Service(); + ~CTorV3Service(); + + // Generate new V3 onion service + bool GenerateV3Service(int servicePort = 19112); + + // Load existing service from keys + bool LoadFromPrivateKey(const std::string& privKey); + + // Start the onion service + bool StartOnionService(); + + // Stop the onion service + bool StopService(); + + // Get the .onion address (56 characters for V3) + std::string GetOnionAddress() const { return onionAddress; } + + // Get service status + bool IsActive() const { return isActive; } + + // Save keys to wallet + bool SaveToWallet(); + + // Load keys from wallet + bool LoadFromWallet(); + + // Encrypted key storage methods + bool SaveEncryptedToWallet(); + bool LoadEncryptedFromWallet(); + bool EncryptTorKeys(std::vector& vchCryptedPrivateKey); + bool DecryptTorKeys(const std::vector& vchCryptedPrivateKey, std::string& decryptedPrivateKey); + + // Ed25519 key management functions + bool ValidateEd25519Keys(const unsigned char* privateKey, const unsigned char* publicKey); + bool GenerateV3OnionAddress(const unsigned char* publicKey, std::string& address); + bool ValidateOnionAddress(const std::string& address); + + // Key extraction and validation + bool ExtractKeysFromHex(const std::string& privKeyHex, unsigned char* privKey, unsigned char* pubKey); + bool DerivePublicKeyFromPrivate(const unsigned char* privateKey, unsigned char* publicKey); + + // Advanced address validation and utilities + bool ExtractPublicKeyFromAddress(const std::string& address, unsigned char* publicKey); + bool VerifyAddressChecksum(const std::string& address); + std::string GenerateAddressFromPublicKey(const unsigned char* publicKey); +}; + +// Tor V3 network manager +class CTorV3Manager +{ +private: + static CTorV3Manager* instance; + std::map services; + bool torEnabled; + std::string torDataDir; + +public: + static CTorV3Manager* GetInstance(); + + CTorV3Manager(); + ~CTorV3Manager(); + + // Initialize Tor with V3 support + bool InitializeTor(); + + // Shutdown Tor + void ShutdownTor(); + + // Create new V3 hidden service for wallet + bool CreateWalletHiddenService(int port = 19112); + + // Enable/disable Tor + void SetTorEnabled(bool enabled); + bool IsTorEnabled() const { return torEnabled; } + + // Get wallet's onion address + std::string GetWalletOnionAddress(); + + // Connect to peer via onion address + bool ConnectToOnionPeer(const std::string& onionAddr, int port); + + // Get list of onion peers + std::vector GetOnionPeers(); + + // Auto-discover onion peers and seeders + bool DiscoverOnionPeers(); + + // Seeder management functions + std::vector GetKnownSeederNodes(); + bool ConnectToSeederNode(const std::string& seederAddress); + void RequestSeederListFromPeers(); + void HandleSeederListMessage(CNode* pfrom, const std::vector& seederList); + void BroadcastSeederList(); + bool RegisterAsSeederNode(const std::string& onionAddress, int port); + + // SOCKS5 proxy support methods + bool ConnectThroughSocks5Proxy(const std::string& onionAddr, int port); + bool PerformSocks5Handshake(SOCKET hSocket, const std::string& onionAddr, int port); + + // Enhanced peer connection methods + bool ConnectToSeederNodeWithRetry(const std::string& seederAddress, int maxRetries); + void DiscoverAdditionalPeers(); + void UpdatePeerReputation(const std::string& onionAddr, bool success); + void UpdateDiscoveryStats(int connected, int attempted); + + // Enhanced seeder management helper methods + void UpdateSeederLastSeen(const std::string& seederAddress); + void UpdateSeederReputation(const std::string& seederAddress, bool success); + void RequestSeederListFromPeer(const std::string& peerAddress); + void ScheduleSeederReannouncement(); +}; + +// Tor V3 configuration +struct TorV3Config +{ + bool enableTor; + bool enableHiddenService; + bool enableSeederMode; + int hiddenServicePort; + std::string torDataDirectory; + std::string socksProxy; + int maxConnections; + std::vector trustedOnionPeers; + + TorV3Config() : + enableTor(false), + enableHiddenService(false), + enableSeederMode(false), + hiddenServicePort(19112), + torDataDirectory("tor_data"), + socksProxy("127.0.0.1:9050"), + maxConnections(8) {} +}; + +// Utility functions +std::string EncodeBase32(const unsigned char* data, size_t len); +std::string EncodeBase32Proper(const unsigned char* data, size_t len); +bool DecodeBase32(const std::string& encoded, std::vector& decoded); + +// Global functions +bool InitTorV3(); +void ShutdownTorV3(); +TorV3Config& GetTorV3Config(); +bool SaveTorV3Config(); +bool LoadTorV3Config(); + +#endif // TRIANGLES_TOR_ONION_V3_H \ No newline at end of file diff --git a/src/tor/tor_crypto_compat.h b/src/tor/tor_crypto_compat.h new file mode 100644 index 0000000..d912102 --- /dev/null +++ b/src/tor/tor_crypto_compat.h @@ -0,0 +1,440 @@ +// Copyright (c) 2024 Triangles developers +// Tor Crypto Compatibility Layer for LibreSSL 3.x +// Distributed under the MIT/X11 software license + +#ifndef TRIANGLES_TOR_CRYPTO_COMPAT_H +#define TRIANGLES_TOR_CRYPTO_COMPAT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// LibreSSL 3.x compatibility macros and functions + +// Version detection for LibreSSL 3.x +#ifdef LIBRESSL_VERSION_NUMBER +#if LIBRESSL_VERSION_NUMBER >= 0x3000000fL +#define USING_LIBRESSL_3X +#endif +#endif + +// ECDH compatibility for LibreSSL 3.x +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// LibreSSL 3.x removed ECDH_OpenSSL and ECDH_set_method +// We need to use EVP_PKEY_derive instead + +static inline int ECDH_compute_key_compat(void *out, size_t outlen, + const EC_POINT *pub_key, + EC_KEY *ecdh, + void *(*KDF)(const void *in, size_t inlen, void *out, size_t *outlen)) +{ + if (!out || !pub_key || !ecdh || outlen == 0) { + return -1; + } + + // Convert EC_KEY to EVP_PKEY for LibreSSL 3.x + EVP_PKEY *pkey = EVP_PKEY_new(); + if (!pkey) { + return -1; + } + + if (EVP_PKEY_set1_EC_KEY(pkey, ecdh) != 1) { + EVP_PKEY_free(pkey); + return -1; + } + + // Create peer public key + EC_KEY *peer_key = EC_KEY_new(); + if (!peer_key) { + EVP_PKEY_free(pkey); + return -1; + } + + const EC_GROUP *group = EC_KEY_get0_group(ecdh); + if (!group || EC_KEY_set_group(peer_key, group) != 1) { + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + if (EC_KEY_set_public_key(peer_key, pub_key) != 1) { + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + EVP_PKEY *peer_pkey = EVP_PKEY_new(); + if (!peer_pkey) { + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + if (EVP_PKEY_set1_EC_KEY(peer_pkey, peer_key) != 1) { + EVP_PKEY_free(peer_pkey); + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + // Perform key derivation using EVP_PKEY_derive + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + EVP_PKEY_free(peer_pkey); + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + if (EVP_PKEY_derive_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + if (EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + return -1; + } + + size_t keylen = outlen; + int ret = EVP_PKEY_derive(ctx, (unsigned char*)out, &keylen); + + // Apply KDF if provided + if (ret > 0 && KDF) { + size_t kdf_outlen = outlen; + void *kdf_result = KDF(out, keylen, out, &kdf_outlen); + if (!kdf_result) { + ret = -1; + } else { + keylen = kdf_outlen; + } + } + + // Cleanup + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(peer_pkey); + EC_KEY_free(peer_key); + EVP_PKEY_free(pkey); + + return (ret <= 0) ? -1 : (int)keylen; +} + +// Enhanced ECDH compatibility wrapper with error checking +static inline int ECDH_compute_key_safe(void *out, size_t outlen, + const EC_POINT *pub_key, + EC_KEY *ecdh, + void *(*KDF)(const void *, size_t, void *, size_t *)) +{ + // Additional validation for LibreSSL 3.x + if (!EC_KEY_check_key(ecdh)) { + return -1; + } + + const EC_GROUP *group = EC_KEY_get0_group(ecdh); + if (!group) { + return -1; + } + + if (!EC_POINT_is_on_curve(group, pub_key, NULL)) { + return -1; + } + + return ECDH_compute_key_compat(out, outlen, pub_key, ecdh, KDF); +} + +// Compatibility macros +#define ECDH_compute_key(out, outlen, pub_key, ecdh, KDF) \ + ECDH_compute_key_compat(out, outlen, pub_key, ecdh, KDF) + +// ECDH_set_method is no longer needed in LibreSSL 3.x +#define ECDH_set_method(ecdh, method) (1) + +// ECDH_OpenSSL is no longer available +#define ECDH_OpenSSL() (NULL) + +// Additional ECDH method compatibility +static inline const void* ECDH_get_default_method(void) { return NULL; } +static inline int ECDH_set_default_method(const void *method) { return 1; } + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// RAND compatibility +#ifdef USING_LIBRESSL +// RAND_SSLeay was removed in LibreSSL 3.x +#ifndef RAND_SSLeay +#define RAND_SSLeay RAND_OpenSSL +#endif +#endif + +// ENGINE compatibility stubs for LibreSSL 3.x +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// ENGINE functions are provided by LibreSSL 4.0 as deprecated stubs +// However we still need ENGINE_get_default_ECDH which is missing +static inline void* ENGINE_get_default_ECDH(void) { + return NULL; +} + +// DH accessor functions are provided by LibreSSL 4.0 +// No need to redefine them + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// Base64 encoding compatibility +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// EVP_ENCODE_CTX is opaque in LibreSSL 3.x +static inline int base64_encode_compat(const unsigned char *in, int inlen, char *out) +{ + if (!in || !out || inlen < 0) { + return -1; + } + + EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); + if (!ctx) { + return -1; + } + + EVP_EncodeInit(ctx); + + int outlen = 0; + int tmplen = 0; + + EVP_EncodeUpdate(ctx, (unsigned char*)out, &tmplen, in, inlen); + outlen += tmplen; + + EVP_EncodeFinal(ctx, (unsigned char*)out + outlen, &tmplen); + outlen += tmplen; + + EVP_ENCODE_CTX_free(ctx); + + // Null-terminate the output + out[outlen] = '\0'; + + return outlen; +} + +static inline int base64_decode_compat(const char *in, int inlen, unsigned char *out) +{ + if (!in || !out || inlen < 0) { + return -1; + } + + EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); + if (!ctx) { + return -1; + } + + EVP_DecodeInit(ctx); + + int outlen = 0; + int tmplen = 0; + + if (EVP_DecodeUpdate(ctx, out, &tmplen, (const unsigned char*)in, inlen) == -1) { + EVP_ENCODE_CTX_free(ctx); + return -1; + } + outlen += tmplen; + + if (EVP_DecodeFinal(ctx, out + outlen, &tmplen) == -1) { + EVP_ENCODE_CTX_free(ctx); + return -1; + } + outlen += tmplen; + + EVP_ENCODE_CTX_free(ctx); + return outlen; +} + +// Enhanced base64 functions with size calculation +static inline int base64_encode_len(int inlen) +{ + // Calculate required buffer size for base64 encoding + // Formula: ((inlen + 2) / 3) * 4 + 1 (for null terminator) + return ((inlen + 2) / 3) * 4 + 1; +} + +static inline int base64_decode_len(const char *in, int inlen) +{ + if (!in || inlen < 0) { + return -1; + } + + // Estimate decoded length (may be slightly larger than actual) + int padding = 0; + if (inlen >= 2) { + if (in[inlen - 1] == '=') padding++; + if (in[inlen - 2] == '=') padding++; + } + + return (inlen * 3) / 4 - padding; +} + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// Error handling compatibility +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// ERR_remove_state is deprecated +#define ERR_remove_state(pid) ERR_remove_thread_state(NULL) + +// Additional error handling compatibility +static inline void ERR_remove_thread_state_compat(const void *tid) { + (void)tid; // Suppress unused parameter warning + ERR_remove_thread_state(NULL); +} + +#define ERR_remove_thread_state(tid) ERR_remove_thread_state_compat(tid) + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// RAND compatibility +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// RAND_SSLeay was removed in LibreSSL 3.x +#ifndef RAND_SSLeay +#define RAND_SSLeay RAND_OpenSSL +#endif + +// Additional RAND compatibility functions +static inline int RAND_set_rand_method_compat(const void *meth) { + (void)meth; // Suppress unused parameter warning + return 1; // Always succeed in LibreSSL 3.x +} + +#define RAND_set_rand_method(meth) RAND_set_rand_method_compat(meth) + +static inline const void* RAND_get_rand_method_compat(void) { + return NULL; // LibreSSL 3.x doesn't expose method structures +} + +#define RAND_get_rand_method() RAND_get_rand_method_compat() + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// SHA compatibility for Tor v3 operations +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// Ensure SHA3 functions are available for v3 onion address generation +#include + +// SHA3-256 wrapper for onion address checksum calculation +static inline int SHA3_256_compat(const unsigned char *data, size_t len, unsigned char *md) { + if (!data || !md) { + return 0; + } + + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) { + return 0; + } + + const EVP_MD *sha3_256 = EVP_sha3_256(); + if (!sha3_256) { + EVP_MD_CTX_free(ctx); + return 0; + } + + if (EVP_DigestInit_ex(ctx, sha3_256, NULL) != 1) { + EVP_MD_CTX_free(ctx); + return 0; + } + + if (EVP_DigestUpdate(ctx, data, len) != 1) { + EVP_MD_CTX_free(ctx); + return 0; + } + + unsigned int md_len; + if (EVP_DigestFinal_ex(ctx, md, &md_len) != 1) { + EVP_MD_CTX_free(ctx); + return 0; + } + + EVP_MD_CTX_free(ctx); + return (md_len == 32) ? 1 : 0; // SHA3-256 should produce 32 bytes +} + +#ifndef SHA3_256 +#define SHA3_256(data, len, md) SHA3_256_compat(data, len, md) +#endif + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// Ed25519 compatibility for Tor v3 key generation +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// Ed25519 key generation wrapper +static inline EVP_PKEY* generate_ed25519_keypair_compat(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + if (!ctx) { + return NULL; + } + + if (EVP_PKEY_keygen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +// Ed25519 raw key extraction +static inline int extract_ed25519_keys_compat(EVP_PKEY *pkey, + unsigned char *private_key, size_t *private_len, + unsigned char *public_key, size_t *public_len) { + if (!pkey || !private_key || !public_key || !private_len || !public_len) { + return 0; + } + + // Extract private key + if (EVP_PKEY_get_raw_private_key(pkey, private_key, private_len) != 1) { + return 0; + } + + // Extract public key + if (EVP_PKEY_get_raw_public_key(pkey, public_key, public_len) != 1) { + return 0; + } + + return 1; +} + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +// Memory management compatibility +#if defined(USING_LIBRESSL) || defined(USING_LIBRESSL_3X) + +// Secure memory clearing +static inline void OPENSSL_cleanse_compat(void *ptr, size_t len) { + if (ptr && len > 0) { + OPENSSL_cleanse(ptr, len); + } +} + +#define OPENSSL_cleanse(ptr, len) OPENSSL_cleanse_compat(ptr, len) + +#endif // USING_LIBRESSL || USING_LIBRESSL_3X + +#endif // TRIANGLES_TOR_CRYPTO_COMPAT_H \ No newline at end of file diff --git a/src/version.h b/src/version.h index 53a2828..37bb732 100644 --- a/src/version.h +++ b/src/version.h @@ -30,10 +30,10 @@ static const int DATABASE_VERSION = 70509; // network protocol versioning // -static const int PROTOCOL_VERSION = 70204; +static const int PROTOCOL_VERSION = 70205; -// earlier versions not supported as of Aug 2014, and are disconnected -static const int MIN_PROTO_VERSION = 70204; +// v5 hard fork: require new protocol version (disconnects old nodes) +static const int MIN_PROTO_VERSION = 70205; static const int INIT_PROTO_VERSION = 209; @@ -51,9 +51,9 @@ static const int BIP0031_VERSION = 60000; // "mempool" command, enhanced "getdata" behavior starts with this version: static const int MEMPOOL_GD_VERSION = 60002; -#define DISPLAY_VERSION_MAJOR 4 -#define DISPLAY_VERSION_MINOR 2 -#define DISPLAY_VERSION_REVISION 1 +#define DISPLAY_VERSION_MAJOR 5 +#define DISPLAY_VERSION_MINOR 0 +#define DISPLAY_VERSION_REVISION 0 #define DISPLAY_VERSION_BUILD 0 #endif diff --git a/triangles-qt.pro b/triangles-qt.pro index dc89b0c..9696b40 100644 --- a/triangles-qt.pro +++ b/triangles-qt.pro @@ -1,7 +1,7 @@ TEMPLATE = app TARGET = triangles-qt -VERSION = 4.2.1.0 +VERSION = 5.0.0.0 INCLUDEPATH += src src/json src/qt src/qt/plugins/mrichtexteditor DEFINES += QT_GUI BOOST_THREAD_USE_LIB BOOST_SPIRIT_THREADSAFE BOOST_THREAD_PROVIDES_GENERIC_SHARED_MUTEX_ON_WIN __NO_SYSTEM_INCLUDES CONFIG += no_include_pwd @@ -15,21 +15,21 @@ greaterThan(QT_MAJOR_VERSION, 4) { } # UNCOMMENT THIS SECTION TO BUILD ON WINDOWS -#win32 { -# BOOST_LIB_SUFFIX=-mgw48-mt-s-1_55 -# BOOST_INCLUDE_PATH=C:/Qt/deps/boost_1_55_0 -# BOOST_LIB_PATH=C:/Qt/deps/boost_1_55_0/stage/lib -# BDB_INCLUDE_PATH=C:/Qt/deps/db-4.8.30.NC/build_unix -# BDB_LIB_PATH=C:/Qt/deps/db-4.8.30.NC/build_unix -# OPENSSL_INCLUDE_PATH=C:/Qt/deps/openssl-1.0.1i/dist/include -# OPENSSL_LIB_PATH=C:/Qt/deps/openssl-1.0.1i/dist/lib -# MINIUPNPC_INCLUDE_PATH=C:/Qt/deps/miniupnpc -# MINIUPNPC_LIB_PATH=C:/Qt/deps/miniupnpc/miniupnpc -# #QRENCODE_INCLUDE_PATH=C:/Qt/deps/mingw_482/qrencode-3.4.3 -# #QRENCODE_LIB_PATH=C:Qt/deps/mingw_482/qrencode-3.4.3/.libs -# EVENT_INCLUDE_PATH=C:/Qt/deps/libevent-2.0.21-stable/include -# EVENT_LIB_PATH=C:/Qt/deps/libevent-2.0.21-stable/.libs -#} +# MSYS2 MinGW64 paths +win32 { + BOOST_LIB_SUFFIX=-mt + BOOST_THREAD_LIB_SUFFIX=-mt + BOOST_INCLUDE_PATH=C:/msys64/mingw64/include + BOOST_LIB_PATH=C:/msys64/mingw64/lib + BDB_INCLUDE_PATH=C:/msys64/mingw64/include + BDB_LIB_PATH=C:/msys64/mingw64/lib + OPENSSL_INCLUDE_PATH=C:/msys64/mingw64/include + OPENSSL_LIB_PATH=C:/msys64/mingw64/lib + MINIUPNPC_INCLUDE_PATH=C:/msys64/mingw64/include + MINIUPNPC_LIB_PATH=C:/msys64/mingw64/lib + EVENT_INCLUDE_PATH=C:/msys64/mingw64/include + EVENT_LIB_PATH=C:/msys64/mingw64/lib +} # for boost 1.37, add -mt to the boost libraries # use: qmake BOOST_LIB_SUFFIX=-mt @@ -214,6 +214,9 @@ HEADERS += src/qt/trianglesgui.h \ src/util.h \ src/uint256.h \ src/kernel.h \ + src/net_bootstrap.h \ + src/tor/onion_v3.h \ + src/tor/tor_crypto_compat.h \ src/scrypt.h \ src/pbkdf2.h \ src/serialize.h \ @@ -448,6 +451,8 @@ SOURCES += src/qt/triangles.cpp src/qt/trianglesgui.cpp \ src/qt/plugins/mrichtexteditor/mrichtextedit.cpp \ src/noui.cpp \ src/kernel.cpp \ + src/net_bootstrap.cpp \ + src/tor/onion_v3.cpp \ src/scrypt-arm.S \ src/scrypt-x86.S \ src/scrypt-x86_64.S \