I really thought this had been fixed, because I commented the problem of using IsFromMe / IsMine to the developers in the github forums (https://github.com/bitcoin/bitcoin/pull/1648), but no. Not fixed, so I’d better alert people before they can be tracked…
The attack requires you to connect to the victim’s node and be disconnected from the victim’s node.
The attack allows only to know the Bitcoin public address related to an IP. If a peer is using Tor, then there is no loss of anonymity. The simplest attack allows the attacker to test if an existing Bitcoin address is owned by a peer. The address to test can be a user’s public address, or an address taken from blockchain. So an advanced attack is to extract all addresses stored in the blockchain and test them one by one, in order find the ones that belong to a node.
Simple Attack (test an address)
1. For the desired public address, find a transaction in the blockchain with an output that sends funds to that address. If no output is found, send some BTC (as little as possible, of course) to the address, and use your own output.
2. Create a transaction Tx with a single input that spends this prevout. Add one output. Put zero fees. (GetMinFee() will return zero)
3. Send the Tx multiple times very “quickly” to the victim node, to trigger the penny-flood prevention.
4. The victim node may:
– Reject the transaction because it penny-floods (normally). In this case you will be still able to send more transactions.
– Disconnect you (DoS detection) you since you are not providing the correct signature scripts (this is the case when you have hit an owned address)
If you cannot use small transactions to trigger the penny-flood detection, you can add additional prevouts to make the transaction bigger
Scanning attack (find an address)
There are currently around 1M addresses with non-zero balance (an estimation based on http://bitcoin.stackexchange.com/questions/3524/how-many-unspent-transaction-outputs-are-there). The attack is similar to the previous one but the attack creates multiple Txs, one for every address found in the blockchain.
It’s also possible to test many addresses at once in a single transaction. An attacker can discover which of the addresses are owned by a peer by the following procedure:
1. For each address, the choose one unspent output Out(i)
2. Group the 1M unspent outputs in 1632 transactions Tx(j), each one containing 613 previns. Each transaction would be approximately 26999 Kbytes long. Add one output. Put zero fees. (GetMinFee() will return zero)
3. Send each Tx(j) one by one to the victim node, test for disconnection.
4. If you have been disconnected, repeat recursively a binary search grouping the prevouts of this transaction until you find a single one that disconnects you. Note that you may need to add additional dummy previns or prevouts to the transaction if it becomes too small to be blocked by the peer with the penny-flooding protection system.
These attack is a little stronger on clients prior to current 0.8 test builds. In current 0.8 test builds you need to refer to unspent prevouts in order to access the oracle. In prior versions, you can use spent outputs, but you need to trigger the DoS disconnection by other means, such as including inputs whose added value overflows.
Timing Attack (variation)
Also the attacker can use a timing attack using spent outputs in all versions (except current 0.8 test builds):
1. Create a transaction with 100 inputs that belong to the attacker, and the spent output you want to test.
2. Send this Tx. Send immediately a “Ping” command.
3. If the victim’s node responds with the command “Pong” almost immediately, then the penny-flooding has stopped the Tx.
If the victim’s node responds 100 msec later that expected (required to validate the 100 signatures), then penny-flooding prevention has been skipped, and you’ve hit the correct public address of the peer.
This attack also works on current 0.8 test builds (but only for spent outputs).
Here is a more formal disclosure:
The problem affects all versions of the Satoshi Bitcoin Client.
The method CTxMemPool::accept() has a protection against ‘penny-flooding’. The protection prevents a transaction with very low fees and big size to be forwarded or even stored in the memory pool.
The problem is that this protection tests if a transaction is from a wallet owned by the user, and if it’s the case, then the protection is skipped. This fact can be used by the attacker as an oracle.
You can protect very easily from the attack by rising the “-limitfreerelay” value to something like 1000000. Of course, that will also disable the penny-flooding protection.
Until the next vuln report,
Yours sincerely, Sergio.