Merge bitcoin/bitcoin#33414: tor: enable PoW defenses for automatically created hidden services

c68e3d2c57 doc: add release notes for Tor PoW defenses (Vasil Dimov)
4bae84c94a doc: add a hint to enable PoW defenses to manual hidden services (Vasil Dimov)
4c6798a3d3 tor: enable PoW defenses for automatically created hidden services (Vasil Dimov)
fb993f7604 tor, fuzz: reuse constants instead of duplicating (Vasil Dimov)

Pull request description:

  Enable [PoW defenses](https://tpo.pages.torproject.net/onion-services/ecosystem/technology/security/pow/) for hidden services that we create via Tor Control using the [`ADD_ONION` command](https://spec.torproject.org/control-spec/commands.html#add_onion).

  The ability to do that has been added in [tor-0.4.9.2-alpha](02c1804446). Previous versions return a syntax error to the `ADD_ONION` command with `PoWDefensesEnabled=1`, so the approach here is to try with PoW and if we get syntax error, then retry without PoW.

  Also update `doc/tor.md` with a hint on enabling PoW on manually configured Tor hidden services.

ACKs for top commit:
  willcl-ark:
    ACK c68e3d2c57
  fjahr:
    tACK c68e3d2c57
  sedited:
    ACK c68e3d2c57

Tree-SHA512: 56f57bc770b89389c35a4c0bc2a28804d17b1479ecd4d9b764695d6c9d2994425aee759e71658d7b57088bbe43ce90b94fb972f66d79ef903e0c1a4d6c4562f3
This commit is contained in:
merge-script
2026-03-23 14:54:47 +08:00
5 changed files with 50 additions and 12 deletions

View File

@@ -0,0 +1,9 @@
Notable changes
===============
P2P and network changes
-----------------------
Tor hidden services that are created automatically by Bitcoin Core will have
[PoW defenses](https://tpo.pages.torproject.net/onion-services/ecosystem/technology/security/pow/)
enabled if the Tor daemon supports that. (#33414)

View File

@@ -181,6 +181,10 @@ Add these lines to your `/etc/tor/torrc` (or equivalent config file):
HiddenServiceDir /var/lib/tor/bitcoin-service/
HiddenServicePort 8333 127.0.0.1:8334
# If `tor --list-modules` shows "pow: yes", then enable PoW protection.
# It is available in tor-0.4.8.1-alpha and newer when configured with
# `./configure --enable-gpl`.
HiddenServicePoWDefensesEnabled 1
The directory can be different of course, but virtual port numbers should be equal to
your bitcoind's P2P listen port (8333 by default), and target addresses and ports

View File

@@ -49,10 +49,13 @@ FUZZ_TARGET(torcontrol, .init = initialize_torcontrol)
CallOneOf(
fuzzed_data_provider,
[&] {
tor_control_reply.code = 250;
tor_control_reply.code = TOR_REPLY_OK;
},
[&] {
tor_control_reply.code = 510;
tor_control_reply.code = TOR_REPLY_UNRECOGNIZED;
},
[&] {
tor_control_reply.code = TOR_REPLY_SYNTAX_ERROR;
},
[&] {
tor_control_reply.code = fuzzed_data_provider.ConsumeIntegral<int>();
@@ -65,7 +68,10 @@ FUZZ_TARGET(torcontrol, .init = initialize_torcontrol)
CallOneOf(
fuzzed_data_provider,
[&] {
tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply);
tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply, /*pow_was_enabled=*/true);
},
[&] {
tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply, /*pow_was_enabled=*/false);
},
[&] {
tor_controller.auth_cb(dummy_tor_control_connection, tor_control_reply);

View File

@@ -53,9 +53,6 @@ const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:" + ToString(DEFAULT_TOR_CONT
static const int TOR_COOKIE_SIZE = 32;
/** Size of client/server nonce for SAFECOOKIE */
static const int TOR_NONCE_SIZE = 32;
/** Tor control reply code. Ref: https://spec.torproject.org/control-spec/replies.html */
static const int TOR_REPLY_OK = 250;
static const int TOR_REPLY_UNRECOGNIZED = 510;
/** For computing serverHash in SAFECOOKIE */
static const std::string TOR_SAFE_SERVERKEY = "Tor safe cookie authentication server-to-controller hash";
/** For computing clientHash in SAFECOOKIE */
@@ -426,10 +423,20 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe
}
}
void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply)
static std::string MakeAddOnionCmd(const std::string& private_key, const std::string& target, bool enable_pow)
{
// Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports.
return strprintf("ADD_ONION %s%s Port=%i,%s",
private_key,
enable_pow ? " PoWDefensesEnabled=1" : "",
Params().GetDefaultPort(),
target);
}
void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply, bool pow_was_enabled)
{
if (reply.code == TOR_REPLY_OK) {
LogDebug(BCLog::TOR, "ADD_ONION successful\n");
LogDebug(BCLog::TOR, "ADD_ONION successful (PoW defenses %s)", pow_was_enabled ? "enabled" : "disabled");
for (const std::string &s : reply.lines) {
std::map<std::string,std::string> m = ParseTorReplyMapping(s);
std::map<std::string,std::string>::iterator i;
@@ -456,6 +463,12 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe
// ... onion requested - keep connection open
} else if (reply.code == TOR_REPLY_UNRECOGNIZED) {
LogWarning("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)");
} else if (pow_was_enabled && reply.code == TOR_REPLY_SYNTAX_ERROR) {
LogDebug(BCLog::TOR, "ADD_ONION failed with PoW defenses, retrying without");
_conn.Command(MakeAddOnionCmd(private_key, m_target.ToStringAddrPort(), /*enable_pow=*/false),
[this](TorControlConnection& conn, const TorControlReply& reply) {
add_onion_cb(conn, reply, /*pow_was_enabled=*/false);
});
} else {
LogWarning("tor: Add onion failed; error code %d", reply.code);
}
@@ -477,9 +490,10 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
private_key = "NEW:ED25519-V3"; // Explicitly request key type - see issue #9214
}
// Request onion service, redirect port.
// Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports.
_conn.Command(strprintf("ADD_ONION %s Port=%i,%s", private_key, Params().GetDefaultPort(), m_target.ToStringAddrPort()),
std::bind_front(&TorController::add_onion_cb, this));
_conn.Command(MakeAddOnionCmd(private_key, m_target.ToStringAddrPort(), /*enable_pow=*/true),
[this](TorControlConnection& conn, const TorControlReply& reply) {
add_onion_cb(conn, reply, /*pow_was_enabled=*/true);
});
} else {
LogWarning("tor: Authentication failed");
}

View File

@@ -24,6 +24,11 @@ constexpr int DEFAULT_TOR_CONTROL_PORT = 9051;
extern const std::string DEFAULT_TOR_CONTROL;
static const bool DEFAULT_LISTEN_ONION = true;
/** Tor control reply code. Ref: https://spec.torproject.org/control-spec/replies.html */
constexpr int TOR_REPLY_OK{250};
constexpr int TOR_REPLY_UNRECOGNIZED{510};
constexpr int TOR_REPLY_SYNTAX_ERROR{512}; //!< Syntax error in command argument
void StartTorControl(CService onion_service_target);
void InterruptTorControl();
void StopTorControl();
@@ -138,7 +143,7 @@ public:
/** Callback for GETINFO net/listeners/socks result */
void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply);
/** Callback for ADD_ONION result */
void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply);
void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply, bool pow_was_enabled);
/** Callback for AUTHENTICATE result */
void auth_cb(TorControlConnection& conn, const TorControlReply& reply);
/** Callback for AUTHCHALLENGE result */