On 21 October 2026 at 14:17 UTC, BitcoinZ block 1,860,181 created 105 billion BTCZ from nothing, using five Sprout transactions with all-zero proofs. A cached validation flag skipped proof checks and an integer overflow blinded the supply turnstile. Anyone running BitcoinZ software should upgrade to 2.3.1, which removes the block.
This was not a stolen key or a 51 percent attack. Every node did what its code told it to do, and the code had been told, by a cached "already checked" flag, that the expensive zero-knowledge proof checks were done when they were not. A second safety net, the ZIP 209 supply turnstile, should have rejected the block anyway, but its arithmetic overflowed and reported the opposite of the truth.
The same validation bug had been used once before, on 20 June 2026, and that block is still part of the valid chain. A user reported the symptom on GitHub on 5 July, with an analysis that pointed at the cached flag, 108 days before the October exploitation. The project's post-mortem, release notes, commits and pull requests are unusually complete. This post uses them to cover the timeline, the two bugs at a level useful to defenders, the published identifiers, what operators, exchanges and bridges should check, and what maintainers of other Zcash-derived code can learn.
Two framing notes. Nothing in the sources we read says Zcash itself, or any other fork, carries either bug, and we do not suggest it. And nothing here is financial or investment advice.
What happened
BitcoinZ (BTCZ) is a proof-of-work coin built on Zcash code. It supports Sprout, the original shielded transaction format, where value moves into and out of a shielded pool through structures called JoinSplits. Each JoinSplit carries a zk-SNARK proof that its hidden inputs exist and balance. A node that accepts a JoinSplit without verifying the proof is accepting an unproven claim about money.
According to the BitcoinZ team's post-mortem, dated 22 October, block 1,860,181 arrived at 14:17:45 UTC. It held five transactions with no transparent inputs. Each spent one Sprout JoinSplit whose public output value, vpub_new, was 21,000,000,000 BTCZ, paid to a single transparent address. The project says the proofs, MACs and one-time public keys were all zeros and the ten note commitments were simply the numbers 1 to 10. Nodes on releases before 2.3.1 accepted the block.
Over the next two and a half hours the counterfeit coins spread across 16 attacker addresses. Seven deposits totalling 510,000,000 BTCZ went into the wBTCZ bridge, the project's wrapped token on BNB Smart Chain (BSC). The bridge credited three of them and minted 150,000,000 wBTCZ to the attacker. The attacker sold all of it on PancakeSwap in seven sales between 15:09 and 15:51 UTC for 2,892.18 USDT, then moved the USDT through two intermediate addresses to an exchange deposit address. The post-mortem says the receiving hot wallet "possibly belongs to HitBTC", based on a public address-labelling dataset, and that the team cannot confirm it. The project says HitBTC has been notified.
The project shipped BitcoinZ 2.3.1, an emergency release, at 19:19 UTC the same day. It treats block 1,860,181 as invalid and rewinds upgraded nodes to block 1,860,180. A valid replacement block 1,860,181 was mined at 22:22 UTC. On the valid chain, the 105 billion BTCZ never existed.
The realised loss sits on the other side of the bridge. The post-mortem notes that the BTCZ rollback reverses nothing on BSC. The 150 million wBTCZ minted against counterfeit deposits still exist there, and the project has not yet decided what to do with them, for example whether to burn them.
The June block
Both the post-mortem and the release notes say the same validation bug let an earlier block through. Block 1,789,967, mined at 15:29 UTC on 20 June 2026, held a Sprout JoinSplit with an invalid proof and no inputs that paid 56,305,778 BTCZ to one address. That address also received the block's 2,500 BTCZ coinbase reward, so the same party mined it. The coins went into the Sapling shielded pool and back out, ending at what the project describes as a service address with 89,498 transactions. The project calls this the June 2026 Sprout theft and attributes both incidents to one person, because a single address received coins from the June flow and 700,000,000 counterfeit BTCZ in October.
The June block was not removed. Its transactions remain on the valid chain. According to the release notes, the Sprout pool held only 0.447078 BTCZ just before the October block.
Timeline
All times UTC, as published by the named source.
| Date and time | Event | Source |
|---|---|---|
| 21 Feb 2026, 01:30 | BitcoinZ 2.2.0 released | GitHub releases |
| 9 Jun 2026 | Project publishes a security audit summary finding no counterfeiting risk | getbtcz.com audit page |
| 20 Jun, 15:29:41 | Block 1,789,967 with an invalid Sprout proof pays 56,305,778 BTCZ | Issue #118 log; post-mortem |
| 20 Jun, 20:16 to 21 Jun, 01:39 | Coins move into Sapling, back out, and on to a service address | Post-mortem section 10 |
| 4 Jul, 02:44:17 | A user's node fails at block 1,789,967 during sync | Issue #118 |
| 5 Jul, 17:43 | Issue #118 opened with a code path analysis pointing at the fChecked cache | GitHub issue #118 |
| 11 Aug | A reply links the failure to an inactive DNS seed; the reporter says the fix for that does not help | Issue #118 comments |
| 10 Oct, 09:10 | PR #120, the validation caching fix, opened as a draft | GitHub PR #120 |
| 19 Oct, 10:31 | BitcoinZ 2.3.0 released, without PR #120 | GitHub releases |
| 21 Oct, 14:17:06 | Block 1,860,180, last valid block before the incident | Checkpoint timestamp, commit 3c20b0f |
| 21 Oct, 14:17:45 | Counterfeit block 1,860,181 creates 105,000,000,000 BTCZ | Post-mortem section 1 |
| 21 Oct, 14:47 | First bridge deposit, 10,000,000 BTCZ | Post-mortem section 3 |
| 21 Oct, 15:03:40 | First wBTCZ mint on BSC | Post-mortem section 4 |
| 21 Oct, 15:09:34 | First PancakeSwap sale | Post-mortem section 5 |
| 21 Oct, 15:35:34 | Third and last mint; 150M wBTCZ in total | Post-mortem section 4 |
| 21 Oct, 15:51:16 | Last of seven sales; 2,892.18 USDT in total | Post-mortem section 5 |
| 21 Oct, 16:39 | Last movement of counterfeit BTCZ (block 1,860,241) | Post-mortem section 8 |
| 21 Oct, 18:15:02 | USDT reaches the exchange deposit address | Post-mortem section 6 |
| 21 Oct, 19:09:28 | PR #120 merged | GitHub PR #120 |
| 21 Oct, 19:15:03 | Commit 3c20b0f rejects block 1,860,181 and disables JoinSplits | GitHub commit |
| 21 Oct, 19:19:20 | BitcoinZ 2.3.1 published; Linux x86_64 build attached; other builds by 19:52 | GitHub release |
| 21 Oct, 19:36 | Block 1,860,306, last invalid-chain block the project's node received | Post-mortem section 2 |
| 21 Oct, 22:22 | Valid-chain block 1,860,181 mined | Post-mortem section 1 |
| 22 Oct | Post-mortem published; correction notice added to the June audit page | getbtcz.com |
| 24 Oct, 14:53 | Post-mortem submitted to Hacker News | HN item 50221857 |
Who is affected
| Group | Affected? | What it means |
|---|---|---|
| Full nodes on any release before 2.3.1 | Yes | They accept the counterfeit block and cannot be trusted to follow the valid chain |
| Mining pools | Yes | Old pools mine on the invalid chain; the project asks pools to upgrade first |
| Exchanges and custodians | Yes | Do not credit deposits confirmed only on the invalid chain; keep BTCZ deposits and withdrawals paused until the project announces the switch |
| The wBTCZ bridge | Yes | 150M wBTCZ minted against counterfeit deposits; their fate is undecided |
| Wallet users | Mostly no | Valid-chain coins are unaffected; shielded sends, including Z-TEXT messages, made during the incident window must be resent |
| Explorers, Electrum servers, wallet backends | Yes | The project asks all of them to run 2.3.1 |
| Other Zcash-derived projects | Unknown | No source says whether any other codebase has the same caching path or overflow |
| Zcash (zcashd) | Not claimed | Nothing we read says upstream Zcash is affected |
Exploitation status, plainly: the bug was exploited twice on mainnet, on 20 June and 21 October 2026, according to the project. No CVE has been assigned as of 24 October.
How it works, in plain language
Two separate mistakes had to line up. Either one alone would not have let the October block through.
Bug 1: a cached "already checked" flag
Full nodes descended from Bitcoin Core check a new block more than once. When a block arrives from a peer, the node runs a quick preliminary check to reject obvious junk cheaply. Later, when it connects the block to its chain, it runs the full, strict check. Sprout proof verification is expensive, so BitcoinZ ran the preliminary pass with it switched off.
As the 2.3.1 release notes describe it, the preliminary pass recorded its success in a flag on the block object, CBlock::fChecked. When the strict check in ConnectBlock later ran on the same in-memory block, it saw the flag and returned early. The proofs were never verified. The cache recorded that some check had passed, not which one.
That explains a strange symptom. A node that reads the block back from disk, during a fresh sync or a reindex, starts with the flag cleared. It runs the strict check for real and rejects the block. That is what the user in issue #118 saw in July: running nodes had accepted block 1,789,967, but a fresh node stopped there with the log line CheckTransaction(): joinsplit does not verify.
The fix in PR #120 caches success only when Sprout verification was actually requested, or when the block has no Sprout JoinSplits at all. Blocks without JoinSplits keep the fast path.
Bug 2: an overflow that turned a withdrawal into a deposit
ZIP 209, a Zcash consensus rule from 2019, adds a second line of defence. Nodes track how much value each shielded pool holds, and a block that would make any pool's balance negative is invalid. Even if a proof bug lets someone forge a withdrawal, they should not be able to take out more than was put in. This check is often called the turnstile.
In BitcoinZ, the per-block sum of Sprout value changes was a signed 64-bit integer counted in zatoshi, one hundred millionth of a coin. One 21 billion BTCZ withdrawal fits in that type. Five do not. The sum ran past the largest value the type can hold and wrapped around. PR #121 gives the result: the turnstile computed a change of plus 79,467,440,737.09552 BTCZ. It concluded the pool, holding 0.447078 BTCZ, had grown by almost 80 billion coins, instead of going deeply negative. A pool that grows does not trip a rule about pools going negative.
ZIP 209 anticipates this class of mistake. Its specification says the total supply check must be "computed without possibility of overflow". Per-pool sums need the same care.
Why June and October differ
The June block needed only Bug 1. It withdrew 56,305,778 BTCZ from the Sprout pool. The turnstile is a balance check, not a proof check: a forged withdrawal that stays within the pool's recorded balance passes it. By October the pool held almost nothing, so a large forged withdrawal would have tripped the turnstile unless the arithmetic failed, and in October it did. The project's write-up makes the same point.
Why the fix is more than a bug fix
Fixing the cache stops new invalid proofs, but by then the network had a chain containing block 1,860,181 and more than a hundred descendants. Per the release notes and commit 3c20b0f, 2.3.1 does four things together. It verifies Sprout proofs even after a preliminary check. It disallows JoinSplits on mainnet from height 1,860,181. It marks block 1,860,181 invalid on first start and rewinds. And it adds a checkpoint at block 1,860,180. The release notes say closing Sprout "removes this whole class of attack". The 0.447078 BTCZ left in the pool stays locked permanently.
Technical details
Every value here comes from the BitcoinZ release notes, commit, pull requests, issue #118, compatibility document or post-mortem. Nothing here is an exploit; the bugs are fixed in 2.3.1 and JoinSplits are now disabled on mainnet.
Identifiers and scoring
| Item | Value | Source |
|---|---|---|
| CVE | None assigned as of 24 Oct 2026 | No CVE named in any project source |
| Bug class (cache) | Validation result cached without recording which checks ran | Release notes; PR #120 |
| Bug class (turnstile) | Signed 64-bit overflow in the per-block Sprout value sum | Release notes; PR #121 |
| Exploited | Yes: block 1,789,967 (20 Jun) and block 1,860,181 (21 Oct) | Post-mortem |
| Fixed release | BitcoinZ 2.3.1 | GitHub release |
| Fix commit | 3c20b0f1ab85ebcc99d49cc6ee1c26651d58d9bc | GitHub |
| PR #120 merge commit | 4619cfffe14f1de95c0ca18554101110ae741b02 | GitHub |
| PR #121 merge commit | 73e1551fcb26dc42e2d59a369f0a7b889cf5435f | GitHub |
For context only, Zcash's 2019 counterfeiting flaw, CVE-2019-7167, was a different bug in how the original Sprout parameters were generated. NVD scores it CVSS 3.0 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N) and lists CWE-754. BitcoinZ's June audit page says the fix for that flaw is in place. The 2026 BitcoinZ bug is not a weakness in the proof system; the proofs were never checked.
Block hashes and checkpoint
| Block | Hash | Notes |
|---|---|---|
| 1,860,180 (valid) | 000002a2a96c4cce6bea19220151116c1a7f6a9ce19fbce3c3286370adb3fd1e | New 2.3.1 checkpoint; timestamp 1792592226 (14:17:06) |
| 1,860,181 (counterfeit) | 000001963db0caee911c3409e3d1f1b868cb3fbfacc7e7e7422d5d3b80cbb37d | 14:17:45; marked invalid by 2.3.1 |
| 1,860,181 (valid replacement) | 0000055c669539bb6e35418f2fef774c89648048ab8e0a362b214c4122eb9052 | Mined 22:22 |
| 1,860,306 (invalid chain tip) | 00000128b2118af53b80c50b389e19a2b29ca16843f356b167838bfe980b1738 | Shown as invalid in getchaintips on an upgraded node that followed it |
| 1,789,967 (June, valid chain) | 0000002e04e291438a422e2d5e9d6d67ac87059afefeb03136ff9f21711bb359 | Invalid proof; raw payload 2,312 bytes |
| 1,789,966 | 000002ac0515f0e34b3692f6aec476a468c1730563c161fc32e722afd95fcaab | Where a strict fresh sync stopped before the fix |
Issue #118 and the compatibility document give the SHA-256 of the June block's raw payload as ef224f10e4976746f7ce2a7fcf1ebc90b8cefcac6dec9d870d16bd74132cc741; the reporter fetched identical bytes from three peers. The new checkpoint also updates chainparams.cpp with 4,790,514 total transactions up to block 1,860,180. The previous last checkpoint was block 1,400,000.
The overflow, in numbers
| Quantity | Value | Source |
|---|---|---|
vpub_new per counterfeit JoinSplit | 21,000,000,000 BTCZ | Release notes |
| Counterfeit JoinSplits | 5 | Release notes |
| Total created | 105,000,000,000 BTCZ | Release notes |
| Sprout pool before the block | 0.447078 BTCZ | Release notes |
| Turnstile's computed change | plus 79,467,440,737.09552 BTCZ | PR #121 |
The arithmetic checks out. The largest signed 64-bit value is about 92.2 billion coins in zatoshi. 105 billion does not fit and wraps to the positive figure in PR #121.
Counterfeit transactions in block 1,860,181
All five pay 21,000,000,000 BTCZ to t1gcbkA2YNNgruDc8qmKJ7sQmFgPN2MCAdm, which also received the 2,500 BTCZ coinbase.
| # | Txid |
|---|---|
| 1 | 68041a36861bd03b23e1b6b815fd523043429b6b5a661430ec2b692d4887c089 |
| 2 | c3d5e3fc543130cbb799e7d8b51be95e5311e8144386b17f393c99609b83debc |
| 3 | 1bf44af0b8d66387bad6f28e1b3c9fd781d5637095a9a685ade70bb9d1d85c08 |
| 4 | 72fc051cfb65029dcb63d29fd6b0b504c15a10120eea27571526f53523203602 |
| 5 | 295a8bf9c682e793b0d48870f7bdce3acc9b641fb5619d557d08c6a310ed98e2 |
The post-mortem lists all 27 transactions that touched counterfeit coins between blocks 1,860,181 and 1,860,306. They exist only on the invalid chain, so valid-chain explorers do not show them; a node that followed the bad chain before upgrading keeps those blocks on disk.
Addresses
The project says counterfeit coins reached only 24 addresses: 16 in the attacker's chain, 7 bridge deposit addresses and the bridge hot wallet.
| Role | Address |
|---|---|
| Coinbase and counterfeit recipient | t1gcbkA2YNNgruDc8qmKJ7sQmFgPN2MCAdm |
| Attacker chain | t1fKbsEov5FPYH7GW6zFDWPiYhvhMmCoyKr, t1ex16baKxSCmf3HfkTJSot32KxSFfyhiaJ, t1T8Jb8rqvjiKWK9hV5Y5TFdjVMqDCGnAtA, t1gQbotYbo9dRN1ZWrZ11SifKS7qCfBXKY9 |
| Attacker chain | t1QXQYCYxij74z7D8hFFsmFn8EooSc7XSWo, t1TBVKyeoxM5bQBSyb2GqDYfRfrjCg7kj8V, t1W74qv5aVK9VMnpGBwNwAgM9SH2q2SVWPf, t1Q7Zh31mWKmJAvbc9kqXguVwFUFDSR79Ze |
| Attacker chain | t1VArYsAJPXzYTCUVp9bECL1krXF7UN2Wum, t1JG8FSdu5wuie6Pm7x2omuYpt4ETBiUKpf, t1LnyWNFanvWAWdapR5dc7TPaPS37BMFgMw, t1bU3uksVHEqyCNVgGgEBDooNuKCTVfUkqz |
| Attacker chain | t1UvbyYfHU3BE2oZJRgcgXiGrXSAx9dsw8m, t1fjzdHebTmE8BKn4QkM9AY79DtaWfCp7mm, t1KgEFBAE9N1rV2gmNKefUcmP9V3n9m8Wob |
| Bridge deposits | t1XBzcMnNMGP3zCPxvJsubVtfcEPatnh4pM, t1UXrcLjh5c8qxsCwXtZcoMKdk5yzaH3KkP, t1UC8XdiAr3Cz1UcgSJ44kgwA8nyFMZppyH, t1dQHx7THBfHNT9iaLCWvin6FWyNLk9GMEQ |
| Bridge deposits | t1eMdoubvVnCdUd8KsjfBY3hhmWXBypGMAb, t1MaJMmrXSfN5b5u54jreJaWFsoNSUsFbYm, t1ZLMMVqywt7xHR3YqgUpv9iAsZ3eR8tt3q |
| Bridge hot wallet | t1NZg8VE5UhqRyXGxqje518GjbZT1msiMvw |
| June recipient and miner | t1RGJarR9UVinsAVmH2Xxdw7sVW8oeSM6Kn |
The June to October link is t1KgEFBAE9N1rV2gmNKefUcmP9V3n9m8Wob. It received the June coins after they left Sapling, and 200,000,000 plus 500,000,000 counterfeit BTCZ in October.
BNB Smart Chain identifiers
| Role | Value |
|---|---|
| wBTCZ token contract | 0xcbbb3e5099f769f6d4e2b8b92dc0e268f7e099d8 |
| Bridge minter | 0xf9e15aab8d82d5e71fd763074f6bed8d5b48aedb |
| Attacker recipient | 0x00479d80b4f45730b637679a961f3e8a35af185c |
| PancakeSwap wBTCZ/USDT pair | 0x7bd31069afb56370f6ab63ef3d66dcba5565ba73 |
| PancakeSwap wBTCZ/WBNB pair | 0x4e3459f55a72455598d4c26050b7b053a9dba162 |
| USDT intermediate address | 0x11d7868c09f750eecb985480774966d28a086366 |
| Exchange deposit address | 0xb3d3944be93aaeb11b7dafd2eaf7bd8dac6f4b9f |
| Suspected exchange hot wallet (unconfirmed) | 0xe647b0db71985b26a1562be585bf270ea4023b10 |
| Mint time | wBTCZ minted | BSC transaction |
|---|---|---|
| 15:03:40 | 9,999,999.9999 | 0x40833836f07e9bfa722dfa6911201281fec0bbf3a8b3229b690b4e95c9257c05 |
| 15:28:23 | 49,999,999.9999 | 0xcdf06661d1a0b233a27e77771b30ec8f4d33cf34152b9286a1cfd4896755b5c5 |
| 15:35:34 | 89,999,999.9999 | 0x206dba0a2fab73c32b49715b9ee39fcc49055688f6a3d6a7128ecc0cb467ee7b |
A detail for anyone monitoring the token: the post-mortem says the bridge records mints with its own event, not a standard transfer from the zero address, so a plain token transfer view may not show them. Supply alarms keyed only to standard transfer events would have missed these mints.
Release 2.3.1 artifacts
GitHub reports these SHA-256 digests for the release assets. Each archive also ships with a .sha256 file.
| Asset | Size (bytes) | SHA-256 |
|---|---|---|
bitcoinz-2.3.1-linux-x86_64.tar.gz | 6,603,827 | 0a0ef825d77af6a81dd52d967343a362c828002577743c4457d127215f3d23bd |
bitcoinz-2.3.1-linux-aarch64.tar.gz | 74,023,990 | 4cd57bc4bcf9d8ea7655e3ce3cd3566e3aebda92bc1cc2d8face48b5205c900d |
bitcoinz-2.3.1-macos-arm64.tar.gz | 7,425,704 | 85a156894a3698a13a41dc14fd319c45c11a2151b85ad1269d397a4b83a1f6d1 |
bitcoinz-2.3.1-macos-x86_64.tar.gz | 7,966,458 | bcd1c9e6d68acb9e594a7a725fa462fcf6ee7c87b9cc9afa0d963448d842e987 |
bitcoinz-2.3.1-win64.zip | 90,928,852 | 64a055afe834b03267ec9bfb46e8238d23a1e713970f081fcaeb79f653b7a9d0 |
What the fix changes
Commit 3c20b0f touches src/chainparams.cpp (the checkpoint), src/init.cpp (a new rewind step at startup), src/main.cpp and src/main.h (which declares RewindMainnetInflationBlock). PR #120 changes src/main.cpp, src/primitives/block.h and src/proof_verifier.h, adds 151 lines of tests in src/test/checkblock_tests.cpp, and checks in the June block as a public test fixture, src/test/data/bitcoinz-1789967.dat.
PR #121 explains one engineering choice. The rewind disconnects blocks without wallet notifications, like the existing RewindBlockIndex, because a normal disconnect deeper than WITNESS_CACHE_SIZE (100 blocks) hits a wallet assertion. The first time the bad block is marked invalid, the wallet clears its witness caches and rescans. Testing on an isolated copy of a production node at the bad tip, height 1,860,295, rewound 115 blocks in 35 seconds, returned 18 transactions to the mempool (17 transparent, 1 shielded) and rescanned the wallet in 4.5 minutes. That testing also caught a bug where the wallet stayed on the bad chain, fixed before release.
The June block exception
Block 1,789,967 stays on the valid chain, but strict validation rejects it. The 2.3.1 checkpoint lets a fresh node sync through it from peers. A -reindex from local data still stops there unless the node runs with -experimentalaccept1789967=1. The compatibility document limits that option to mainnet and to transaction 02ee84491eee2fc6a7e1558bec4adb34f50e6fd8325378bedd3fa341db5e692d in that one block, keeps proof of work and Merkle binding mandatory, and never lets an exception result fill the strict cache. It also says the option "does not establish that the historical withdrawal was authorized".
Affected and fixed versions
| Release | Published (UTC) | Status | Notes |
|---|---|---|---|
| 2.2.0 | 21 Feb 2026, 01:30 | Affected | The version in issue #118 (v2.2.0-e90047d4ae65) |
| 2.3.0 | 19 Oct 2026, 10:31 | Affected | PR #120 merged after it shipped |
| 2.3.1 | 21 Oct 2026, 19:19 | Fixed | Verifies Sprout proofs, disables mainnet JoinSplits from height 1,860,181, rewinds past the counterfeit block |
| Older (2.1.x, 2.0.x) | 2024 to 2025 | Not stated | The release notes give no lower bound; treat every pre-2.3.1 node as affected |
What changed since first reports
- 21 October, release notes. Within about five hours of the block, the 2.3.1 notes gave the block hash, the transaction structure, both root causes and upgrade steps. They linked the June block to issue #118 but did not yet call it a theft.
- 22 October, post-mortem. The write-up added the full money trail: 27 invalid-chain transactions, 16 attacker addresses, bridge deposits and mints, the PancakeSwap sales, the USDT hops and the suspected exchange. It attributed June and October to the same actor, and said no counterfeit coins reached SafeTrade, WhiteBIT or any other exchange, countering claims that had circulated.
- 22 October, audit correction. The 9 June audit page now opens with a correction notice saying the review missed this bug, and the post-mortem has a section titled "A correction to our June audit".
- Network status. The post-mortem says the network runs on 2.3.1 and blocks are arriving at the normal pace again, citing block 1,860,239. The project also opened a no-fee 2.3.1 solo mining pool and a 2.3.1 Electrum server.
- Still open. The bridge decision, any recovery of the USDT, and issue #118 itself, still open when we checked on 24 October.
The 108 day signal in issue #118
On 5 July a user opened issue #118: their node could not sync past block 1,789,967. They included the log lines, the block hash, the raw payload hash fetched identically from three peers, and a code path analysis, labelled as AI-assisted, suggesting that the preliminary check set fChecked with proof verification off and the strict check then returned early. They proposed caching the flag only when proofs had actually been verified. That is, in substance, the fix that merged on 21 October.
In August a reply linked the failure to an inactive DNS seed; the reporter rebuilt with that fix and hit the same error. The draft fix, PR #120, was opened on 10 October and merged a few hours after the second exploitation.
We draw no conclusions about anyone's choices. Volunteer-run projects triage with the hours they have, and this one has been candid. The general pattern applies everywhere, though: a deterministic validation failure on a fresh install, at a specific input that every other node accepted, is a correctness alarm, not a networking glitch. If one honest node rejects what the rest of the network accepted, either that node is wrong or everyone else is.
One more timing point, stated carefully. The root cause was described publicly in July and again in the PR #120 description on 10 October. No source says the October attacker read either, and we do not suggest it. The only connection is timing.
What to do now
These steps follow the 2.3.1 release notes and the post-mortem. Commands assume standard bitcoinzd and bitcoinz-cli binaries and the default data directory.
Node operators
-
Check the running version. Anything below 2.3.1 must be upgraded.
bitcoinz-cli getnetworkinfo | grep -E '"(version|subversion)"' -
Download 2.3.1 and verify the archive against the digests above and the published
.sha256file.# Linux sha256sum bitcoinz-2.3.1-linux-x86_64.tar.gz # expect 0a0ef825d77af6a81dd52d967343a362c828002577743c4457d127215f3d23bd # macOS shasum -a 256 bitcoinz-2.3.1-macos-arm64.tar.gz # expect 85a156894a3698a13a41dc14fd319c45c11a2151b85ad1269d397a4b83a1f6d1On Windows,
Get-FileHash .\bitcoinz-2.3.1-win64.zip -Algorithm SHA256should return64a055afe834b03267ec9bfb46e8238d23a1e713970f081fcaeb79f653b7a9d0. -
Stop cleanly, back up the wallet, install and start.
bitcoinz-cli stop cp ~/.bitcoinz/wallet.dat ~/wallet-backup-$(date +%F).dat # install the 2.3.1 binaries, then bitcoinzd -daemonExpect a slow first start: the rewind is deeper than the wallet's 100 block witness cache, so the wallet rescans from genesis. Nodes started with
-disablewalletskip the rescan. -
Confirm you are on the valid chain.
bitcoinz-cli getblockhash 1860180 # 000002a2a96c4cce6bea19220151116c1a7f6a9ce19fbce3c3286370adb3fd1e bitcoinz-cli getblockhash 1860181 # 0000055c669539bb6e35418f2fef774c89648048ab8e0a362b214c4122eb9052 bitcoinz-cli getchaintipsIf the node followed the bad chain before upgrading,
getchaintipsshould list00000128b2118af53b80c50b389e19a2b29ca16843f356b167838bfe980b1738at height 1,860,306 with statusinvalid. -
Find hosts you missed. Per the release notes, non-upgraded nodes stop with a message that begins
A block chain reorganization has been detected.grep -n "reorganization has been detected" ~/.bitcoinz/debug.log -
Do not ban peers in a panic. 2.3.1 deliberately rejects the bad block without banning the sender, so old nodes can reconnect once they upgrade.
-
If you reindex,
-reindexstops at block 1,789,967 unless you pass-experimentalaccept1789967=1. The compatibility document recommends trying it on a separate data directory and keeping the original database.
Mining pools
- Upgrade before anything else, as the release notes ask.
- Add other upgraded pools with
addnodeso the corrected chain spreads quickly. - Remove any coinbase rewards from invalid-chain blocks 1,860,181 to 1,860,306 from payout records. They do not exist on the valid chain.
Exchanges, custodians and bridges
-
Keep BTCZ deposits and withdrawals suspended until the BitcoinZ team announces the switch.
-
For every BTCZ deposit credited after 14:17 UTC on 21 October, confirm it is in a block on the valid chain. On an upgraded node with
txindex=1:bitcoinz-cli getrawtransaction <txid> 1 | grep -E '"(blockhash|confirmations)"'No block hash on an upgraded node means not confirmed on the valid chain.
-
Screen records for the 24 BTCZ addresses and the BSC addresses above. The post-mortem asks services that find them to keep those records for law enforcement.
Wallet users
- Update to a wallet built on 2.3.1, or point it at a 2.3.1 backend.
- If a shielded payment or Z-TEXT message sent between 14:17 UTC and the switch never arrived, send it again. The project says those could not be carried over because their anchors refer to a note commitment tree that no longer exists.
- If you lost funds, the project asks you to file a police report citing the post-mortem and the transaction IDs.
Maintainers of Zcash-derived node software
No source says another codebase is affected. A short review against the 2.3.1 changes is better than an assumption either way.
-
Find every place a validation result is cached and every place a check runs with proof verification disabled.
git grep -n "fChecked" -- src/ git grep -n "ProofVerifier::Disabled" -- src/For each cache, ask: does it record which checks passed, or only that something passed? A strict caller must never be satisfied by a lenient result.
-
Compare your validation code with the PR #120 and PR #121 diffs.
-
Review chain value pool arithmetic. Sums over many attacker-controlled values in one block need checked addition and range checks at each step, not only on each input value.
-
Add a regression test that validates the same block from memory and from disk and expects the same answer, as PR #120's tests do.
-
Run a fresh sync from genesis, with full proof verification, on every release candidate. A strict fresh sync is what exposed the June block.
Common mistakes
- Restarting instead of upgrading. An old binary restarted is still an old binary. Only 2.3.1 rejects the block and rewinds.
- Upgrading only the public node. One old backend behind a load balancer can keep feeding invalid-chain data to internal services.
- Trusting explorers for October transactions. Invalid-chain transactions are not on valid-chain explorers. Read them from a node that followed the bad chain.
- Treating BSC as rolled back. The BTCZ rewind does not touch BNB Smart Chain. The 150M wBTCZ still exist there.
- Calling it a "hack of the blockchain". The cryptography held. Node software skipped a check and miscounted.
- Assuming Zcash is affected. Nobody has said so. Review your own fork instead of spreading a claim nobody has made.
What would have helped
None of this criticises the BitcoinZ maintainers, who fixed the bug, rolled back the chain and published a complete, verifiable record within a day. These practices apply to any team that writes validation code or relies on someone else's.
Practices any team can use
Make caches remember what they verified. A boolean that says "checked" is a trap when there is more than one kind of check. Record the level or set of checks that passed, and let a strict caller accept only a strict result. The same pattern shows up in request validation, signature checks on downloads and cached policy decisions.
Test the same input through every path. Here, a block validated from memory and the same block validated from disk gave different answers. A test that runs both and compares them would have failed on the June block.
Use checked arithmetic for money and supply. Any running total of values controlled by untrusted input should use overflow-checked addition and a range check after every step. A wrapping sum is not a conservative sum.
Treat a consistent sync failure as a security signal. Any report with a deterministic rejection at a specific block, plus a byte-identical payload from several peers, should reach someone who can read validation code within days.
Audit the control flow, not only the checks. The June audit page says the review examined zero-knowledge proof verification. The proof checks were sound; the code path that should have called them returned early. Every review of a security check should ask under what conditions the check does not run.
Retire legacy features you do not need. The Sprout pool held 0.447078 BTCZ, yet carried the full attack surface of its validation code. The same goes for old API versions and legacy authentication schemes.
Bridges and exchanges: size confirmation depth to the chain, and reconcile. The first wBTCZ mint came about 17 minutes after the first counterfeit deposit. For a small proof-of-work chain, deeper confirmations, mint limits per period relative to circulating supply, and an independent check of deposits against an upgraded node would all have slowed the drain.
Monitor supply invariants from outside the node. A separate process that recomputes pool balances with arbitrary precision and alerts on any block that creates more than its subsidy would have flagged block 1,860,181 at once, whatever the node's own turnstile concluded.
Where Safeguard fits
Honesty first: Safeguard would not have prevented this incident, and nothing in Safeguard verifies blockchain consensus. The bug sat in C++ consensus code in a cryptocurrency node, and no CVE exists for it. Vulnerability matching depends on a vulnerability record, so a scan would not have flagged BitcoinZ 2.3.0 on 21 October.
Safeguard helps with the work around an incident like this:
- Inventory. Safeguard scans repositories, container images and SBOM files (
safeguard scan --dir .,--image <name>, or--sbom <file>, results as JSON under./output), and the platform shows which projects contain a given component and version. Exchanges that run node daemons in containers, or record them in an SBOM, can find every service still bundling a pre-2.3.1 node in one query. A binary copied into an image by hand is easiest to track when the build records it in an SBOM. - Policies and gates. Policies and gates evaluated in the platform can flag a specific component version, so a pipeline can refuse to ship a node image on an old release. The gate acts on what the scan reports; it does not judge consensus correctness.
- The services around the node. Exchange and bridge services usually have long JavaScript, Python, Go or Java dependency trees. Safeguard matches those against vulnerability databases with reachability analysis, so the code paths your services call come first.
- Review and upgrade work. The Safeguard CLI's assistant can edit code and run commands with your approval. For a team maintaining a Zcash-derived fork, it can run the
git grepreview above, read the PR #120 and #121 diffs against your validation code, draft a memory versus disk regression test, and run your test suite, asking before each command./permissionssets the mode (Ask, Accept edits, Plan, Auto by default, or Strict); for consensus code, Ask or Strict is sensible, and a human reviewer owns the conclusion.safeguard execruns the same checks in CI, and the desktop and phone apps can send that work to a computer running the CLI and let you approve each command.
Rolling back a chain, coordinating pools and the bridge decision are human calls that sit outside any tooling.
Zero-Day Discovery and Gold
Zero-Day Discovery looks for previously unknown flaws before a CVE exists, in an agentic mode and a deterministic scanner mode, and issues findings as SGZ advisories before they reach public CVE databases. It covers seven package ecosystems. A cryptocurrency full node built from its own C++ source tree is not a package in those ecosystems, and Zero-Day Discovery played no part in this incident. It does show why that category of work exists: the flaw sat in source for months, and a public bug report described it before its second use.
Gold Open Source at gold.safeguard.sh is free CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version. This incident has no CVE, so the BitcoinZ release notes are the authority on the safe version, 2.3.1. Gold Registry provides hardened, Sigstore-signed open source components and container images held to zero exploitable critical or high CVEs, with SLSA L3 or higher provenance. Here it is relevant to the libraries and base images under exchange and bridge services, not to the BitcoinZ node. The habit that carries over is the same: pull patched, signed components from a source you can verify, and be able to rebuild and redeploy within hours.
Open questions
- Are other Zcash-derived codebases affected? Nobody has said. Each fork's maintainers can answer for their own code.
- What happens to the 150M wBTCZ? The project says it will announce a decision separately.
- Who is the attacker? Unknown. The project names addresses only.
- Is the exchange link right? The project bases it on a public labelling dataset and cannot confirm it.
- What about the June coins? The project has not said whether any recovery is planned for the 56,305,778 BTCZ withdrawn in June.
- Will a CVE be assigned? None exists as of 24 October.
- Will the June audit be redone? The project added a correction notice but has not published a revised technical review.
FAQ
What is the BitcoinZ counterfeit block?
Block 1,860,181, mined at 14:17:45 UTC on 21 October 2026, held five Sprout JoinSplits with all-zero proofs that each created 21 billion BTCZ. Nodes before 2.3.1 accepted it. Release 2.3.1 marks it invalid and rewinds to block 1,860,180.
Was it exploited in the wild?
Yes. The project says the bug was used on 20 June 2026 (block 1,789,967, 56,305,778 BTCZ) and on 21 October 2026 (block 1,860,181). Part of the October coins was bridged and sold for 2,892.18 USDT.
Which versions are affected and which is fixed?
All releases before 2.3.1, with 2.2.0 and 2.3.0 confirmed by the project's issue and release notes. BitcoinZ 2.3.1, published at 19:19 UTC on 21 October, is the fix.
Is Zcash affected?
No source says so, and we do not claim it. Zcash's 2019 flaw, CVE-2019-7167, was a different issue in parameter generation.
Why did the turnstile not stop it?
The per-block Sprout sum is a signed 64-bit integer. Five 21 billion BTCZ withdrawals overflowed it to about plus 79.47 billion BTCZ, so the check saw the pool grow. In June, one withdrawal stayed within the pool's balance, so there was nothing for the turnstile to catch.
Do I lose coins I received that afternoon?
The project says valid-chain coins are unaffected and most transparent transactions were confirmed again. Shielded transactions anchored after block 1,860,180 must be resent. Anything that spent counterfeit coins was dropped.
Why does my reindex stop at block 1,789,967?
Strict validation rejects that block's invalid proof. Fresh nodes sync through it from peers thanks to the new checkpoint, but -reindex from local data needs -experimentalaccept1789967=1, best tested on a separate data directory.
Could an audit have caught it?
The project now says its June review missed the bug. The proof checks and the turnstile looked correct in isolation. The failure was in when the proof check ran and how the turnstile added numbers, which is why control flow and arithmetic deserve explicit review.
Fix it without waiting for upstream
Here the upstream project shipped its own fix within five hours, and the node fix has to come from BitcoinZ. The block below is about the other software around it: the services, libraries and container images that exchanges, pools and bridges build and run.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would have helped
None of these demos shows this incident. Each shows a real flow on another example, and the notes explain how it applies to a BitcoinZ upgrade or a fork review.
The desktop app demo sends work to your computer: you pick a folder, then Approve or Deny each command and file edit, and it ends with an upgrade done and tests passing. Here, a pool or exchange engineer would point it at the repository that builds their node image, ask it to move to 2.3.1, check the archive's SHA-256 against the published digest and run the smoke tests, approving each step.
Click through it at your own pace, or open it full screen.
The CLI demo fixes a lodash CVE: the assistant reads the finding, asks before each command, edits package.json, runs the install and tests, and pushes nothing. A maintainer of a Zcash-derived fork could run a similar session in their node repository: search for cached validation flags and unchecked pool sums, draft a memory versus disk regression test and run the suite, approving every command first.
Click through it at your own pace, or open it full screen.
The mobile demo is a real Android session: you send work to a computer running the CLI and approve a command on the phone with Allow once, Allow for turn or Deny. Emergency releases like 2.3.1 land when they land, and the person who can approve restarting a production node is not always at a desk.
Click through it at your own pace, or open it full screen.
To try it on your own repositories, download Safeguard and follow the CLI docs.
Quick check
Why did fresh nodes reject the June block while running nodes accepted it?
Running nodes received the block from a peer, ran a preliminary check with Sprout proof verification off, and cached the result in fChecked, so the strict check returned early. A fresh sync reads the block from disk with the flag cleared, runs the strict check for real, and rejects the invalid proof.
Does upgrading to 2.3.1 undo the wBTCZ minted on BNB Smart Chain?
No. 2.3.1 removes block 1,860,181 from the BTCZ chain only. The post-mortem says the rollback reverses nothing on BSC, and the fate of the 150M wBTCZ minted against counterfeit deposits is undecided.
Sources
- BitcoinZ: The 21 October Counterfeit Block, What Happened and Where Every Coin Went (post-mortem, 22 October 2026)
- BitcoinZ 2.3.1 (emergency) release notes and assets
- BitcoinZ releases list
- Commit 3c20b0f: Reject block 1860181 and disable JoinSplits on mainnet
- PR #120: Fix Sprout validation caching and add opt-in historical mainnet compatibility
- PR #121: BitcoinZ 2.3.1, reject inflation block 1860181
- Issue #118: BitcoinZ node validation issue around block 1789967
- BitcoinZ: Historical mainnet compatibility (doc at tag 2.3.1)
- BitcoinZ: Security Audit, Supply Integrity Verified (9 June 2026, with 22 October correction)
- ZIP 209: Prohibit Out-of-Range Chain Value Pool Balances
- NVD: CVE-2019-7167
- Hacker News submission of the post-mortem
Last reviewed: 24 October 2026. This is a developing story; check the linked advisories for the latest status.