Not waiting for minimum number of block confirmations results in double spend
Description
Forks of length 1 (that is, a reorganization of one block in the blockchain) happen semifrequently in the Bitcoin chain. This occurs when two miners mine a winning nonce at nearly the same time. When this occurs, each full node will consider the first block it sees (from either miner) to be the best block for that block height. This would mean that for a short period of time, nodes will be divided on which block should be part of the canonical chain.
Some nodes will continue with block A, while the others will continue with block B. The way the nodes come to consensus on which chaintip to follow is by waiting to see which chaintip pulls ahead of the other by adding another block. When this occurs, all nodes that are not on this chaintip will reorganize to the longest chaintip.
Note that forks of length greater than 1 can also occur, but the probability of it occurring goes down as the length goes up. In Satoshi's Bitcoin whitepaper↗, it is recommended that applications wait for six block confirmations after a transaction before considering it to be part of the canonical chain (i.e., confirmed and irreversible). This assumes that a malicious attacker who is attempting to construct a malicious chaintip has access to ~10% of the total hashing power of all nodes on the chain.
Impact
In the Bitcoin client, there is a state variable for the amount of block confirmations that the code must wait before considering a transaction as confirmed.
type BitcoinChainClient struct {
// [ ... ]
confCount int64 // must wait this many blocks to be considered "confirmed"
// [ ... ]
}
However, this variable is not used anywhere in the code. The client assumes that any transaction it sees in new blocks are confirmed, and it will create and broadcast CCTXs immediately. This causes an issue, because if the Bitcoin chain reorganizes at any point in time after the CCTX has been created, the Bitcoin transaction will revert, but funds will have already been sent across to the zEVM.
To demonstrate this in the local testing environment, we used the invalidateblock
RPC call. The steps for the attack are as follows:
Send 1 BTC from the
smoketest
wallet to the Bitcoin TSS addressbcrt1q7cj32g6scwdaa5sq08t7dqn7jf7ny9lrqhgrwz
.Mine a block using the
generatetoaddress
RPC.Confirm that the transaction was included, either by checking the client logs for the CCTX or using a block explorer such as btc-rpc-explorer↗.
Use the
invalidateblock
RPC to invalidate the block that the transaction occurred in.
The above steps will result in a CCTX being generated for 1 BTC to be sent to the zEVM. However, due to the reorganization triggered in step 4, the 1 BTC that was sent in step 1 will remain in the smoketest
wallet. Therefore, 1 BTC will essentially have been minted in the zEVM.
Recommendations
The Bitcoin client should wait for a minimum number of block confirmations before assuming that a block has been confirmed. The recommended number is six block confirmations according to the Bitcoin whitepaper.
Remediation
This issue has been acknowledged by ZetaChain, and a fix was implemented in commit c276e903↗.