A common criticism of Opt-In Replace-by-Fee (RBF) has been that we need to give the ecosystem more time to implement detection of opt-in RBF transactions, arguing that if we don’t users will be put at a significantly greater risk of being ripped off by malicious double-spends. For this criticism to be valid, user and merchant wallets must already be able to detect situations where a transaction may be maliciously double-spent, and warn their users appropriately. On the other hand, if they can’t do that, then opt-in RBF doesn’t change the situation anyway: an attacker doesn’t need to use it anyway to rip people off, and when wallet authors fix that they can trivially add opt-in RBF detection anyway with one or two lines of code.
For merchants payment providers have responded quickly, with Shapeshift.io and Coinbase - among others - publicly announcing that they’ve implemented opt-in RBF detection as part of their zero-conf risk-mitigation strategies.
That leaves user wallets. We tested a selection of the eight most popular wallets out there, including Blockchain.info, Coinbase, Bitcoin Wallet for Android, Mycelium, Electrum, and others. What we found was every wallet tested did a terrible job of protecting users from zero-conf double-spends:
Wallets not only failed to warn users that a double-spend might happen, the majority even failed to warn their users that a double-spend has happened.
Half the wallets tested could be double-spent by an attacker with nearly zero technical sophistication with 100% probability of success.
The other half could be double-spent with about 25% success rates, again by an attacker with nearly zero technical sophistication.
With the above in mind, deployment of opt-in RBF will have zero effect on users’ vulnerability to double-spends. Instead we must strongly recommend that users assume until a transaction is confirmed if the sender chooses to reverse it they can. And in an ironic counter-example to Betteridge’s law of headlines, the answer is yes.
What do we mean by “protect”? We’ll use a simple scenario where Alice is selling bitcoins to Bob in a person-to-person trade:
Alice sends bitcoins to Bob with transaction #1.
Bob’s wallet displays the unconfirmed transaction, convincing Bob the transaction will eventually confirm.
Bob gives Alice the cash.
Alice now sends transaction #2 with a higher fee.
Alice succeeds if transaction #1 shows up on Bob’s wallet software and transaction #2 is the one that gets mined. She fails if Bob’s wallet software does not show transaction #1, or at least warns him that it can be easily double-spent by Alice in a way that he’s likely to see and understand. Alice also fails if Bob’s wallet software informs him of the double-spend when Alice sends it, also in a way he’s likely to see and understand.
We assume Alice is not very technically sophisticated, beyond having the
ability to run custom double-spend-capable wallet software that someone else
wrote. Thus we will only look at the simplest way to double-spend: a low-fee transaction
followed by a higher-fee transaction. To that end we will use the author’s
either the default settings, or with the fee of the first transaction being
changed from the default using the
--fee1 option. The script was run on a
full-RBF node using the author’s
a fork of Bitcoin Core
v0.12.0rc1. Here’s a
typical attempt, this one (successfully) against a Blockchain.info wallet:
$ ./doublespend.py 1JmAWEELXFV8cYPqrZibfoRkLrME6TTYNn 0.005 DEBUG:root:Delta fee: 0.00500086 DEBUG:root:Adding new input 0bc206fd7d9f6587dd8bbc72c53e10dba8415ea3474baab3aec383d50e00b81e:0 with value 0.10807506 BTC DEBUG:root:Delta fee: 0.00500248 DEBUG:root:Payment tx 01000000011eb8000ed583c3aeb3aa4b47a35e41a8db103ec572bc8bdd87659f7dfd06c20b000000006a473044022050eeeddbb3cfae33a84eca74acaf632d524ba083fc4ff247bc4a0d0b2d015df202201396c8d875a4d5447da01a6ec24672f5960f9fb5a1b7eabb312b03370198d4e30121036d7839d3b7eea61627c021e95efd997aa7e2935173534dc94f3170ad5cf51376ffffffff02ba469d00000000001976a914b50858356652f81310a00d5bb4d78afba93d917388ac20a10700000000001976a914c2d45fc6c177d2a100733382c5916872a188d34b88ac00000000 INFO:root:Payment tx size: 0.225 KB, fees: 0.00000248, 0.00001102 BTC/KB INFO:root:Sent payment tx: d20217d5fe2f1cdb086131f967eecfaaa7f4834e6548c08052d76bf08d3333e8 DEBUG:root:Delta fee: 0.000191 DEBUG:root:Double-spend tx 01000000011eb8000ed583c3aeb3aa4b47a35e41a8db103ec572bc8bdd87659f7dfd06c20b000000006a4730440220077b2ea3cb4af23ca6e76094fc6c67edd8d8a1c55c5e5c27568bd264282b69bc02202a40fcb9dfd984591f6cafdf01942a7718a2746f82faf6fdb9150352c9d1ea0d0121036d7839d3b7eea61627c021e95efd997aa7e2935173534dc94f3170ad5cf51376ffffffff01369ea400000000001976a914b50858356652f81310a00d5bb4d78afba93d917388ac00000000 INFO:root:Double-spend tx size: 0.191 KB, fees: 0.000191, 0.001 BTC/KB INFO:root:Sleeping for 15 seconds INFO:root:Sent double-spend tx: f1c713988ad62ebd8c5bec99d3ba559ba6ef41a3a4e48df941d1ae9960dc3611
Even without a GUI these tools require relatively little technical sophistication to use - the full-RBF fork preferentially peers with other full-RBF nodes automatically, and the doublespend.py script does the rest. Adding this double-spend functionality to an existing open-source mobile wallet would only be a day or two of work.
Version 1.8.4 2016011302 was tested. Interestingly, the first double-spend attempt at 11µBTC/KB did not show up in the UI immediately; a second attempt using 50µBTC/KB did show up immediately. However, Airbitz also displayed the first attempt about two minutes after broadcasting the first transaction - well after the double-spend was broadcast - resulting in the following:
Even after the double-spend confirmed, the double-spent transaction was still displayed as “pending”:
Bitcoin Wallet For Android
Version 4.46 was tested with a 11µBTC/KB transaction, which displayed immediately:
Yet again, ever after the double-spend was broadcast, and later confirmed, the UI remained the same; even multiple days later the transaction is still displayed in the UI.
The web interface was tested on 2016-01-22 with a 11µBTC/KB transaction. Blockchain.info displayed it immediately, with no visible warning that the transaction was unlikely to confirm in a reasonable amount of time:
After the double-spend was broadcast the UI remained the same; even after the double-spend confirmed the UI gave no indication that the transaction was double-spent, unless the user clicked on the transaction to open up the detailed view:
The web-wallet interface was tested on 2016-01-22 with a 11µBTC/KB transaction. Coinbase displayed it immediately, with no visible warning that the transaction was unlikely to confirm in a reasonable amount of time:
As with Blockchain.info, even after the double-spend was broadcast, and later confirmed, the UI remained the same. Coinbase even notified the email associated with that account of the transaction after the double-spend was broadcast:
There seems to be some kind of transaction expiration process, because as of two days later the transaction had been silently removed from the account’s list of transactions.
Version 1.8.2 was tested on Android. Copay doesn’t seem to display transactions with less than 50µBTC/KB fees, so it took three attempts with 50µBTC/KB transactions to get a succesful double-spend. While Copay didn’t detect the double-spends immediately, when the double-spend confirmed it did display a clear “transaction invalid” “possible double-spend” notification in the UI. However, when the author re-opened Copay to get a screenshot of that UI while writing this paragraph, the double-spent transaction was deleted, giving the user no indication the transaction failed.
Version 2.5.4 was tested in the default configuration. While previous versions displayed 11µBTC/KB transactions prior to confirmation, as of this version it appears that transactions must pay at least 50µBTC/KB to be displayed immediately. It took three attempts to get a successful double-spend at 50µBTC/KB, upon which the double-spent transaction was simply removed from the UI leaving the user no indication that the transaction had failed.
Version 1.63 was tested on Android in the default SPV configuration. GreenBits doesn’t seem to display transactions with less than 50µBTC/KB fees, so it took four attempts with 50µBTC/KB transactions (one attempt shown below, and another two failures on another wallet). Again, even after the double-spend has confirmed, GreenBits failed to notify the user:
Version 2.6.0 was tested on Android. Mycelium doesn’t seem to display transactions with less than 50µBTC/KB fees, so it took two attempts to get a successful double-spend. Mycelium didn’t detect the double-spend immediately, however after the first confirmation of the double-spend transaction it did display this unintuitive error message:
Eventually the double-spent transaction was deleted entirely from the transaction history, leaving no indication that it had ever happened.