Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
501if ((i = m.find("ServiceID")) != m.end())502m_service_id = i->second;503if ((i = m.find("PrivateKey")) != m.end())504m_private_key = i->second;505}506if (m_service_id.empty()) {507LogWarning("tor: Error parsing ADD_ONION parameters:");508for (const std::string &s : reply.lines) {509LogWarning(" %s", SanitizeString(s));510}511return;512}513m_service = LookupNumeric(std::string(m_service_id+".onion"), Params().GetDefaultPort());514LogInfo("Got tor service ID %s, advertising service %s", m_service_id, m_service.ToStringAddrPort());515if (WriteBinaryFile(GetPrivateKeyFile(), m_private_key)) {516LogDebug(BCLog::TOR, "Cached service private key to %s", fs::PathToString(GetPrivateKeyFile()));517} else {518LogWarning("tor: Error writing service private key to %s", fs::PathToString(GetPrivateKeyFile()));519}520AddLocal(m_service, LOCAL_MANUAL);521// ... onion requested - keep connection open522} else if (reply.code == TOR_REPLY_UNRECOGNIZED) {523LogWarning("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)");524} else if (pow_was_enabled && reply.code == TOR_REPLY_SYNTAX_ERROR) {525LogDebug(BCLog::TOR, "ADD_ONION failed with PoW defenses, retrying without");526_conn.Command(MakeAddOnionCmd(m_private_key, m_target.ToStringAddrPort(), /*enable_pow=*/false),527[this](TorControlConnection& conn, const TorControlReply& reply) {528add_onion_cb(conn, reply, /*pow_was_enabled=*/false);529});530} else {531LogWarning("tor: Add onion failed; error code %d", reply.code);532}533}535void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& reply)536{537if (reply.code == TOR_REPLY_OK) {538LogDebug(BCLog::TOR, "Authentication successful");540// Now that we know Tor is running setup the proxy for onion addresses541// if -onion isn't set to something else.542if (gArgs.GetArg("-onion", "") == "") {543_conn.Command("GETINFO net/listeners/socks", std::bind_front(&TorController::get_socks_cb, this));544}546// Finally - now create the service547if (m_private_key.empty()) { // No private key, generate one548m_private_key = "NEW:ED25519-V3"; // Explicitly request key type - see issue #9214549}550// Request onion service, redirect port.551_conn.Command(MakeAddOnionCmd(m_private_key, m_target.ToStringAddrPort(), /*enable_pow=*/true),552[this](TorControlConnection& conn, const TorControlReply& reply) {553add_onion_cb(conn, reply, /*pow_was_enabled=*/true);554});555} else {556LogWarning("tor: Authentication failed");557}558}560/** Compute Tor SAFECOOKIE response.561*562* ServerHash is computed as:563* HMAC-SHA256("Tor safe cookie authentication server-to-controller hash",564* CookieString | ClientNonce | ServerNonce)565* (with the HMAC key as its first argument)566*567* After a controller sends a successful AUTHCHALLENGE command, the568* next command sent on the connection must be an AUTHENTICATE command,569* and the only authentication string which that AUTHENTICATE command570* will accept is:571*572* HMAC-SHA256("Tor safe cookie authentication controller-to-server hash",573* CookieString | ClientNonce | ServerNonce)574*575*/576static std::vector<uint8_t> ComputeResponse(std::string_view key, std::span<const uint8_t> cookie, std::span<const uint8_t> client_nonce, std::span<const uint8_t> server_nonce)577{578CHMAC_SHA256 computeHash((const uint8_t*)key.data(), key.size());579std::vector<uint8_t> computedHash(CHMAC_SHA256::OUTPUT_SIZE, 0);580computeHash.Write(cookie.data(), cookie.size());581computeHash.Write(client_nonce.data(), client_nonce.size());582computeHash.Write(server_nonce.data(), server_nonce.size());583computeHash.Finalize(computedHash.data());584return computedHash;585}587void TorController::authchallenge_cb(TorControlConnection& _conn, const TorControlReply& reply)588{589if (reply.code == TOR_REPLY_OK) {590LogDebug(BCLog::TOR, "SAFECOOKIE authentication challenge successful");591if (reply.lines.empty()) {592LogWarning("tor: AUTHCHALLENGE reply was empty");593return;594}595std::pair<std::string,std::string> l = SplitTorReplyLine(reply.lines[0]);596if (l.first == "AUTHCHALLENGE") {597std::map<std::string,std::string> m = ParseTorReplyMapping(l.second);598if (m.empty()) {599LogWarning("tor: Error parsing AUTHCHALLENGE parameters: %s", SanitizeString(l.second));600return;601}602std::vector<uint8_t> server_hash = ParseHex(m["SERVERHASH"]);603std::vector<uint8_t> server_nonce = ParseHex(m["SERVERNONCE"]);604LogDebug(BCLog::TOR, "AUTHCHALLENGE ServerHash %s ServerNonce %s", HexStr(server_hash), HexStr(server_nonce));605if (server_nonce.size() != 32) {606LogWarning("tor: ServerNonce is not 32 bytes, as required by spec");607return;608}610std::vector<uint8_t> computed_server_hash = ComputeResponse(TOR_SAFE_SERVERKEY, m_cookie, m_client_nonce, server_nonce);611if (computed_server_hash != server_hash) {612LogWarning("tor: ServerHash %s does not match expected ServerHash %s", HexStr(server_hash), HexStr(computed_server_hash));613return;614}616std::vector<uint8_t> computedClientHash = ComputeResponse(TOR_SAFE_CLIENTKEY, m_cookie, m_client_nonce, server_nonce);617_conn.Command("AUTHENTICATE " + HexStr(computedClientHash), std::bind_front(&TorController::auth_cb, this));618} else {619LogWarning("tor: Invalid reply to AUTHCHALLENGE");620}621} else {622LogWarning("tor: SAFECOOKIE authentication challenge failed");623}624}626void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorControlReply& reply)627{628if (reply.code == TOR_REPLY_OK) {629std::set<std::string> methods;630std::string cookiefile;631/*632* 250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/home/x/.tor/control_auth_cookie"633* 250-AUTH METHODS=NULL634* 250-AUTH METHODS=HASHEDPASSWORD635*/636for (const std::string &s : reply.lines) {637std::pair<std::string,std::string> l = SplitTorReplyLine(s);638if (l.first == "AUTH") {639std::map<std::string,std::string> m = ParseTorReplyMapping(l.second);640std::map<std::string,std::string>::iterator i;641if ((i = m.find("METHODS")) != m.end()) {642std::vector<std::string> m_vec = SplitString(i->second, ',');643methods = std::set<std::string>(m_vec.begin(), m_vec.end());644}645if ((i = m.find("COOKIEFILE")) != m.end())646cookiefile = i->second;647} else if (l.first == "VERSION") {648std::map<std::string,std::string> m = ParseTorReplyMapping(l.second);649std::map<std::string,std::string>::iterator i;650if ((i = m.find("Tor")) != m.end()) {651LogDebug(BCLog::TOR, "Connected to Tor version %s", i->second);652}653}654}655for (const std::string &s : methods) {656LogDebug(BCLog::TOR, "Supported authentication method: %s", s);657}658// Prefer NULL, otherwise SAFECOOKIE. If a password is provided, use HASHEDPASSWORD659/* Authentication:660* cookie: hex-encoded ~/.tor/control_auth_cookie661* password: "password"662*/663std::string torpassword = gArgs.GetArg("-torpassword", "");664if (!torpassword.empty()) {665if (methods.contains("HASHEDPASSWORD")) {666LogDebug(BCLog::TOR, "Using HASHEDPASSWORD authentication");667ReplaceAll(torpassword, "\"", "\\\"");668_conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind_front(&TorController::auth_cb, this));669} else {670LogWarning("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available");671}672} else if (methods.contains("NULL")) {673LogDebug(BCLog::TOR, "Using NULL authentication");674_conn.Command("AUTHENTICATE", std::bind_front(&TorController::auth_cb, this));675} else if (methods.contains("SAFECOOKIE")) {676// Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie677LogDebug(BCLog::TOR, "Using SAFECOOKIE authentication, reading cookie authentication from %s", cookiefile);678std::pair<bool,std::string> status_cookie = ReadBinaryFile(fs::PathFromString(cookiefile), TOR_COOKIE_SIZE);679if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) {680// _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), std::bind_front(&TorController::auth_cb, this));681m_cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end());682m_client_nonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0);683GetRandBytes(m_client_nonce);684_conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(m_client_nonce), std::bind_front(&TorController::authchallenge_cb, this));685} else {686if (status_cookie.first) {687LogWarning("tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec", cookiefile, TOR_COOKIE_SIZE);688} else {689LogWarning("tor: Authentication cookie %s could not be opened (check permissions)", cookiefile);690}691}692} else if (methods.contains("HASHEDPASSWORD")) {693LogWarning("tor: The only supported authentication mechanism left is password, but no password provided with -torpassword");694} else {695LogWarning("tor: No supported authentication method");696}697} else {698LogWarning("tor: Requesting protocol info failed");699}700}702void TorController::connected_cb(TorControlConnection& _conn)703{704m_reconnect_timeout = RECONNECT_TIMEOUT_START;705// First send a PROTOCOLINFO command to figure out what authentication is expected706if (!_conn.Command("PROTOCOLINFO 1", std::bind_front(&TorController::protocolinfo_cb, this)))707LogWarning("tor: Error sending initial protocolinfo command");708}710void TorController::disconnected_cb(TorControlConnection& _conn)711{712// Stop advertising service when disconnected713if (m_service.IsValid())714RemoveLocal(m_service);715m_service = CService();716if (!m_reconnect)717return;719LogDebug(BCLog::TOR, "Not connected to Tor control port %s, retrying in %.2f s",720m_tor_control_center, m_reconnect_timeout.count());721_conn.Disconnect();723m_interrupt.sleep_for(std::chrono::duration_cast<std::chrono::milliseconds>(m_reconnect_timeout));724m_reconnect_timeout = std::min(m_reconnect_timeout * RECONNECT_TIMEOUT_EXP, RECONNECT_TIMEOUT_MAX);725}727fs::path TorController::GetPrivateKeyFile()728{729return gArgs.GetDataDirNet() / "onion_v3_private_key";730}732CService DefaultOnionServiceTarget(uint16_t port)733{734struct in_addr onion_service_target;735onion_service_target.s_addr = htonl(INADDR_LOOPBACK);736return {onion_service_target, port};737}