Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-cwfq-rfcr-8hmp
No affected components available
Zebra Transparent SIGHASH_SINGLE Corresponding-Output Handling Diverges From zcashd
Summary
For V5+ transparent spends, Zebra and zcashd disagree on the same consensus rule: SIGHASH_SINGLE must fail when the input index has no corresponding output. zcashd treats this as consensus-invalid under ZIP-244, while Zebra's transparent verification path computes a digest for the missing-output case instead of failing.
The result is a direct block-validity split. A malformed V5 transparent transaction can be accepted by Zebra, retained in Zebra's mempool, selected into Zebra getblocktemplate, mined into a block, and then rejected by zcashd.
Details
Validated code revisions used during analysis:
zcashd:2c63e9aa08cb170b0feb374161bea94720c3e1f5Zebra:a905fa19e3a91c7b4ead331e2709e6dec5db12cb
Scope note:
- earlier triage material grouped pre-V5 and V5 behavior together;
- re-execution on the pinned revisions did not reproduce the claimed pre-V5 / V4 reject-side behavior;
- this advisory therefore covers the V5+ / ZIP-244 variant only.
zcashd side:
- Transparent scripts in blocks are checked through
TransactionSignatureChecker::CheckSig()andSignatureHash():zcash/src/script/interpreter.cpp. - In the ZIP-244 branch,
SignatureHash()explicitly throws whenSIGHASH_SINGLEorSIGHASH_SINGLE|ANYONECANPAYis used withnIn >= txTo.vout.size():zcash/src/script/interpreter.cpp. CheckSig()catches that exception and returnsfalse, causing the transparent script to fail.
Zebra side:
- V5 transparent inputs route into the same FFI-based transparent script verifier used for block validation:
zebra/zebra-consensus/src/transaction.rs. Zebraconverts the decoded hash type and asks its Rust sighash engine for a digest without adding the corresponding-output pre-check thatzcashdenforces first:zebra/zebra-script/src/lib.rs,zebra/zebra-chain/src/primitives/zcash_primitives.rs.Zebraforwards canonicalSIGHASH_SINGLEinto the Rust ZIP-244 implementation.- In that implementation, when
input.index() >= bundle.vout.len(), the code usestransparent_outputs_hash::<TxOut>(&[])instead of erroring:zcash_primitives/src/transaction/sighash_v5.rs,zcash_primitives/src/transaction/sighash_v5.rs.
Why this is exploitable:
- the malformed transaction only needs fewer transparent outputs than inputs;
- the attacker signs the digest that
Zebracomputes for the missing-output case; Zebrathen sees a valid transparent signature, whilezcashdnever reaches the same digest because it fails first.
Ordinary path viability:
zcashdordinary mempool admission is not the practical trigger path, because the same ZIP-244SignatureHash()checks fail there first:zcash/src/main.cpp,zcash/src/script/interpreter.cpp.Zebraordinary mempool admission is viable becauseZebrauses the same transparent verifier for mempool and block validation and does not have a separate "one output per input" standardness rule here:zebra/zebra-consensus/src/transaction.rs,zebra/zebrad/src/components/mempool/storage.rs.Zebrais a block-template producer, so the realistic stock path isZebramempool ->Zebragetblocktemplate-> external miner:zebra/zebra-rpc/src/methods/types/get_block_template/zip317.rs.
PoC
Validated commits:
zcashd:2c63e9aa08cb170b0feb374161bea94720c3e1f5Zebra:a905fa19e3a91c7b4ead331e2709e6dec5db12cb
Manual reproduction steps:
- Build an otherwise-valid V5 transaction with at least two transparent inputs and only one transparent output.
- Sign input
0normally. - Sign input
1with canonicalSIGHASH_SINGLEorSIGHASH_SINGLE|ANYONECANPAY. - Use the digest returned by
Zebra's ZIP-244 path, where the missing output contributestransparent_outputs_hash([]). - Submit the transaction to
Zebraand tozcashd. - Observe:
Zebraaccepts it into the mempool;Zebraselects it intogetblocktemplate;Zebracan mine and accept a block containing it;zcashdrejects it in the ordinary mempool path.
Impact
This is a direct V5+ transparent consensus split.
Who can trigger it:
- an ordinary transaction author can craft the malformed V5 transparent transaction;
- the accept-side stock path is
Zebra's mempool and block-template path; - an external miner still has to include the transaction in a block for the split to materialize.
Who is impacted:
Zebracan accept and template a transaction / block thatzcashdrejects;- this makes the issue both a consensus-divergence problem and a practical
Zebrablock-template safety problem.
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker does not need any special privileges or access rights. No user interaction is needed for the attacker to exploit this vulnerability.
Exploitation activity has been observed. Apply available patches or mitigations urgently.
Probability that this vulnerability will be exploited in the wild within the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard