Monitor vulnerabilities like this one. Sign up free to get alerted when software you use is affected.
9.3

CVE-2026-52735: Zebra's P2SH Sigop Counter Has Incorrect Block Validation

GHSA-gf9r-m956-97qx CVE-2026-52735 GHSA-gf9r-m956-97qx
Summary

Zebra's block validation process has a bug that can cause it to accept blocks that other nodes may reject. This can lead to a chain split, where different nodes have different versions of the blockchain. If you run Zebra, you should update to a fixed version to prevent this issue.

What to do
  • Update zebra-script to version 7.0.0.
  • Update zebrad to version 4.5.0.
  • Update conradoplg zebra-script to version 7.0.0.
  • Update conradoplg zebrad to version 4.5.0.
Affected software
Ecosystem VendorProductAffected versions
rust zebra-script <= 6.0.1
Fix: upgrade to 7.0.0
rust zebrad <= 4.4.1
Fix: upgrade to 4.5.0
crates.io conradoplg zebra-script < 7.0.0
Fix: upgrade to 7.0.0
crates.io conradoplg zebrad < 4.5.0
Fix: upgrade to 4.5.0
Original title
zebrad has consensus divergence via P2SH sigop undercount in pure-Rust disabled-opcode parser
Original description
### Am I affected

You are affected if:

1. You run any version of `zebrad` up to and including `v4.4.1`.
2. Your node validates blocks on mainnet, testnet, or any network where both Zebra and zcashd nodes participate.

All default configurations are affected. No feature flags, non-default settings, or special build options are required.

### Summary

Zebra's P2SH sigop counter uses a pure-Rust code path that short-circuits on disabled opcodes (such as `OP_CODESEPARATOR`), returning a partial count of zero for any sigops following the disabled opcode. The reference implementation (zcashd) correctly counts through disabled opcodes in its static sigop analysis. This produces a consensus divergence: Zebra accepts blocks that zcashd rejects when the block-wide `MAX_BLOCK_SIGOPS = 20,000` threshold is crossed on one side but not the other.

An attacker can exploit this without mining capability. Broadcasting transactions that spend P2SH outputs with malicious redeem scripts is sufficient; any Zebra miner who includes those transactions in a block triggers a chain split between Zebra and zcashd validators.

### Details

The P2SH sigop counter at `zebra-script/src/lib.rs:399` calls `script::Code(redeemed_bytes).sig_op_count(true)`, which is a pure-Rust path through `zcash_script-0.4.4`. The legacy (non-P2SH) sigop counter at `lib.rs:282-289` correctly uses the C++ FFI via `interpreter.legacy_sigop_count_script()`. Only the P2SH path bypasses the FFI.

The Rust parser in `zcash_script-0.4.4/src/opcode/mod.rs:1247-1260` treats 16 disabled opcodes (0x7e through 0xab, including `OP_CAT`, `OP_SUBSTR`, `OP_AND`, `OP_OR`, `OP_XOR`, `OP_2MUL`, `OP_2DIV`, `OP_MUL`, `OP_DIV`, `OP_MOD`, `OP_LSHIFT`, `OP_RSHIFT`, and `OP_CODESEPARATOR`) as `Err(Error::Disabled(...))`. The `sig_op_count` function at `iter.rs:104-115` uses `try_fold`, which terminates on the first `Err` and returns the partial sum accumulated so far.

zcashd's `GetOp2` (`script.h:514-562`) returns `true` for all non-push opcodes including the disabled range. Its `GetSigOpCount(true)` (`script.cpp:152-174`) continues counting through disabled opcodes. zcashd rejects disabled opcodes at execution time in the interpreter, not during static sigop analysis.

A redeem script of `[0xab, OP_CHECKMULTISIG x 50]` produces: Zebra = 0 sigops, zcashd = 1,000 sigops. Across 21 inputs in a block, Zebra computes 0 while zcashd computes 21,000, crossing the `MAX_BLOCK_SIGOPS = 20,000` threshold on one side only.

### Patches

Patched in Zebra 4.4.2. The fix routes the P2SH sigop counter through the same C++ FFI already used by the legacy sigop counter.

### Workarounds

There is no configuration-level workaround. All Zebra nodes validating blocks on a network shared with zcashd are affected. Upgrade as soon as the patched version is available.

### Impact

A chain split between Zebra and zcashd validators. The attacker broadcasts spending transactions referencing P2SH outputs whose redeem scripts contain a disabled opcode followed by `OP_CHECKSIG` or `OP_CHECKMULTISIG` opcodes. When a Zebra miner (estimated ~30% of current network hashrate) includes these transactions in a block, Zebra validators accept the block while zcashd validators reject it with `bad-blk-sigops`. The two halves of the network diverge and every subsequent block extending the Zebra-side tip inherits the divergence.

The attacker does not need mining capability, RPC access, or any special privileges. The cost is the transaction fees for the funding and spending transactions.

### Credit

Reported by `@samsulselfut` via a private GitHub Security Advisory submission.
ghsa CVSS4.0 9.3
Vulnerability type
CWE-684
Published: 2 Jul 2026 · Updated: 2 Jul 2026 · First seen: 2 Jul 2026