Skip to content

fix(zcash): use S.2 transparent_sig_digest for Orchard sighash in shield txs#247

Merged
BitHighlander merged 2 commits into
alphafrom
fix/zcash-orchard-s2-transparent-digest
May 26, 2026
Merged

fix(zcash): use S.2 transparent_sig_digest for Orchard sighash in shield txs#247
BitHighlander merged 2 commits into
alphafrom
fix/zcash-orchard-s2-transparent-digest

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

Problem

Shield transactions (transparent input → Orchard output) broadcast with "could not validate orchard proof" from Zebra. Orchard spend-auth and binding signatures were being rejected by the network.

Root Cause

ZIP-244 §4.9 defines two forms of the transparent digest:

Form When used Contents
T.1 (txid form) Transaction ID, deshield/private prevouts || sequences || outputs
S.2 (sig form) Orchard sighash in shield txs hash_type || prevouts || amounts || scripts || sequences || outputs || empty_txin_hash

The firmware was computing T.1 to verify the sidecar-provided transparent_digest, then using T.1 in the Orchard sighash. Zebra verifies Orchard sigs with S.2 for any tx with transparent inputs → signatures invalid at the network.

Fix

Add zcash_compute_orchard_transparent_sig_digest:

  • n_inputs == 0 → delegates to T.1 (deshield / private-send, per §4.10b)
  • n_inputs > 0 → computes S.2: BLAKE2b("ZTxIdTranspaHash", 0x01 || prevouts || amounts || scripts || sequences || outputs || BLAKE2b("Zcash___TxInHash", ""))

Switch zcash_finalize_transparent_digest to use the new function so firmware verifies and signs with the same form Zebra uses.

Files

  • lib/firmware/zcash.c — new zcash_compute_orchard_transparent_sig_digest function
  • include/keepkey/firmware/zcash.h — declaration
  • lib/firmware/fsm_msg_zcash.hzcash_finalize_transparent_digest uses new function
  • lib/firmware/ethereum_contracts/zxappliquid.c — clang-format cleanup from make format

Test Plan

  • Build kkemu on alpha with this branch
  • Run shield tx (transparent → Orchard) end-to-end: firmware must accept without "Transparent digest mismatch"
  • Broadcast to Zcash mainnet: Zebra must accept without "could not validate orchard proof"
  • Run deshield tx (Orchard → transparent): confirm T.1 path still works (n_inputs == 0 branch)

…eld txs

ZIP-244 §4.9 requires the S.2 form of the transparent digest when computing
the Orchard sighash for transactions with non-empty transparent inputs (shield
txs). S.2 includes UTXO amounts, scriptPubKeys, and an empty txin_sig_digest;
the T.1 (txid) form omits these.

Previously the firmware computed the T.1 form and verified it against the
sidecar-provided value, then used that digest in the Orchard sighash. This
produced Orchard spend-auth and binding signatures that passed local
BatchValidator checks but were rejected by Zebra ("could not validate orchard
proof") because Zebra uses S.2 when verifying Orchard sigs.

Add zcash_compute_orchard_transparent_sig_digest which:
- Returns T.1 when n_inputs == 0 (deshield / private-send, per §4.10b)
- Returns S.2 with empty txin_sig_digest when n_inputs > 0 (shield tx)

Switch zcash_finalize_transparent_digest to use the new function so both the
firmware verification and the Orchard sighash computation use S.2.
The prior commit included an unrelated clang-format pointer-style change
(EthereumSignTx *msg → EthereumSignTx* msg) that breaks CI lint-format.
Revert to alpha to keep the PR focused on the Zcash S.2 fix only.
@BitHighlander BitHighlander merged commit 48366b6 into alpha May 26, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant