Summary
Zebra Transparent SIGHASH_SINGLE Corresponding-Output Handling Diverges From zcashd
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.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is GHSA-CWFQ-RFCR-8HMP? GHSA-CWFQ-RFCR-8HMP is a critical-severity security vulnerability in zebrad (rust), affecting versions < 4.4.0. It is fixed in 4.4.0.
- Which versions of zebrad are affected by GHSA-CWFQ-RFCR-8HMP? zebrad (rust) versions < 4.4.0 is affected.
- Is there a fix for GHSA-CWFQ-RFCR-8HMP? Yes. GHSA-CWFQ-RFCR-8HMP is fixed in 4.4.0. Upgrade to this version or later.
- Is GHSA-CWFQ-RFCR-8HMP exploitable, and should I be worried? Whether GHSA-CWFQ-RFCR-8HMP is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-CWFQ-RFCR-8HMP is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-CWFQ-RFCR-8HMP? Upgrade
zebradto 4.4.0 or later.