Don’t trust, verify: A Bitcoin Private case study

Bitcoin Private (BTCP) is a fork-merge of Bitcoin and ZClassic (ZCL, a fork of ZCash that removed the founders’ reward). BTCP defined its initial supply according to the sum of the outstanding supply of Bitcoin at the time (16.8m), ZClassic (3.4m) and a small 62,500-unit miner program. This was intended to give it an initial supply of ~20.4m BTCP, with a decaying miner reward, capping the total supply at 21m units as with Bitcoin.

However, 2.04m additional units were covertly minted during the import of the Bitcoin UTXOs and sent to the BTCP shielded pool, bringing the initial supply to 22.6 million, contradicting the whitepaper and all of the materials published by the team. Three hundred thousand units of the covert premine were moved out of the shielded pool towards what appear to be exchanges. Ultimately the lack of uptake of BTCP by the recipients of the airdrop meant that those additional 300k transparent units today represent close to 10% of the BTCP supply in circulation, with 1.80M covertly minted units remaining in the shielded pool. 

*   *  *

While BTCP was a “merge fork” conjoining the ZClassic and Bitcoin states, the basis of the BTCP fork was the ZClassic ledger, not Bitcoin. At the agreed-upon snapshot block, Bitcoin’s state (the registry of unspent outputs) was imported into the parent ZClassic chain by mining thousands of blocks with transactions creating the Bitcoin unspent outputs, effectively forking it into BTCP at block 272,992. At the end of this import, an extra 62,500 BTCP were minted in accordance with the “Voluntary Miner Contribution Program”. Once this import was over, Bitcoin Private’s own history began.
The snapshot heights and statistics at that height for each of the parent chains is as follows:

Bitcoin ZClassic
Snapshot Height 511,346 272,991
Unspent outputs 59,188,317 1,679,093
Outstanding supply 16,891,665 BTC 3,393,798 transparent ZCL + 18,378 shielded ZCL

Therefore, after the Bitcoin UTXO set import, there were:

 16.891M + 3.393M + 0.018M + 0.0625M = 20.3645M BTCP outstanding

These figures match the abstract of the Bitcoin Private whitepaper:

This means approximately 20.4 million out of 21 million coins will exist at fork time.

Additionally, the mining reward after the fork was set to 1.5625 BTCP per block, halving every 210,000 blocks. At the time of writing, BTCPs block height was 446,997. Since the fork started mining at height 278,458, there have been 141,542 blocks mined with a reward of 1.5625 BTCP (up to and including block #420,000) and 26,996 blocks with a reward of 0.78125 BTCP.
Therefore, we expected the current outstanding supply (at the time of writing) to be:

20.3645M + 141,542 * 1.5625 + 26,996 * 0.78125 = 20.607M BTCP

CoinMarketCap indicates a circulating supply of 20.525M BTCP, which seems to be computed as the expected figure minus the 62.5k BTCP from the “Voluntary Miner Contribution Program” and the initial ZCL shielded pool.
In verifying these figures, we ran a BTCP node (version 1.0.12-1), and made a call to the RPC method gettxoutsetinfo, yielding the following results:

{
"height":446997,
"bestblock":"00000000c34dab5c33925227e1210c85d12466ea6f6efa072ef801a44449c335",
"transactions":54003755,
“txouts":55824434,
"bytes_serialized":3352165260,
"hash_serialized":"40535f9a5cfdc3cfd9db6c70f20d85f43e1614180a8b13f293c53fa053e0f8e3",
"total_amount":20840886.44311337
}

At the time of writing, our full node reported an outstanding supply of 20.841M BTCP. This contradicted both CMC and the expected figures from the initial supply at fork time combined with the subsequent block rewards.
Several hypotheses can be brought forward to explain this discrepancy:

    • Our node is not on the correct chain – someone is somehow feeding our node garbage
    • There’s a bug in the gettxoutsetinfo code
    • The mining reward changed since the publication of the whitepaper making our original estimate wrong
    • Zk-snarks have been broken and someone is minting BTCP in the shielded pool
    • There was a hidden premine

Let’s try to see which explanation is correct:

Not on the correct chain?

The explorer running on btcprivate.org has the same block hash for height 446997 as our node. 

Bug in gettxoutsetinfo?

The main function called to get the data (GetStats()) hasn’t been edited since the BTCP fork, therefore such a bug would also affect BTC and its derivatives, but the figures from our BTC node match our expected value.  

Has the mining reward changed?

It hasn’t been altered beyond the defined schedule.

Are zk-Snarks broken?

It is very implausible that this would have been exploited on BTCP and not on ZEC, which is much more valuable and uses the same parameters.

Covert premine?

Yes.
Blocks 272,992 to 278,457 were used to import the BTC UTXO set (snapshotted at height 511,346). The BTC UTXO to import was made up of 59,188,317 unspent outputs whose combined value was 16,891,665 BTC. Block 278,458 contained the 62,500 BTCP from the “Voluntary Miner Contribution Program” (this is not the premine, this was a publicly disclosed charitable developer fund.)
Each block in this import range contained 10,000 outputs, each corresponding to a BTC UTXO. (10,000 outputs in 6000 or so blocks yields the 60m or so outputs necessary to introduce the Bitcoin state to ZClassic.) This is the expected size. However, in this range there also appeared some special blocks containing 10,400 outputs. These 400 extra outputs were 50 BTC each. There were 102 of those abnormal, larger-than-expected blocks.

So in the import period we have 102 extra-large blocks, each with 400 unexpected outputs in addition to the 10,000 outputs expected. Each of those additional outputs contained 50 BTC. This gives us 102 * 400 * 50 = 2,040,000 BTCP.

Zoom in on the blocks between 274,590 and 275,017.

That’s an extra 2.040M BTCP created, undeclared in the whitepaper, not present in the BTC UTXO set, that were minted nonetheless. Today, there are 1,807,549 BTCP in the shielded pool and 20,841,921 BTCP in the unshielded UTXO set, making the current BTCP supply 22,649,470, compared to a claimed supply of 20,631,984 at time of publication. 

Was the premine acknowledged by the developer team?

Unambiguously, no. The developer team repeatedly claimed that there was no premine or developer tax on Bitcoin Private. ZClassic, created by largely the same set of developers, was a fork of ZCash with the founders reward (20% of the total supply eventually vesting with the founding team and backers) forked out. Bitcoin Private was meant to preserve the absence of a founder tax and also airdrop the supply onto the Bitcoin UTXO set.

And of course Bitcoin Private’s own block explorer lists the circulating supply figure with the premine excluded. This is why we are calling it a covert premine. Could this be the “larger fraud that was never disclosed to the community,” in the words of the BTCP creator (source)?

There’s another wrinkle too. Our node only found an additional 200k BTCP beyond what was signaled by the developer team, not the 2.040M coins that were imported (but not part of the BTC UTXO set). Where did the other coins go?

So what happened to the premine?

Since ZCL is a fork of ZCash, it contains a privacy feature where some addresses are shielded. What happens between shielded addresses is private – observers can’t determine addresses or transaction amounts. However, observers can watch money enter and exit the shielded pool when it interacts with non shielded ones.

At the time of the ZCL snapshot, the shielded pool contained a meager 17k ZCL. At the time of writing, the pool contained 1.807M BTCP. Adding the shielded pool to the unshielded portion makes the numbers add up:
    1.8M (shielded pool) + 20.8M (non shielded supply) = 22.6M BTCP
    20.35M (imported from BTC & ZCL) + 2.04M (premine) + 0.25M (new issuance from mining since the fork) = 22.6M

The hidden premine was sent to shielded addresses on April 29th 2018. Around 300k BTCP were withdrawn from the shielded pool between July 11th and August 18th. At that time, the value of BTCP went from $10 and $3. If those coins were sold on the open market, they could have netted a profit on the order of $1M to $3M. 

This address received 136k BTCP from the shielded pool in July/August 2018. This is more BTCP that there was in the shielded pool before the premine was sent to it, and it also too much BTCP to have plausibly been mined (mined BTCP has to go through the shielded pool). It is therefore highly probable that it came from the premine. Interestingly, most of the premine remains unspent, sitting in the shielded pool.

One interesting facet of the fork is that it had relatively little uptake: only about 15% of all possible coins were “activated” on BTCP. This means that the premine ended up representing a significant portion of the ultimate active supply of BTCP. Since the fork, 2.55M ZCL and 572k BTC were claimed on the BTCP chain, which brings the non-premined post-fork active supply to 3.12M BTCP. The roughly 300k BTCP from the covert premine that was moved out of the shielded pool therefore represents 9.5% of all post-fork BTCP. For every 10 BTCP in post-fork wallets, 0.95 comes from the premine.

How did this go undetected?

Bitcoin Private’s mainnet was released in March 2018, meaning that the fact that the 21M cap was broken was detectable for months. Some consensus code was written to check that the import process didn’t import too many coins, or coins not present in the UTXO set.

There’s a couple things at play here. The first is that the process to verify the BTC UTXO import was rather arcane and unfamiliar to many, and the checks themselves were weak. The second is that BTCP inherited BTC’s supply checks which are a function of auditing coinbase outputs – however this wouldn’t have caught the deception, since the mining function was not abused but rather the UTXO import.

Inappropriate supply checks
Given that BTCPs supply derived from a variety of places, trusting Bitcoin’s  supply-verification model from Bitcoin is inappropriate. In Bitcoin, supply is audited on a per-block basis by making sure that the number of newly-minted coins (the coinbase output) does not exceed 50 / 25 / 12.5 / 6.25 based on wherever Bitcoin is in the predefined halving schedule. Bitcoin’s protocol has no top-down supply audit built into validation. The difficulty of minting coins, the difficulty adjustment to ensure that the schedule is kept, and the per-block coinbase audit takes care of that instead.

In BTCP, the bulk of the supply derived not from mining but from the BTC UTXO import and the existing ZCL UTXO set. Since the BTC UTXO import checking tools were weak, BTCP lacked a genuine supply auditing function. In the end, we caught the deception by looking at the actual movement of coins. BTCP users had to trust that both the UTXO import and the mining processes were done in accordance with the developer’s claims. At Peter Todd’s suggestion, we experimented by running a full validation with the checkpoints removed, but the additional issuance was outside the scope of the verification checks performed by the node, and hence would not have been immediately obvious to node-runners. The issue is that the issuance was done outside of the scope of the protocol, so fully validating nodes would not easily have caught the additional issuance. Ingesting the data and then running supply checks was required.

Weak UTXO import verification
A set of files meant to audit the import blocks was released by the BTCP team, as well as a set of tools to recreate those files. Each file contains the contents of a single import block (10,000 transactions, each supposed to import a single BTC UTXO). The published files reflected a correct UTXO set dump: people auditing these files and glancing at the consensus code that used them would have probably found no issue. For each import block, the consensus code checked that the block contained 10,000 transactions and that each transaction’s first output matched the expected BTC value and script. However, it didn’t check that there were no additional outputs in the block, an oversight that enabled the extra coins to avoid the sanity-checking.

The pull request that added those checks received no comments or review despite touching consensus code and being relatively large. Furthermore, the name of the PR does not reflect the criticality of the proposed change.
Those checks were only run if import blocks were after the last known checkpoint (or if checkpointing was disabled altogether). As checkpoints covering the import process were added only 3 days after the fork, they were only run by people running full nodes during or immediately after the import process, or people disabling checkpointing (enabled by default).
This case study should be a reminder to audit the supply by running fully validating nodes and auditing the data produced by those nodes, rather than trusting developer teams or data sources who naively compute supply with the height formula.
Disclosure: no one at Coinmetrics has a position in BTCP, long or short.

More

LinkedIn
X.com
Telegram
Email