Missing subresource integrity check In zebrad

Description

Zebra's Transparent SIGHASH_SINGLE Handling Diverges from zcashd for Corresponding Outputs

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: 2c63e9aa08cb170b0feb374161bea94720c3e1f5

    Zebra: 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() and SignatureHash(): zcash/src/script/interpreter.cpp.

    In the ZIP-244 branch, SignatureHash() explicitly throws when SIGHASH_SINGLE or SIGHASH_SINGLE|ANYONECANPAY is used with nIn >= txTo.vout.size(): zcash/src/script/interpreter.cpp.

    CheckSig() catches that exception and returns false, causing the transparent script to fail.

Zebra side:

Why this is exploitable:

    the malformed transaction only needs fewer transparent outputs than inputs;

    the attacker signs the digest that Zebra computes for the missing-output case;

    Zebra then sees a valid transparent signature, while zcashd never reaches the same digest because it fails first.

Ordinary path viability:

PoC

Validated commits:

    zcashd: 2c63e9aa08cb170b0feb374161bea94720c3e1f5

    Zebra: a905fa19e3a91c7b4ead331e2709e6dec5db12cb

Manual reproduction steps:

    Build an otherwise-valid V5 transaction with at least two transparent inputs and only one transparent output.

    Sign input 0 normally.

    Sign input 1 with canonical SIGHASH_SINGLE or SIGHASH_SINGLE|ANYONECANPAY.

    Use the digest returned by Zebra's ZIP-244 path, where the missing output contributes transparent_outputs_hash([]).

    Submit the transaction to Zebra and to zcashd.

    Observe:

      Zebra accepts it into the mempool;

      Zebra selects it into getblocktemplate;

      Zebra can mine and accept a block containing it;

      zcashd rejects 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:

    Zebra can accept and template a transaction / block that zcashd rejects;

    this makes the issue both a consensus-divergence problem and a practical Zebra block-template safety problem.

Mitigation

Update Impact

Minimal update. May introduce new vulnerabilities or breaking changes.

Ecosystem
Package
Affected version
Patched versions