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:

1. 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.

2. Half the wallets tested could be double-spent by an attacker with nearly zero technical sophistication with 100% probability of success.

3. 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.

## Methodology

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:

1. Alice sends bitcoins to Bob with transaction #1.

2. Bob’s wallet displays the unconfirmed transaction, convincing Bob the transaction will eventually confirm.

3. Bob gives Alice the cash.

4. 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 doublespend.py script from replace-by-fee-tools in 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 replace-by-fee-v0.12.0rc1, 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
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
INFO:root:Double-spend tx size: 0.191 KB, fees: 0.000191, 0.001 BTC/KB
INFO:root:Sleeping for 15 seconds


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.

## Results

### Airbitz

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.

### Blockchain.info

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:

### Coinbase

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.

### Copay

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.

### Electrum

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.

### GreenBits

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:

### Mycelium

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.