-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Broadcast own transactions only via short-lived Tor or I2P connections #29415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29415. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
This comment was marked as abuse.
This comment was marked as abuse.
bd82629
to
74ba7c7
Compare
@1440000bytes, thanks for asking! There is some discussion at #27509 (the previous attempt on this).
Yes, it is. See below.
Sending the transaction over clearnet reveals the IP address/geolocation of the sender. A spy with many connections to the network could try to guess who was the originator. So, why not send it to our Tor peers only? Because it is relatively easy for a spy to fingerprint and link clearnet and Tor connections to the same peer. That is, a long running connection over Tor could be linked to a long running clearnet connection. This is why the proposed changes open a short-lived connection that does not reveal any of the identity of the sender. Would this benefit nodes that don't have clearnet connections, e.g. Tor/I2P-only nodes? Yes! In the case where the sender sends two otherwise unrelated transactions over the same long-running Tor connection, the recipient will know that they have the same origin, even though they are not related on-chain. Using single shot connections fixes that too.
Linked in the OP, thanks! |
v2 Transport will be enabled by default in the next release (#29347). If there were eventually a change to force clearnet transactions over v2 transport (so the details of the communications were encrypted), would that solve the same problem that this PR is aiming to solve? |
@epiccurious, p2p encryption "solves" the spying from intermediate routers on clearnet (aka man-in-the-middle). Tor, I2P and CJDNS solve that too. While this PR uses only Tor and I2P it would solve that problem. But there is more - it will as well solve issues with spying bitcoin nodes. |
Suggested by: David Gumberg (bitcoin#29415 (comment))
Co-authored-by: brunoerg <brunoely.gc@gmail.com>
We will open a short-lived connection to a random Tor or I2P peer, send our transaction to that peer and close the connection.
Normally `ConnectNode()` could choose whether to use a proxy and which one. Make it possible to override this from the callers and same for `OpenNetworkConnection()` - pass down the proxy to `ConnectNode()`. Document both functions.
Implement opening `ConnectionType::PRIVATE_BROADCAST` connections with the following properties: * Only to Tor or I2P (or IPv4/IPv6 through the Tor proxy, if provided) * Open such connections only when requested and don't maintain N opened connections of this type. * Since this is substantially different than what `OpenNetworkConnection()` does, open the private broadcast connections from a different thread instead of modifying `OpenNetworkConnection()` to also open those types of connections.
Rename `PeerManager::RelayTransaction()` to `PeerManager::ScheduleTxForBroadcastToAll()`. The transaction is not relayed when the method returns. It is only scheduled for broadcasting at a later time. Also, there will be another method which only schedules for broadcast to Tor or I2P peers.
Previously the `bool relay` argument to `BroadcastTransaction()` designated: ``` relay=true: add to the mempool and broadcast to all peers relay=false: add to the mempool ``` Extend this with a third option to not add the transaction to the mempool and broadcast privately. This is a non-functional change - the new third option is not handled inside `BroadcastTransaction()` and is not used by any of the callers. The idea for the new `node/types.h` and the comments in it by Ryan. Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
Extend `PeerManager` with a transaction storage and a new method `ScheduleTxForPrivateBroadcast()` which: * adds a transaction to that storage and * calls `CConnman::PrivateBroadcastAdd()` to open dedicated privacy connections that will pick an entry from the transaction storage and broadcast it.
Change the order in which code snippets are executed as a result of receiving the `VERSION` message. Move the snippets that do `MakeAndPushMessage()` near the end. This will help with handling of private broadcast connections - they do not require any of that. This is a non-functional change.
For connections of type `ConnectionType::PRIVATE_BROADCAST`: * After receiving VERACK, relay a transaction from the list of transactions for private broadcast and disconnect * Don't process any messages after VERACK * Don't send any messages other than the minimum required for the transaction relay
Remove the transaction from the list of transactions to broadcast after we receive it from the network. Only remove the transaction if it is the same as the one we sent: both txid and wtxid match. Don't remove transactions that have the same txid and different wxtid. Such transactions show that some of the private broadcast recipients malleated the witness and the transaction made it back to us. The witness could be either: * invalid, in which case the transaction will not be accepted in anybody's pool; or * valid, in which case either the original or the malleated transaction will make it to nodes' mempools and eventually be mined. Our response is to keep broadcasting the original. If the malleated transaction wins then we will eventually stop broadcasting the original when it gets stale and gets removed from the "to broadcast" storage cause it is not acceptable in our mempool.
Periodically check for stale transactions in peerman and if found, reschedule new connections to be opened by connman for broadcasting them.
dbd76e6
to
2bd4714
Compare
I am back from some afk time. |
2bd4714
to
8fbff52
Compare
The functional test has grown quite extensive. It is a further load on reviewers. That is in the last two commits. If you don't feel like reviewing it, you can omit those last two commits and review up to bb814cd |
8fbff52
to
4327327
Compare
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
I tried sending a tx with fee rate 0.1 sat/vbyte using 4327327. I connected to 3 nodes and disconnected after receiving the PONG, and then attempted one more connection every 10 minutes. |
To improve privacy, broadcast locally submitted transactions (from the
sendrawtransaction
RPC) to the P2P network only via Tor or I2P short-lived connections, or to IPv4/IPv6 peers but through the Tor network.Introduce a new connection type for private broadcast of transactions with the following properties:
PING
is sent and after receivingPONG
the connection is closedPONG
)Broadcast transactions submitted via
sendrawtransaction
using this new mechanism, to a few peers. Keep doing this until we receive back this transaction from one of our ordinary peers (this takes about 1 second on mainnet).The transaction is stored in peerman and does not enter the mempool.
Once we get an
INV
from one of our ordinary peers, then the normal flow executes: we request the transaction withGETDATA
, receive it with aTX
message, put it in our mempool and broadcast it to all our existent connections (as if we see it for the first time).After we receive the full transaction as a
TX
message, in reply to ourGETDATA
request, only then consider the transaction has propagated through the network and remove it from the storage in peerman, ending the private broadcast attempts.The messages exchange should look like this:
Whenever a new transaction is received from
sendrawtransaction
RPC, the node will send it to a few (NUM_PRIVATE_BROADCAST_PER_TX
) recipients right away. If after 10-15 mins we still have not heard anything about the transaction from the network, then it will be sent to 1 more peer (seePeerManagerImpl::ReattemptPrivateBroadcast()
).A few considerations:
How to test this?
Thank you, @stratospher and @andrewtoth!
Start
bitcoind
with-privatebroadcast=1 -debug=privatebroadcast
.Create a wallet and get a new address, go to the Signet faucet and request some coins to that address:
Get a new address for the test transaction recipient:
Create the transaction:
Finally, send the transaction:
High-level explanation of the commits
New logging category and config option to enable private broadcast
log: introduce a new category for private broadcast
init: introduce a new option to enable/disable private broadcast
Implement the private broadcast connection handling on the
CConnman
side:net: introduce a new connection type for private broadcast
net: support overriding the proxy selection in ConnectNode()
net: implement opening PRIVATE_BROADCAST connections
Prepare
BroadcastTransaction()
for private broadcast requests:net_processing: rename RelayTransaction to better describe what it does
node: change a tx-relay on/off flag to a tri-state
net_processing: store transactions for private broadcast in PeerManager
Implement the private broadcast connection handling on the
PeerManager
side:net_processing: reorder the code that handles the VERSION message
net_processing: handle ConnectionType::PRIVATE_BROADCAST connections
net_processing: stop private broadcast of a transaction after round-trip
net_processing: retry private broadcast
Engage the new functionality from
sendrawtransaction
:rpc: use private broadcast from sendrawtransaction RPC if -privatebroadcast is ON
Independent test framework improvement:
test: move create_malleated_version() to messages.py for reuse
New functional test that exercies some of the new code:
test: add functional test for local tx relay
Add an intermediate step that sends
INV
message and waits for a request for the transaction. If reviewers like this, then I will squash it into the relevant commit, or otherwise drop it:fixup! net_processing: handle ConnectionType::PRIVATE_BROADCAST connections
This PR would close the following issues:
#3828 Clients leak IPs if they are recipients of a transaction
#14692 Can't configure bitocoind to only send tx via Tor but receive clearnet transactions
#19042 Tor-only transaction broadcast onlynet=onion alternative
#24557 Option for receive events with all networks, but send transactions and/or blocks only with anonymous network[s]?
#25450 Ability to broadcast wallet transactions only via dedicated oneshot Tor connections
#32235 Tor: TX circuit isolation
Issues that are related, but (maybe?) not to be closed by this PR:
#21876 Broadcast a transaction to specific nodes
#28636 new RPC: sendrawtransactiontopeer
Further extensions:
submitpackage
RPC do the private broadcast as well, draft diff in the comment below, thanks @ismaelsadeeq!A previous incarnation of this can be found at #27509. It puts the transaction in the mempool and (tries to) hide it from the outside observers. This turned out to be too error prone or maybe even impossible.