Update 3: How the attacker is using the bug in validation code to mint coins
It seems that Zcoin (XZC) has a bug which has allowed an attacker to magically summon 370,000 coins out of thin air. Reading the discussion over at HackerNews, I saw that there was a commit to fix the problem and it was a one character patch titled “Urgent fix for pool owners to prevent malicious zerospend txs”.
Could it be so simple? Intrigued, I spent an hour or two digging into the source code. I’m not familair with Zcoin but I know my way around the Bitcoin source code (which Zcoin is a fork of) and I have a high-level understanding of the Zerocoin protocol (which Zcoin claims to implement).
Here are some notes I made:
First, for those unfamiliar with the Zerocoin protocol,
- A Zcoin Mint transaction transfers funds from the public pool to the private pool.
- A Zcoin Spend transaction transfers funds from the private pool to the public pool.
Next, what does the single character patch do? The change from
unsignedint MAX_SPEND_ZC_TX_PER_BLOCK = 1;
to
unsignedint MAX_SPEND_ZC_TX_PER_BLOCK = 0;
means that Zcoin Spend transactions are no longer mined in a block.
The section of code which used to include the transaction in a block is here. Why the previous limit on Zcoin Spend transactions was 1 is an open question.
Since these Zcoin Spend transactions can no longer be mined, this means that legitimate holders of private Zcoin can no longer transfer their private funds to the public pool until the restriction is lifted.
Of course, the real intent is to stop the attacker digitally printing Zcoin, and this patch does solve that problem since the fraudulent transactions will no longer be mined.
Important: the points above are only true if we assume that all miners and nodes use the Zcoin reference client. There doesn’t actually seem to be a consensus rule which actually forbids the inclusion of 1 or 100 ZCoin Spend transactions in a block. You can check yourself by looking at CBlock::CheckBlock. Maybe I missed something.
With a block explorer, if you examine the history of blocks you will see many blocks with 100 or 150 XZC value, where there are two transactions.
Drilling down into the raw transaction data you can see the attacker’s transaction is listed after the coinbase transaction.
Zcoin Spend transactions are identified via the OP_ZEROCOINSPEND opcode. On the output side you can see the address at which the attacker is sending funds to.
So how does someone create a Zcoin Spend transaction?
Well, the only way an honest end-user can create a Zcoin Spend transaction is to use the rpc call, spendzerocoin. This in turn invokes CWallet::CreateZerocoinSpendTransaction() which in the process of creating the transaction, will check for double-spending and perform sanity checks.
A node which receives a Zcoin Spend transaction from a peer, or receives a mined block containing such a transaction, will of course validate the Zcoin Spend transaction in CTransaction::CheckTransaction(). Invalid transactions should be rejected, somewhere in this block of code.
Which begs the question, if the attacker is crafting bogus OP_ZEROCOINSPEND transactions which spend non-existent private funds, shouldn’t the fraudulent transaction be detected in CheckTransaction()?
Zcoin’s blog post hints at this:
The bug from the typo error allowed the attacker to reuse his existing valid proofs to generate additional Zerocoin spend transactions.
As discussed earlier, the single character patch posted to GitHub could potentially stop the attacker from making any further financial gain, but it would require all miners to upgrade and a consensus rule added to prevent valid blocks from including Zcoin Spend transactions. As it stands, the patch on its own does not prevent the attacker from writing their own miner which does include Zcoin Spend transactions.
However, since the intent of the patch is to effectively prevent OP_ZEROCOINSPEND transactions from being mined, it seems to me that the real bug lies somewhere in CheckTransaction()’s validation code.
To demonstrate that the bug has been fixed, all the fraudulent Zcoin Spend transactions found on the blockchain should be run through a patched CheckTransaction and detected as invalid.
Whether or not those transactions stay valid in a future hard fork is a non-technical decision for Zcoin policy-makers.
Despite the severity of the hack, we will not be forfeiting or blacklisting any coins.
It’s understandable why this decision was made. First, it protects honest folk who purchased Zcoin on exchanges. Second, the numbers look better over time. Today, the legitimate monetary base of Zcoin is approaching 1.1 million but the attacker has expanded that to 1.47 million. So 25% of Zcoin has been minted by the attacker. In the long run, assuming the problem is resolved, this percentage will trend towards 1.7% since Zcoin inherits Bitcoin’s money supply limit of 21 million coins.
If I’ve got this completely wrong and none of the above is true, intrepid bug-hunters might want to jump down the RSA rabbit hole…
Update: There are two Zcoins. Both Zcoin.io and Zcoin.tech have similar looking websites and share co-founders. Looking at the code it appears the problem exists in both projects as they share the same validation code.
Update 2: Zcoin have now tweeted that a fix is available. Pull requests #72 and #73 confirm a bug in CheckTransaction() where an incorrect constant was being used. Also, the default miner will no longer include Zcoin Spend transactions. As discussed, this puts ordinary users at a disadvantage, since there is no consensus rule which forbids custom mining software from doing so. It’s also not clear what happens if a user reindexes as existing bogus transactions will no longer validate.
Update 3: So this is probably how the attacker is minting coins (also see thread from one of the authors of the Zerocoin protocol).
A Zcoin Spend transaction claims that a value of 100 should move from the private pool to the attacker’s address at the public pool. Here is the attacker’s transaction in a recent block.
Such a transaction is verified in the block of code beginning main.cpp, line 1484
else if (txout.nValue == libzerocoin::ZQ_WILLIAMSON * COIN) {
This ZQ_WILLIAMSON constant is defined in libzerocoin and has a value of 100.
enum CoinDenomination { ZQ_LOVELACE = 1, ZQ_GOLDWASSER = 10, ZQ_RACKOFF = 25, ZQ_PEDERSEN = 50, ZQ_WILLIAMSON = 100 // Malcolm J. Williamson, // the scientist who actually invented // Public key cryptography };
Back to main.cpp. During verification, the code should check that a private coin of ZQ_WILLIAMSON (100) denomination is being spent. It doesn’t. Instead it checks that a ZQ_PEDERSEN (50) is being spent.
See here, main.cpp, line 1523-1525:
if (pubCoinItem.denomination == libzerocoin::ZQ_PEDERSEN && pubCoinItem.id == pubcoinId && pubCoinItem.nHeight != -1) { ... libzerocoin::PublicCoin pubCoinTemp(ZCParams, pubCoinItem.value, libzerocoin::ZQ_PEDERSEN);
So the attacker creates a Zcoin Spend transaction which consumes a valid private coin worth 50. On the output side, instead of specifying a UTXO value of 50, they set a value of 100 which passes the broken validation code. The transaction is now deemed valid, accepted into the memory pool and later mined into a block.
Since executing the attack requires having private funds, the attacker should also be creating OP_ZEROCOINMINT transactions, perhaps via the RPC call mintzerocoin. If the attacker has created 370,000 coins, there should be a fair number of these OP_ZEROCOINMINT transactions in the blockchain. However, from a cursory look at tht block explorer, I haven’t found any. Perhaps I missed them or there’s a bug in the explorer. If I were to hazard a guess, there could be problems with proof verfication and tracking of private coin serial numbers which enable the attacker to reuse an existing proof to double-spend private coins.