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

pull vasild wants to merge 4 commits into bitcoin:master from vasild:tor_pow changing 5 files +50 −12
  1. vasild commented at 10:40 am on September 17, 2025: contributor

    Enable PoW defenses for hidden services that we create via Tor Control using the ADD_ONION command.

    The ability to do that has been added in tor-0.4.9.2-alpha. 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.

  2. DrahtBot added the label P2P on Sep 17, 2025
  3. DrahtBot commented at 10:40 am on September 17, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33414.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK willcl-ark

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34158 (torcontrol: Remove libevent usage by fjahr)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. dergoegge commented at 12:52 pm on September 17, 2025: member
    Should we then also add PoW to the connections that we make to other nodes running behind hidden services?
  5. willcl-ark commented at 2:27 pm on September 17, 2025: member

    Should we then also add PoW to the connections that we make to other nodes running behind hidden services?

    Reading the linked FAQ, the feature still supports “older clients” (which don’t have PoW defence capability), but they may take a lower priority when a service considers itself under DoS. So no PoW is required on the client side.

    When the client-side tor is new-enough, my understanding is that the puzzle-solving is automatically handled by Tor, and doesn’t need client-side changes to the connection code, as it happens during the introduction. But I am not 100% certain.

  6. fanquake commented at 1:03 pm on September 23, 2025: member
    @laanwj you might have some thoughts here?
  7. DrahtBot added the label Needs rebase on Dec 2, 2025
  8. fanquake commented at 12:27 pm on February 16, 2026: member
    What is the status of this?
  9. in src/test/fuzz/torcontrol.cpp:56 in 5aefa08017 outdated


    willcl-ark commented at 9:34 pm on February 16, 2026:
    I think tor_control_reply_code = 512 could be added here, to hit this expected path more frequently than random?

    vasild commented at 11:12 am on February 19, 2026:
    Right, good catch! Done and also moved the TOR_REPLY_* constants to torcontrol.h so this fuzz test can use them instead of hardcoding numbers. Thanks!
  10. willcl-ark commented at 9:55 pm on February 16, 2026: member

    Approach and lightly-tested ACK on PoW-enabled Tor (version 0.4.8.22).

    02026-02-16T21:44:26Z [tor] Get SOCKS port command yielded 127.0.0.1:9050
    12026-02-16T21:44:26Z [tor] Configuring onion proxy for 127.0.0.1:9050
    22026-02-16T21:44:26Z [tor] ADD_ONION failed with PoW defenses, retrying without
    32026-02-16T21:44:26Z [tor] ADD_ONION successful (PoW defenses disabled)
    

    Seems very reasonable to me to implement this configuration in order that we fare better during DoS attacks on the Tor network. Noting for others that as far as I read, due to the dynamic difficulty the (tor) PoW is ~ free on idle, and the cost only applies during an attack, which seems nice.

    I quite like the pragmatism of “detecting” the tor version via the failure mode. It appears that the 512 failure happens early on the Tor side, before any service is created. So failing and retrying is “clean”.

    What is the status of this?

    Looks like it needs a pretty trivial rebase at the moment is all.

  11. vasild force-pushed on Feb 19, 2026
  12. vasild commented at 10:50 am on February 19, 2026: contributor

    8a526d39d8a00edff2361ceaa012574d1337b77b...206da5e5e420ab43857e4d15ddb7d1c603d6e762: rebase due to conflicts

    What is the status of this?

    Needed a rebase.

  13. tor, fuzz: reuse constants instead of duplicating
    `src/torcontrol.cpp` used to define some constants that are used
    explicitly in `src/torcontrol.cpp` and implicitly in
    `src/test/fuzz/torcontrol.cpp` by duplicating their values.
    
    Move the constants to `src/torcontrol.h` and reuse them in
    `src/test/fuzz/torcontrol.cpp` to avoid duplication and magic
    numbers.
    fb993f7604
  14. tor: enable PoW defenses for automatically created hidden services
    Enable PoW defenses [1] for hidden services that we create via
    Tor Control using the `ADD_ONION` command [2].
    
    The ability to do that has been added in tor-0.4.9.2-alpha [3]. 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.
    
    [1] https://tpo.pages.torproject.net/onion-services/ecosystem/technology/security/pow/
    [2] https://spec.torproject.org/control-spec/commands.html#add_onion
    [3] https://gitlab.torproject.org/tpo/core/tor/-/commit/02c18044464bfe45f168b55297a785244094cfd5
    4c6798a3d3
  15. doc: add a hint to enable PoW defenses to manual hidden services 4bae84c94a
  16. doc: add release notes for Tor PoW defenses c68e3d2c57
  17. vasild force-pushed on Feb 19, 2026
  18. DrahtBot added the label CI failed on Feb 19, 2026
  19. vasild commented at 11:11 am on February 19, 2026: contributor
    206da5e5e420ab43857e4d15ddb7d1c603d6e762...c68e3d2c57dcab5cea22ad5986fcd2b147a7daaa: address suggestion
  20. DrahtBot removed the label Needs rebase on Feb 19, 2026
  21. willcl-ark commented at 11:34 am on February 19, 2026: member

    Great, the range-diff looks good:

     0git range-diff 2d6a0c464912c325faf35d4ad28b1990e828b414..8a526d3 8ee24d764a2820259fe42f8def93fd8a2c36a4cf..c68e3d2c57d
     1-:  ----------- > 1:  fb993f76047 tor, fuzz: reuse constants instead of duplicating
     21:  5aefa080178 ! 2:  4c6798a3d38 tor: enable PoW defenses for automatically created hidden services
     3    @@ Commit message
     4         [3] https://gitlab.torproject.org/tpo/core/tor/-/commit/02c18044464bfe45f168b55297a785244094cfd5
     5
     6      ## src/test/fuzz/torcontrol.cpp ##
     7    +@@ src/test/fuzz/torcontrol.cpp: FUZZ_TARGET(torcontrol, .init = initialize_torcontrol)
     8    +             [&] {
     9    +                 tor_control_reply.code = TOR_REPLY_UNRECOGNIZED;
    10    +             },
    11    ++            [&] {
    12    ++                tor_control_reply.code = TOR_REPLY_SYNTAX_ERROR;
    13    ++            },
    14    +             [&] {
    15    +                 tor_control_reply.code = fuzzed_data_provider.ConsumeIntegral<int>();
    16    +             });
    17     @@ src/test/fuzz/torcontrol.cpp: FUZZ_TARGET(torcontrol, .init = initialize_torcontrol)
    18              CallOneOf(
    19                  fuzzed_data_provider,
    20    @@ src/test/fuzz/torcontrol.cpp: FUZZ_TARGET(torcontrol, .init = initialize_torcont
    21                      tor_controller.auth_cb(dummy_tor_control_connection, tor_control_reply);
    22
    23      ## src/torcontrol.cpp ##
    24    -@@ src/torcontrol.cpp: static const int TOR_NONCE_SIZE = 32;
    25    - /** Tor control reply code. Ref: https://spec.torproject.org/control-spec/replies.html */
    26    - static const int TOR_REPLY_OK = 250;
    27    - static const int TOR_REPLY_UNRECOGNIZED = 510;
    28    -+static const int TOR_REPLY_SYNTAX_ERROR = 512; //!< Syntax error in command argument
    29    - /** For computing serverHash in SAFECOOKIE */
    30    - static const std::string TOR_SAFE_SERVERKEY = "Tor safe cookie authentication server-to-controller hash";
    31    - /** For computing clientHash in SAFECOOKIE */
    32     @@ src/torcontrol.cpp: void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe
    33          }
    34      }
    35    @@ src/torcontrol.cpp: void TorController::get_socks_cb(TorControlConnection& _conn
    36     @@ src/torcontrol.cpp: void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe
    37              // ... onion requested - keep connection open
    38          } else if (reply.code == TOR_REPLY_UNRECOGNIZED) {
    39    -         LogPrintf("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)\n");
    40    +         LogWarning("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)");
    41     +    } else if (pow_was_enabled && reply.code == TOR_REPLY_SYNTAX_ERROR) {
    42     +        LogDebug(BCLog::TOR, "ADD_ONION failed with PoW defenses, retrying without");
    43     +        _conn.Command(MakeAddOnionCmd(private_key, m_target.ToStringAddrPort(), /*enable_pow=*/false),
    44    -+                      std::bind(&TorController::add_onion_cb,
    45    -+                                this,
    46    -+                                std::placeholders::_1,
    47    -+                                std::placeholders::_2,
    48    -+                                /*pow_was_enabled=*/false));
    49    ++                      [this](TorControlConnection& conn, const TorControlReply& reply) {
    50    ++                          add_onion_cb(conn, reply, /*pow_was_enabled=*/false);
    51    ++                      });
    52          } else {
    53    -         LogPrintf("tor: Add onion failed; error code %d\n", reply.code);
    54    +         LogWarning("tor: Add onion failed; error code %d", reply.code);
    55          }
    56     @@ src/torcontrol.cpp: void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
    57                  private_key = "NEW:ED25519-V3"; // Explicitly request key type - see issue [#9214](/bitcoin-bitcoin/9214/)
    58    @@ src/torcontrol.cpp: void TorController::auth_cb(TorControlConnection& _conn, con
    59              // Request onion service, redirect port.
    60     -        // Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports.
    61     -        _conn.Command(strprintf("ADD_ONION %s Port=%i,%s", private_key, Params().GetDefaultPort(), m_target.ToStringAddrPort()),
    62    --            std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2));
    63    +-            std::bind_front(&TorController::add_onion_cb, this));
    64     +        _conn.Command(MakeAddOnionCmd(private_key, m_target.ToStringAddrPort(), /*enable_pow=*/true),
    65    -+                      std::bind(&TorController::add_onion_cb,
    66    -+                                this,
    67    -+                                std::placeholders::_1,
    68    -+                                std::placeholders::_2,
    69    -+                                /*pow_was_enabled=*/true));
    70    ++                      [this](TorControlConnection& conn, const TorControlReply& reply) {
    71    ++                          add_onion_cb(conn, reply, /*pow_was_enabled=*/true);
    72    ++                      });
    73          } else {
    74    -         LogPrintf("tor: Authentication failed\n");
    75    +         LogWarning("tor: Authentication failed");
    76          }
    77
    78      ## src/torcontrol.h ##
    79    +@@ src/torcontrol.h: static const bool DEFAULT_LISTEN_ONION = true;
    80    + /** Tor control reply code. Ref: https://spec.torproject.org/control-spec/replies.html */
    81    + constexpr int TOR_REPLY_OK{250};
    82    + constexpr int TOR_REPLY_UNRECOGNIZED{510};
    83    ++constexpr int TOR_REPLY_SYNTAX_ERROR{512}; //!< Syntax error in command argument
    84    +
    85    + void StartTorControl(CService onion_service_target);
    86    + void InterruptTorControl();
    87     @@ src/torcontrol.h: public:
    88          /** Callback for GETINFO net/listeners/socks result */
    89          void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply);
    902:  a61080aef87 = 3:  4bae84c94af doc: add a hint to enable PoW defenses to manual hidden services
    913:  8a526d39d8a = 4:  c68e3d2c57d doc: add release notes for Tor PoW defenses
    

    Just realised though that my tor, although it has PoWDefenses compiled in, does not support it via ADD_ONION yet

     0❯ tor --list-modules
     1relay: yes
     2dirauth: yes
     3dircache: yes
     4pow: yes
     5
     6❯ nc 127.0.0.1 9051
     7AUTHENTICATE ""
     8250 OK
     9ADD_ONION NEW:ED25519-V3 PoWDefensesEnabled=1 Port=38333,127.0.0.1:38333
    10512 Bad arguments to ADD_ONION: Unrecognized keyword argument "PoWDefensesEnabled"
    

    I think it might only be for pre-configured services unti addition to the ONION keyword in 0.4.9.5. That said, it does show the fallback mechanism, even in this edge case, works as intended… (and in fact that querying the binary modules would have been a bug here?).

  22. vasild commented at 11:50 am on February 19, 2026: contributor

    and in fact that querying the binary modules would have been a bug here?

    I think yes, better to actually check if the ADD_ONION command supports pow (like in this PR).

  23. willcl-ark approved
  24. willcl-ark commented at 12:07 pm on February 19, 2026: member

    ACK c68e3d2c57dcab5cea22ad5986fcd2b147a7daaa

    Tested with two versions of tor. ON 0.4.8.22 automatic PoWDefense via ONION message (correctly) does not work and falls back to without.

    On 0.4.9.5 I see:

    02026-02-19T12:04:04Z torcontrol thread start
    12026-02-19T12:04:04Z [tor] Reading cached private key from /xxxxxxxx/onion_v3_private_key
    22026-02-19T12:04:04Z [tor] Successfully connected!
    32026-02-19T12:04:04Z [tor] Connected to Tor version 0.4.9.5
    42026-02-19T12:04:04Z [tor] Supported authentication method: NULL
    52026-02-19T12:04:04Z [tor] Using NULL authentication
    62026-02-19T12:04:04Z [tor] Authentication successful
    72026-02-19T12:04:04Z [tor] Get SOCKS port command yielded 127.0.0.1:9050
    82026-02-19T12:04:04Z [tor] Configuring onion proxy for 127.0.0.1:9050
    92026-02-19T12:04:04Z [tor] ADD_ONION successful (PoW defenses enabled)
    

    The changes look clean and correct to me.

  25. DrahtBot removed the label CI failed on Feb 19, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-03-09 21:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me