Skip to content

feat(wasm-utxo): expose requiresPrevTx prevTx-inclusion policy#306

Draft
OttoAllmendinger wants to merge 1 commit into
masterfrom
otto/T1-3654-requires-prevtx
Draft

feat(wasm-utxo): expose requiresPrevTx prevTx-inclusion policy#306
OttoAllmendinger wants to merge 1 commit into
masterfrom
otto/T1-3654-requires-prevtx

Conversation

@OttoAllmendinger

@OttoAllmendinger OttoAllmendinger commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Expose a pure-JS requiresPrevTx policy from @bitgo/wasm-utxo so all callers share one source of truth for whether a PSBT input needs non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

Why

wasm-utxo's add_wallet_input_to_psbt deserializes prev_tx with the standard Bitcoin consensus::deserialize, which rejects Zcash overwintered transactions — the root cause of the Zcash (tzec) signing failures (T1-3654). Centralizing the prevTx-inclusion decision inside the shared wasm-utxo package gives every caller the same correct policy without each one re-deriving it.

Change

New pure-JS module packages/wasm-utxo/js/fixedScriptWallet/prevTx.ts exporting:

  • requiresPrevTx(coinName, txFormat, chain) — returns false when witness_utxo-only suffices:
    • psbt-lite never includes non_witness_utxo;
    • segwit/taproot inputs (non-p2sh chains) always use witness_utxo;
    • value-committing coins commit the input amount into the sighash, so prevTx is pointless for signing p2sh inputs even when txFormat === "psbt":
      • Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount. (Including prevTx also crashes wasm-utxo.)
      • BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected BIP-143 sighash (SIGHASH_FORKID, the default for the whole family) commits the 8-byte value as preimage item feat(wasm-utxo): implement MuSig2 with BitGo-specific p2tr variant #6.
  • isValueCommittingCoin — backed by a mainnet Set {zec, bch, bcha, bsv, btg} normalized via getMainnet (covers testnets).
  • isZcashCoin — kept (selects ZcashBitGoPsbt).
  • isNonSegwitChain — checks chain === 0 || 1 literally so the module does not trigger WASM initialization; the predicate is cheap to evaluate without loading wasm-utxo.
  • TxFormat type ("psbt" | "psbt-lite" | "legacy").

Exported both namespaced (fixedScriptWallet.requiresPrevTx) and as top-level named exports alongside CoinName/getMainnet.

Test plan

  • npx mocha test/fixedScript/prevTx.ts — 26 passing (full requiresPrevTx truth table + isValueCommittingCoin/isZcashCoin/isNonSegwitChain suites)
  • npm run build:ts-esm (tsc) — clean
  • eslint + prettier on touched files — clean
  • CI on this PR

Out of scope

  • No Rust change — the wasm-utxo consensus::deserialize crash site is untouched; this PR only exposes a shared predicate so callers can avoid sending prevTx when it's pointless. (The Zcash-aware decode_zcash_transaction_parts hardening and the repro test live on a separate jj change.)

Refs: T1-3654

@linear-code

linear-code Bot commented Jun 29, 2026

Copy link
Copy Markdown

T1-3654

@OttoAllmendinger OttoAllmendinger force-pushed the otto/T1-3654-requires-prevtx branch 3 times, most recently from ebe2805 to 6d7ba59 Compare June 29, 2026 17:16
Add a pure-JS prevTx-inclusion predicate to @bitgo/wasm-utxo so all
callers share one source of truth for whether a p2sh PSBT input needs
non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

requiresPrevTxForP2sh(coinName) answers only the coin-level question
for an input the caller has already determined is p2sh (non-segwit) and
whose tx format includes prevTx (e.g. "psbt", not "psbt-lite"). It
returns false for value-committing coins whose sighash commits the
input amount, making prevTx cryptographically pointless for signing
p2sh inputs:

- Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount.
  Including prevTx also crashes wasm-utxo, whose consensus::deserialize
  rejects Zcash overwintered transactions.
- BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected
  BIP-143 sighash (SIGHASH_FORKID, the default for the whole family)
  commits the 8-byte value as preimage item #6.

The value-committing mainnet set {zec, bch, bcha, bsv, btg} is
normalized via getMainnet (covers testnets). The module is pure JS — no
WASM initialization — so the predicate is cheap to evaluate without
loading wasm-utxo.

Exported both namespaced (fixedScriptWallet.requiresPrevTxForP2sh) and
as a top-level named export (alongside CoinName/getMainnet).

Refs: T1-3654
@OttoAllmendinger OttoAllmendinger force-pushed the otto/T1-3654-requires-prevtx branch from 6d7ba59 to de82daa Compare June 29, 2026 17:19
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