feat(wasm-utxo): add sighash policy primitives to BitGoPsbt - #416
Merged
Merged
Conversation
Add get_input_sighash_types() and assert_sighash_all_policy() to the BitGoPsbt wasm class and its TypeScript wrapper. assert_sighash_all_policy enforces the policy required when signing externally supplied PSBTs: every input's declared sighash type and every signature already present in the PSBT must commit to the entire transaction (SIGHASH_ALL 0x01, SIGHASH_ALL|FORKID 0x41 on BCH-family coins, SIGHASH_DEFAULT 0x00/SIGHASH_ALL on Taproot inputs). An absent sighash type is accepted and uses the signer default. get_input_sighash_types exposes each input's declared BIP-174 sighash type so callers can display it or apply their own policies. Why: signers honor the per-input PSBT_IN_SIGHASH_TYPE field, and SIGHASH_NONE/SINGLE/ANYONECANPAY signatures do not bind the signer to the transaction outputs. Callers that ingest foreign PSBTs (wallet recovery tools) need one shared, network-aware primitive instead of per-app reimplementations that miss the BCH FORKID and Taproot rules. Ticket: WCN-1994 Session-Id: 5c05e047-31d0-43fa-91ab-f1c595b37850 Task-Id: f7cdbdd7-f9f0-4164-a69b-0c7e58e609a8
Add mocha coverage for getInputSighashTypes and assertSighashAllPolicy: declared NONE/SINGLE/ANYONECANPAY and combined modes are rejected before signing, absent types use the signer default, BCH-family coins require SIGHASH_ALL|FORKID, Taproot inputs accept SIGHASH_DEFAULT, and real signing runs (p2wsh, BCH p2sh, Taproot) keep the policy satisfied while binding signatures to the outputs (an output swap invalidates every signature). Ticket: WCN-1994 Session-Id: 5c05e047-31d0-43fa-91ab-f1c595b37850 Task-Id: f7cdbdd7-f9f0-4164-a69b-0c7e58e609a8
3 tasks done
Draft
3 of 5 tasks
OttoAllmendinger
marked this pull request as ready for review
September 30, 2026 07:56
OttoAllmendinger
approved these changes
Sep 30, 2026
hitansh-madan
approved these changes
Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
get_input_sighash_types()to theBitGoPsbtwasm class (and the TypeScript wrapper asgetInputSighashTypes()): returns each input's declared BIP-174PSBT_IN_SIGHASH_TYPE,undefined/nullwhen absent (signer default applies).Adds
assert_sighash_all_policy()(TS:assertSighashAllPolicy()): asserts the policy required when signing externally supplied PSBTs. For every input it rejectsAccepted types are
SIGHASH_ALL(0x01),SIGHASH_ALL|SIGHASH_FORKID(0x41) on BCH-family coins, andSIGHASH_DEFAULT(0x00)/SIGHASH_ALLon Taproot inputs; an absent type uses the signer default. MuSig2 partial signatures carry no sighash byte and are governed by the declared type checked above.Rust unit tests cover the accepted/rejected sets per network (incl. BCH FORKID and Taproot DEFAULT) and the policy assert on declared types, injected signatures, and real signed wallet PSBTs; mocha tests in
test/fixedScript/sighashPolicy.tscover the wasm/TS surface end-to-end, including real signing runs for p2wsh/BCH-p2sh/Taproot and the output-swap invalidation property.Why
The signer honors the per-input
PSBT_IN_SIGHASH_TYPEembedded in a PSBT. A foreign PSBT can requestSIGHASH_NONE/SINGLE/ANYONECANPAY, producing a signature that does not bind the signer to the transaction outputs — AnchorWatch demonstrated an output-swap drain of recovered funds this way (SIG-001 / WCN-1994). Consumers that ingest foreign PSBTs need one shared, network-aware primitive; per-app reimplementations miss the BCH FORKID and Taproot rules (a hardcoded0x01-only check rejects every BCH PSBT).Test plan
cargo test --lib— 620 passed (5 new tests)cargo clippy --all-targets --all-features -- -D warnings,cargo fmt --check— cleantsc --noEmit -p tsconfig.test.json— cleantest/**/*.ts) — 1572 passing (14 new tests)test:wasm-packchrome/node legs run in CI; not runnable in this sandbox)Stack
This PR is part 1 of 3 in the WCN-1994 stack. Review and merge in order:
@bitgo/wasm-utxo≥ 5.6.0)@bitgo/abstract-utxo—signExternalPsbt/assertExternalPsbtSighashPolicybuilt on these primitives (publishes as ≥ 13.3.0)Ticket: WCN-1994