# Zebra has Consensus Divergence in Transparent Sighash Hash-Type Handling due to Stale Buffer

Zebra's FFI sighash callback returned None for invalid hash types but left the C++ buffer stale, so an undefined-hash-type OP_CHECKSIG verified against a prior valid digest, splitting consensus with zcashd.

- Payout: Research
- Severity: critical
- Program: Zebra
- Found by: sangsoo-osec
- Disclosed: 2026-05-07
- CVE: CVE-2026-44497
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-gq4h-3grw-2rhv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-gq4h-3grw-2rhv
- Tags: area:web3 domain:web3 impact:dos impact:integrity kind:research platform:ghsa sev:critical tech:rust technique:incomplete-fix technique:source-review vuln:business-logic vuln:crypto vuln:signature-replay

**Target:** zebrad transparent transaction script verification (zcash_script FFI bridge)
**Root cause:** The Rust sighash callback returns None for undefined hash types but the FFI bridge only writes the C++ buffer on Some, while the C++ checker reads the buffer unconditionally, losing the failure signal and reusing a stale digest.
**Steps:**
1. Construct a transparent output spent by a script containing a valid `OP_CHECKSIGVERIFY` immediately followed by an `OP_CHECKSIG` with an undefined hash type.
2. First opcode primes the C++ sighash buffer with a valid digest.
3. Second opcode makes Zebra's Rust callback return None; C++ verifies the invalid signature against the stale digest and accepts.
4. Submit the spend: Zebra accepts, zcashd rejects, causing a consensus split.
**How found:** Reviewing the fix for GHSA-8m29-fpq5-89jj: noticed the new V5 hash-type check returns None on the Rust side, then traced the FFI bridge and saw the C++ checker reads the sighash buffer unconditionally, so the error signal is dropped and a prior valid digest persists.
**Exploitation:** Attacker crafts a transparent spend whose script primes the shared C++ sighash buffer via a valid OP_CHECKSIGVERIFY, then presents an invalid-hash-type OP_CHECKSIG that Zebra validates against the stale digest. Zebra accepts the transaction while zcashd rejects it, partitioning the network and enabling potential double-spends against affected nodes.
**Escalation:** Consensus split between Zebra and zcashd nodes enables network partitioning and potential double-spend; impact currently limited because most miners run zcashd.
**Insight:** When a fix adds a validation error across an FFI boundary, verify the error actually propagates: a callback returning Option/Result whose None path skips a buffer write leaves stale state that the foreign side reads unconditionally. Prime-then-invalidate opcode sequences (valid CHECKSIGVERIFY before invalid CHECKSIG) expose such stale-buffer reuse.

