Double-spend attacks are often thought of as a theoretical possibility, but an uncommon occurrence. In my opinion, it's reckless to assume that this will always be the case. For the sake of this article, I will separate double-spend attacks into two categories:

There is another variant of the unconfirmed transaction replacement but without the RBF feature. However, it involves a fair bit of uncertainty and luck. This article will instead focus mostly on double-spending using RBF.

Change of History

The first category of double-spend attacks involves rewriting blocks to effectively erase a confirmed transaction from history. Once this is done, the inputs used in the original transaction can be double-spent however the attacker sees fit. The more confirmations a transaction has, the more difficult it becomes to change it. This is why it is recommended to wait at least a few confirmations before considering high-value transactions as final.

Note that it is not necessary, but highly unlikely, to be able to achieve a change-of-history double-spend attack without 51% of the network hashrate. But due to the size of the Bitcoin network, this is a very expensive and risky attack to manage successfully. There have been instances of this attack in recent history, but on much smaller networks - see the Bitcoin Gold double-spend attack from 2018.

For the reasons outlined above, you can rest assured knowing that change-of-history double-spend attacks will likely remain an uncommon occurrence for the foreseeable future. This article is going to focus on the second category of double-spend attacks which affects only zero-confirmation transactions. So let's move on.

Unconfirmed Transaction Replacement

A transaction that has not yet been included in a block is considered "unconfirmed" - also referred to as a zero-confirmation transaction. Once confirmed in a block, it is not possible to replace it in the manner described here.

To successfully double-spend by replacing an unconfirmed transaction, you will need to be able to:

  • Build raw transactions that use the replace-by-fee (RBF) feature
  • Broadcast raw transactions reliably
  • Fetch unspent transaction outputs (UTXO) for a given address
  • Get network fee estimate

All this can be done for you with the help of a script or, even more conveniently, as an app. To illustrate the point, I created PayNoWay to demonstrate to business owners what a practical double-spend attack can look like.

After using the form to send the "payment" (with the blue button on the left), the transaction is visible in a block explorer:

But then after using the red button to broadcast a second transaction, we can see that the block explorer has detected our double-spend:

Both transactions are visible in the block explorer (both marked as "double spent"). Once the second transaction has been confirmed in a block, the first transaction is no longer visible in the block explorer at all:

And the funds have been returned to the originating address, less some fees:

Almost every person to whom I have demonstrated the double-spend attack was shocked at just how easy it was. This attack is within the capabilities of many cryptocurrency users. Anyone who is accepting transactions as payment for goods or services without waiting for at least one confirmation is vulnerable and should be aware of the risks.

Mitigations

There is no magical solution to this problem. Nodes and transaction relays can't know if a transaction is actually a malicious double-spend. A transaction might look suspicious because it's consuming the same exact inputs but has changed its outputs. But that doesn't mean it's necessarily a malicious double-spend. The solution is likely in the UI / UX layer of the businesses' point-of-sale applications. I can imagine a couple features that could help mitigate the risks to businesses accepting zero-confirmation transactions:

  • Warn visually whenever receiving an RBF transaction - This is a feature that we added to CryptoTerminal, a terminal application for in-person cryptocurrency payments. The way that we implemented this was to show a warning dialog with an option to either accept or ignore the payment. The idea is that at least the business owner (or employee) is made aware that there is a double-spend possibility with this particular transaction.
  • Monitor received RBF transactions for double-spends - After receiving an RBF transaction, monitor the transaction until it is confirmed in a block. If a double-spend occurs, then the app should "throw a fit" - alarm noises and annoying visual cues might be enough. We have not yet implemented this feature in CryptoTerminal, but it is on our roadmap.
  • Use the Lightning Network for small, quick payments - The Bitcoin Lightning Network does not require confirmations for the transaction recipient to trust that they have been paid.

The purpose of this article is to bring awareness to this over-looked issue. Ignoring it or pretending it does not exist will only harm end-users who do not have the expertise needed to fully understand the technical details of these new systems. We should try our best to design and build applications that are easy and safe for these users.

I'd love to hear about other solutions to this problem, so please feel free to reach out to me with your ideas or suggestions. Thanks for your time and have a great new year!