If you were a miner, what are the steps you would take to create the extra (21,000,012.5th) bitcoin?

Where in the source code is this exactly (link)?

Why can't this be done by a non-miner?

Also, which forks are/were vulnerable?

  • 21 upvotes, very meta, good job everyone Commented Nov 29, 2023 at 19:19

1 Answer 1


If you were a miner, what are the steps you would take to create the extra (21,000,012.5th) bitcoin?

Where in the source code is this exactly (link)?

There are two components to CVE-2018-17144. There is a crash bug and an inflation bug. Both are triggered by almost the same scenario: a transaction contains an input multiple times.

In general, how this would work is as follows: lets suppose a miner has an unspent output A for 1 BTC. They create a transaction with that input in twice, so input 1 spends from output A and input 2 also spends from output A. The output of that transaction has a value of 2 BTC. Note how the output's value is larger than the value of output A, but if you had output A twice, the value is correct.

The miner would then take this transaction and include it in a block that he is mining. Once the miner find a block with his transaction included in it, he broadcasts it to the Bitcoin network.

When a Bitcoin Core 0.14.x node receives this block, it will validate the block, but it will skip the duplicate input check because of the false parameter on this line. So the transaction the miner made will pass this step of validation, and the other transaction validation steps, including input script validation, until it reaches this loop. In this loop, the inputs to the transaction are being marked as spent in the UTXO database. The first time the duplicated input is seen, it is marked as spent. But the second time it is seen, the coin is already marked as spent so coins->vout[nPos].IsNull() will be true. This means that it will go into this if statement and subsequently hit the assert statement that follows. The assert causes the software to crash.

For Bitcoin Core 0.15.0 - 0.16.2, the behavior is different. This is due to the change in how the UTXO database is structured. Everything is largely the same until the same loop is reached. Here, instead of returning whether the output was spent, SpendCoin actually returns whether the input exists in the database. So the first time, it will pass as expected, but the second time, instead of returning false, it still returns true.

Looking at SpendCoin, you can see that it only returns false when it is unable to fetch the coin (object representing a UTXO) from the database. With the new database structure, this makes sense as the output should be removed from the database when it is spent. But, if you look a few lines down, you see that it only deletes the coin when it is marked as FRESH. In the case the coin was FRESH, SpendCoin would delete the object on the first pass so the second pass the coin would not be found and thus it would return false. This triggers the assert following the function call causing the node to shutdown.

If the coin was not FRESH, the coin object itself is not deleted, but its contents are cleared. This means that the second time the input is seen, if the coin was not FRESH, SpendCoin would still return true as the object still exists in memory, which means that it passes the assert that follows the SpendCoin (which caused the crash when the coin was not FRESH). Then validation continues as normal, and the output this transaction created is added to the UTXO database, which means that money that shouldn't exist now exists in the UTXO database.

So now the question is, when are UTXOs marked as FRESH? They are marked FRESH when they are added to the UTXO database. But the UTXO database is still only in memory (as a cache). When it is saved to disk, the entries in memory are then no longer marked as FRESH. This saving to disk happens after every block (as well as at other times, but that is not important).

Thus, if a miner has an output that was part of a transaction that has already confirmed, and he spends the output twice in the same transaction (so the transaction has two inputs that refer to the same output), and this transaction is not broadcast to the network but instead included in a block that he mines, he is able to create a new output that has twice the value of the output that he spent, thereby creating coins.

Why can't this be done by a non-miner?

The reason that this cannot be done by a non-miner is because transactions that are received outside of blocks are still checked for duplicate inputs. The transaction will be rejected as invalid and not added to the node's mempool, so the transaction will never get into a block. It is only transactions with duplicate inputs that get into blocks that trigger this vulnerability, and thus only miners can do this as they must knowingly insert an invalid transaction into their block.

Also, which forks are/were vulnerable?

Any fork whose software includes commit eecffe50efc3944d713c701fa375dacbf17fb7cf. This would mean any software forked from or pulled in changes from Bitcoin Core after November 10th, 2016.

  • Would a node that receives the block as a compact block check for the duplicate?
    – Murch
    Commented Sep 22, 2018 at 18:16
  • 3
    No, it would not. The transaction with the duplicate input would not be in the mempool so it would be sent along with the block (as part of a blocktxn message). Such transactions are not run through the mempool acceptance code. Instead the block is reconstructed and run through the normal block acceptance code.
    – Ava Chow
    Commented Sep 22, 2018 at 19:04
  • Great write-up, thank you! It seems to me having a bool flag such as fCheckDuplicateInputs seems pretty unfortunate. It is quite easy to set it to an invalid value. I'm just wondering if we shouldn't be more explicit and verbose in those critical parts
    – tsusanka
    Commented Sep 26, 2018 at 7:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.