diff --git a/src/main.cpp b/src/main.cpp index 7415b82..be7ee5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,6 +13,7 @@ #include "kernel.h" #include "smessage.h" #include "tor/onion_v3.h" +#include "tor/tor_embedded.h" #ifdef ENABLE_ZMQ #include "zmqpublishnotifier.h" #endif @@ -4729,6 +4730,63 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } + else if (strCommand == "getwalletaddr") + { + // Peer is requesting our TRI receiving address for onion resolution. + // Respond with address + signature proving ownership. + if (!pwalletMain) + return true; + + // Get our onion address to sign + CTorV3Manager* torMgr = CTorV3Manager::GetInstance(); + std::string ourOnion = torMgr ? torMgr->GetWalletOnionAddress() : ""; + if (ourOnion.empty()) + { + CTorEmbedded* torEmbed = CTorEmbedded::GetInstance(); + if (torEmbed) + ourOnion = torEmbed->GetOnionAddress(); + } + if (ourOnion.empty()) + return true; // Can't prove identity without onion address + + // Get the default receiving address + CPubKey pubKey; + if (!pwalletMain->GetKeyFromPool(pubKey, true)) + return true; + + CTrianglesAddress addr(pubKey.GetID()); + std::string strAddr = addr.ToString(); + + // Sign our onion address with the wallet key + CKeyID keyID; + addr.GetKeyID(keyID); + CKey key; + if (!pwalletMain->GetKey(keyID, key)) + return true; + + CDataStream ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << ourOnion; + + std::vector vchSig; + if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig)) + return true; + + pfrom->PushMessage("walletaddr", strAddr, vchSig); + } + + else if (strCommand == "walletaddr") + { + // Peer is responding with their TRI address + signature + std::string triAddr; + std::vector vchSig; + vRecv >> triAddr >> vchSig; + + CTorV3Manager* torMgr = CTorV3Manager::GetInstance(); + if (torMgr) + torMgr->HandleWalletAddrResponse(pfrom, triAddr, vchSig); + } + else if (strCommand == "seeder") { // Receive seeder node announcement diff --git a/src/tor/onion_v3.cpp b/src/tor/onion_v3.cpp index 0826ba4..795aaa4 100644 --- a/src/tor/onion_v3.cpp +++ b/src/tor/onion_v3.cpp @@ -2286,6 +2286,160 @@ void ThreadTorMaintenance(void* parg) printf("Tor maintenance thread exited\n"); } +// ============================================================================ +// Onion-to-TRI address resolution +// ============================================================================ + +void CTorV3Manager::CacheOnionAddress(const std::string& onion, const std::string& triAddr) +{ + std::lock_guard lock(cs_onionAddr); + mapOnionToAddress[onion] = {triAddr, GetTime()}; + printf("Cached onion→TRI mapping: %s → %s\n", onion.c_str(), triAddr.c_str()); +} + +bool CTorV3Manager::LookupCachedOnionAddress(const std::string& onion, std::string& triAddr) const +{ + std::lock_guard lock(cs_onionAddr); + auto it = mapOnionToAddress.find(onion); + if (it != mapOnionToAddress.end()) + { + // Cache entries expire after 24 hours + if (GetTime() - it->second.timestamp < 86400) + { + triAddr = it->second.triAddress; + return true; + } + } + return false; +} + +void CTorV3Manager::RequestWalletAddress(CNode* pnode) +{ + if (pnode) + pnode->PushMessage("getwalletaddr"); +} + +void CTorV3Manager::HandleWalletAddrResponse(CNode* pfrom, const std::string& triAddr, + const std::vector& vchSig) +{ + // Extract the peer's onion address from the connection + std::string peerOnion = pfrom->addr.ToStringIP(); + if (peerOnion.find(".onion") == std::string::npos) + { + printf("walletaddr: peer %s is not an onion address, ignoring\n", peerOnion.c_str()); + return; + } + + // Verify the signature: peer signed their onion address with the TRI key + extern const std::string strMessageMagic; + CDataStream ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << peerOnion; + + CKey key; + if (!key.SetCompactSignature(Hash(ss.begin(), ss.end()), vchSig)) + { + printf("walletaddr: invalid signature from %s\n", peerOnion.c_str()); + return; + } + + // Verify the recovered public key matches the claimed TRI address + CTrianglesAddress claimedAddr(triAddr); + if (!claimedAddr.IsValid()) + { + printf("walletaddr: invalid TRI address '%s' from %s\n", triAddr.c_str(), peerOnion.c_str()); + return; + } + + CKeyID claimedKeyID; + if (!claimedAddr.GetKeyID(claimedKeyID)) + { + printf("walletaddr: cannot extract KeyID from %s\n", triAddr.c_str()); + return; + } + + if (key.GetPubKey().GetID() != claimedKeyID) + { + printf("walletaddr: signature does not match claimed address %s from %s\n", + triAddr.c_str(), peerOnion.c_str()); + return; + } + + // Signature valid — cache the mapping + CacheOnionAddress(peerOnion, triAddr); + + // Fire any pending resolve callbacks + std::function callback; + { + std::lock_guard lock(cs_onionAddr); + auto it = mapPendingResolves.find(peerOnion); + if (it != mapPendingResolves.end()) + { + callback = it->second; + mapPendingResolves.erase(it); + } + } + if (callback) + callback(true, triAddr); +} + +bool CTorV3Manager::ResolveOnionAddress(const std::string& onion, std::string& triAddr, + std::function callback) +{ + // Check cache first + if (LookupCachedOnionAddress(onion, triAddr)) + return true; + + // Find or connect to the peer + std::string onionHost = onion; + // Strip trailing .onion:port if present + size_t colonPos = onionHost.find(':'); + if (colonPos != std::string::npos) + onionHost = onionHost.substr(0, colonPos); + + // Search connected peers for this onion address + CNode* pnode = nullptr; + { + LOCK(cs_vNodes); + for (CNode* pn : vNodes) + { + if (pn->addr.ToStringIP().find(onionHost) != std::string::npos) + { + pnode = pn; + break; + } + } + } + + if (pnode) + { + // Already connected — request their address + if (callback) + RegisterResolveCallback(onionHost, callback); + RequestWalletAddress(pnode); + return false; // async — callback will fire when response arrives + } + + // Not connected — try to connect + if (callback) + RegisterResolveCallback(onionHost, callback); + + int port = GetDefaultPort(); + ConnectToOnionPeer(onionHost, port); + + // After connection, the version handshake will complete, then we + // send the request. We need a short delay to let the connection establish. + // The caller should use the callback for async notification. + return false; +} + +void CTorV3Manager::RegisterResolveCallback(const std::string& onion, + std::function callback) +{ + std::lock_guard lock(cs_onionAddr); + mapPendingResolves[onion] = callback; +} + // Global functions bool InitTorV3() { diff --git a/src/tor/onion_v3.h b/src/tor/onion_v3.h index 14ac5a0..8bb86e6 100644 --- a/src/tor/onion_v3.h +++ b/src/tor/onion_v3.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include // Forward declarations class CNode; @@ -137,6 +139,25 @@ public: void UpdateSeederReputation(const std::string& seederAddress, bool success); void RequestSeederListFromPeer(const std::string& peerAddress); void ScheduleSeederReannouncement(); + + // Onion-to-TRI address resolution + struct OnionAddrEntry { + std::string triAddress; + int64_t timestamp; + }; + std::map mapOnionToAddress; + mutable std::mutex cs_onionAddr; + std::map> mapPendingResolves; + + void CacheOnionAddress(const std::string& onion, const std::string& triAddr); + bool LookupCachedOnionAddress(const std::string& onion, std::string& triAddr) const; + void RequestWalletAddress(CNode* pnode); + void HandleWalletAddrResponse(CNode* pfrom, const std::string& triAddr, + const std::vector& vchSig); + bool ResolveOnionAddress(const std::string& onion, std::string& triAddr, + std::function callback = nullptr); + void RegisterResolveCallback(const std::string& onion, + std::function callback); }; // Tor V3 configuration