Back to Blog

Zcash Orchard Soundness Bug Analysis | BlockSec Weekly

Code Auditing
June 10, 2026
11 min read
Key Insights

During the past week (2026/06/01 - 2026/06/07), multiple attack incidents were observed across blockchain ecosystems. This week's report focuses on the public disclosure of a critical soundness vulnerability in Zcash's Orchard shielded pool, which could have enabled undetectable counterfeiting of ZEC. No on-chain exploitation has been confirmed, but the disclosure triggered an emergency network upgrade and ZEC lost over 40% of its value.

Date Incident Type Estimated Loss
2026/06/04 Zcash Orchard ZK Soundness Bug No exploitation confirmed

Best Security Auditor for Web3

Validate design, code, and business logic before launch

Weekly Highlight: Zcash Orchard Soundness Bug

This incident is selected as this week's highlight because it is one of the most severe ZK vulnerabilities found in production. A missing circuit constraint went undetected for over four years despite multiple audits and was ultimately found through an AI-assisted security review. The vulnerability class (under-constrained relations in ZK circuits) could exist in any protocol built on ZK circuits.

On June 4, 2026, Zcash publicly disclosed a critical soundness bug in its Orchard shielded pool circuit [1]. A missing constraint in the zero-knowledge proof circuit broke the cryptographic binding that prevents double-spending, potentially allowing the same shielded note to be spent multiple times without detection. The vulnerability existed since Orchard's activation in May 2022 and was discovered on May 29, 2026, by security researcher Taylor Hornby through an AI-assisted audit. An emergency network upgrade (NU6.2) patched the circuit on June 3, 2026. No evidence of exploitation has been found.

Background

Zcash is a privacy-focused Layer-1 blockchain whose native token is ZEC. Unlike Bitcoin, where all transaction details are publicly visible, Zcash supports shielded transactions through zero-knowledge proofs, hiding sender addresses, recipient addresses, and transaction amounts. Zcash has multiple shielded pools, each corresponding to a different generation of its privacy protocol. Orchard is the latest generation, activated in May 2022 with the NU5 network upgrade, and uses the halo2 zero-knowledge proof system [2] to verify transactions. When a user makes a shielded transaction, they act as the prover: they construct a zero-knowledge proof using the Orchard circuit that demonstrates the transaction is valid — they own the note, the nullifier is correctly derived, and amounts balance — without revealing any private details. Network nodes act as verifiers, checking the proof without seeing the underlying data. The Orchard circuit defines the constraints that every valid proof must satisfy. The vulnerability is in this circuit implementation and involves four core concepts in Zcash: keys, addresses, notes, and nullifiers.

Keys. Zcash's key hierarchy is more complex than most blockchains. A single spending key (the wallet secret) derives several sub-keys: ask (spend authorizing secret key), nk (nullifier deriving key), and rivk (a randomizer). From these, the system computes ak (authorizing public key) and ivk (incoming viewing key, via ivk = Commit(ak, nk, rivk)). The key ivk is used to identify and receive incoming funds.

Addresses. To create an Orchard address, the user picks a diversifier d (an 11-byte value), which determines a diversified base point g_d = DiversifyHash(d). The address is the pair (d, pk_d), where the diversified transmission key pk_d is computed as an elliptic curve scalar multiplication: pk_d = [ivk] g_d. This means the address is cryptographically bound to the user's viewing key.

Notes. A Note is an asset record representing received funds. For privacy, what gets stored on-chain is not the Note itself but a Note commitment cm (a cryptographic commitment to the Note's contents, including pk_d, value, and other data). The Note is locked to the recipient's address.

Nullifiers. When spending a Note, the spender reveals a nullifier nf, computed from the Note's data and the nullifier key nk: nf = Extract_P([(F_nk(ρ) + ψ) mod p]G + cm). The critical security property is that each Note must produce exactly one nullifier. Once a nullifier appears on-chain, the corresponding Note is considered permanently spent. If the same Note could produce different nullifiers, it could be spent multiple times without anyone noticing.

The binding chain. These concepts are connected by a cryptographic binding chain:

The red-highlighted nodes (pk_d, nk) are the points where the vulnerability breaks the binding chain: the circuit must enforce pk_d = [ivk] g_d, which binds the spender to the correct ivk, and therefore to the correct nk for that Note. If this binding is broken, the spender can forge a fake ivk and therefore use a different nk for each spend. Different nk values produce different nullifiers for the same note, enabling double-spending.

How the circuit enforces this binding. The relation pk_d = [ivk] g_d is an elliptic curve scalar multiplication (Q = [k]P), implemented using the double-and-add algorithm. In a zero-knowledge circuit, the verifier does not execute the algorithm directly; instead, the circuit constrains every intermediate step. A correct scalar multiplication circuit must enforce, among other things, that the internal loop base point equals the intended input base point (P_loop = P_input). Without this constraint, the circuit may prove that a valid scalar multiplication was performed, but with the wrong base point, making the entire binding chain meaningless.

Vulnerability Analysis

The vulnerable ECC gadget was used in the scalar multiplication relation that enforces pk_d = [ivk] g_d, where Q = pk_d, k = ivk, and P = g_d. The vulnerable code is located in the halo2 repository's variable-base scalar multiplication implementation (incomplete.rs L309-L310):

// incomplete.rs (full source linked above)
298  for (row, k) in bits.iter().enumerate() {
299      // z_{i} = 2 * z_{i+1} + k_i
         ...
305      z = region.assign_advice(|| "z", self.z, row + offset, || z_val)?;
306      zs.push(Z(z.clone()));
307
308      // Assign `x_p`, `y_p`
309      region.assign_advice(|| "x_p", self.double_and_add.x_p, row + offset, || x_p)?;  // BUG
310      region.assign_advice(|| "y_p", self.y_p, row + offset, || y_p)?;                  // BUG
311
312      // If the bit is set, use `y`; if the bit is not set, use `-y`
313      let y_p = y_p
314          .zip(k.as_ref())
315          .map(|(y_p, k)| if !k { -y_p } else { y_p });
316
317      // Compute and assign lambda1
318      let lambda1 = y_a.zip(y_p).zip(x_a.value()).zip(x_p)
             .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert());
         ...
     }

In a zero-knowledge circuit, the prover fills in every cell value; the circuit itself cannot copy or push values between cells — it can only define constraints that the verifier checks. The two lines marked BUG use assign_advice() to write the base point coordinates x_p and y_p (the private circuit inputs known as witness values) into the circuit's advice columns (the prover's private input region). This function fills in a value without creating a constraint that ties it to the external base point. A separate constraint does ensure that the base values are equal across all loop iterations — the prover cannot use a different base point in each iteration. However, the prover can substitute a single arbitrary base point across all iterations, and nothing in the circuit will reject it, because no constraint anchors the loop's base values to the actual g_d passed in from outside.

The correct function is copy_advice(), which fills the value and adds an equality constraint (a copy constraint) enforcing that it matches the actual base point g_d. Because g_d varies per address (derived from each address's diversifier), the circuit cannot hardcode it — it must constrain the loop's base point to match the g_d computed upstream.

As a result, the circuit did not actually enforce pk_d = [ivk] g_d. A malicious prover could supply an arbitrary base point inside the loop (instead of the correct g_d), and the circuit would still accept the proof: the internal computation remained algebraically self-consistent, but was no longer anchored to the protocol-specified diversified base point. This extra degree of freedom allows the prover to choose a different nk each time, compute the corresponding ivk = Commit(ak, nk, rivk), and use the unconstrained scalar multiplication to make the forged ivk pass. Because the nullifier depends on nk, each spend yields a distinct nullifier:

same note N + nk_1 → nf_1
same note N + nk_2 → nf_2
where: nf_1 ≠ nf_2

Consensus only checks whether a nullifier has already appeared on-chain. Because each spend produces a unique, valid-looking nullifier, the double-spend is invisible to the network.

The fix [3] adds a copy_advice() call in the first iteration of the loop (row == 0), creating a copy constraint that anchors the loop's base point to the actual base passed in from outside. The remaining iterations continue to use assign_advice(), but the existing inter-iteration consistency constraint propagates the anchor to all of them.

Impact and Response

Exploitation scenario. A malicious prover could exploit the vulnerability to spend the same Orchard note multiple times, each time generating a different nullifier. Because the zero-knowledge proof conceals private circuit inputs, the fraudulent nullifiers are indistinguishable from legitimate ones. The attack leaves no definitive cryptographic on-chain evidence, making it impossible to conclusively determine whether it was ever exploited.

The vulnerability existed since Orchard's activation in May 2022 (NU5 upgrade), giving a total exposure window of over four years. Zcash's turnstile accounting limits how much counterfeit value can exit the Orchard pool into transparent or Sapling pools, bounding the realized impact on the broader supply. However, counterfeit notes could exist undetected inside the shielded pool, and its privacy properties make it impossible to cryptographically prove whether the vulnerability was ever exploited.

Note: the turnstile only triggers when total outflows exceed total inflows for a pool. An attacker could withdraw counterfeit value in small amounts over time without hitting this limit. The discrepancy would only become apparent if legitimate users collectively tried to withdraw more than the pool actually holds.

Discovery and response. The vulnerability was discovered on May 29, 2026, by security researcher Taylor Hornby (contracted by Shielded Labs), using an AI-assisted auditing framework with Anthropic's Opus 4.8 model released the day before. Prior audits of the same circuit with older models had not found the bug. Hornby reported the issue to ZODL the same day. An emergency soft fork on June 2 (UTC) temporarily disabled Orchard transactions, and the NU6.2 network upgrade on June 3 (UTC, block 3,364,600) introduced a corrected circuit, restoring Orchard functionality [1]. Following public disclosure on June 4, ZEC lost over 40% of its value, with liquidations exceeding $100 million [4].

Conclusion

The Zcash Orchard vulnerability was caused by a missing equality constraint in the scalar multiplication circuit, allowing the prover to bypass the base point binding and forge nullifiers to double-spend. Unlike traditional smart contract bugs that can often be caught through code review or testing, a soundness bug in a ZK circuit requires understanding the gap between what the circuit actually proves and what it should prove — a subtlety that evaded over four years of professional audits by expert cryptographers.

The halo2 library is used across the ZKP ecosystem, and similar under-constrained relations could exist in other projects built on these cryptographic building blocks. Protocols using zero-knowledge proofs should implement balance integrity checks (such as turnstile accounting) to bound the potential impact of undiscovered soundness bugs: without Zcash's turnstile mechanism, counterfeit value created inside the shielded pool could have flowed freely into the broader supply. Shielded Labs has announced plans to formally verify the Orchard circuit mathematically.

The discovery is a typical example of a human-in-the-loop workflow: an experienced security researcher built the auditing framework and directed the investigation, while AI handled the breadth of checking individual circuit constraints. Neither component alone was sufficient: prior AI-only runs with older models missed the bug, and years of expert human review missed it as well. Hornby is himself a Zcash security expert — domain expertise was needed to design the right audit scope for AI to operate within. As recent research released by BlockSec has also shown [5], AI models are making rapid progress in security analysis, but expert guidance is still needed to point AI at the right targets and validate what it finds. The interactive collaboration between experts and AI — rather than relying on AI alone — is the most effective working model.

References

  1. The Orchard counterfeiting vulnerability and next steps, Zcash Community Forum, June 4, 2026. Link
  2. halo2: A zero-knowledge proof system, GitHub. Link
  3. Fix: anchor scalar multiplication base point via copy constraint, halo2 GitHub. Link
  4. Why ZEC fell 40% even after Zcash patched a shielded pool bug, CoinTelegraph, June 5, 2026. Link
  5. Re-Evaluating EVMBench: Are AI Agents Ready for Smart Contract Security?, arXiv, 2026. Link

Get Started with Phalcon Security

Detect every threat, alert what matters, and block attacks.

Try now for free

About BlockSec

BlockSec is a full-stack blockchain security and crypto compliance provider. We build products and services that help customers to perform code audit (including smart contracts, blockchain and wallets), intercept attacks in real time, analyze incidents, trace illicit funds, and meet AML/CFT obligations, across the full lifecycle of protocols and platforms.

BlockSec has published multiple blockchain security papers in prestigious conferences, reported several zero-day attacks of DeFi applications, blocked multiple hacks to rescue more than 20 million dollars, and secured billions of cryptocurrencies.

Sign up for the latest updates
Newsletter - May 2026
Security Insights

Newsletter - May 2026

In May 2026, the DeFi ecosystem experienced three major security incidents. Echo Protocol lost ~$76.7M due to an administrator key compromise that enabled unauthorized minting of unbacked eBTC on Monad, StablR suffered ~$12.8M from a multisig governance breach leading to unauthorized stablecoin issuance, and the Verus-Ethereum Bridge incurred ~$11.7M following a type-validation failure that allowed a crafted supplemental export to be misclassified as a valid primary export.

~$16M Lost: DxSale, SquidRouterModule & More | BlockSec Weekly
Security Insights

~$16M Lost: DxSale, SquidRouterModule & More | BlockSec Weekly

This weekly security report covers 5 notable attack incidents between May 25 and May 31, 2026, with combined losses of approximately $16M across BNB Chain, Ethereum, Base, Arbitrum, and Cosmos. Key incidents include the DxSale token locker exploit ($7.3M) involving three missing state updates compounded by a deployer key compromise, the SquidRouterModule exploit ($3.2M) caused by improper input validation in an Axelar Bridge integration that allowed forged cross-chain messages to drain 86 Safe wallets, and the Gravity Bridge signing key compromise ($5.4M). Other incidents involve a compromised deployer key (Stake DAO, $91K) and a vulnerable off-chain bridge backend (Alephium, $300K).

~$104.6M Lost: Verus, RetoSwap & More | BlockSec Weekly
Security Insights

~$104.6M Lost: Verus, RetoSwap & More | BlockSec Weekly

This BlockSec weekly security report covers 5 notable attack incidents identified between May 18 and May 24, 2026, with total estimated losses of approximately $104.6M. Two incidents are analyzed in detail: the highlighted $11.7M Verus-Ethereum Bridge exploit, where a type-validation failure allowed a handcrafted supplemental export output to be misclassified as a valid primary export; and the $2.7M RetoSwap exploit on Monero, where a protocol-level authentication flaw in the P2P trade flow allowed an attacker to hijack the arbitrator role via a forged ACK message. Three additional key compromise incidents (EchoProtocol, Polymarket, StablR) accounted for ~$90.2M.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit