refactor(abstract-utxo): decode descriptor PSBTs directly to wasm Psbt#8824
Merged
Conversation
260ca71 to
de1cffc
Compare
Descriptor flows were going through a wasteful byte round-trip: prebuild bytes -> fixedScriptWallet.BitGoPsbt -> bytes -> descriptorWallet.Psbt decodeTransactionFromPrebuild + decodeWith='wasm-utxo' returns the fixed-script BitGoPsbt, which descriptor signing/parsing/verifying/ explaining then had to convert via toWasmPsbt before reaching the descriptor APIs. Beyond the wasted work, it produced T1-3400 — a guard that only accepted utxo-lib PSBTs rejected the BitGoPsbt the default backend produced. Add a decodeDescriptorPsbt helper (transaction/decode.ts) that parses prebuild bytes straight into the wasm-utxo descriptor Psbt. Use it in all four descriptor sites: - signTransaction.ts descriptor branch - descriptor/parse.ts - descriptor/verifyTransaction.ts - abstractUtxoCoin.explainTransaction (+ explainTx descriptor branch) This makes the BitGoPsbt path unreachable for descriptor wallets, so the T1-3400 'descriptor wallets require PSBT format transactions' guard relaxation is no longer needed. Closes: T1-3400
No test in the monorepo exercised the top-level signTransaction against a descriptor wallet, leaving the decode-and-route logic in the SDK entry point unprotected. T1-3400 lived in that gap for the full lifetime of the wasm-utxo backend default. Add an end-to-end test that drives a real PSBT through coin.signTransaction with decodeWith='wasm-utxo', mocks the keychain fetch via nock, and asserts the returned txHex deserializes as a signed PSBT with valid user-key signatures on each input. Refs: T1-3401
de1cffc to
6766023
Compare
davidkaplanbitgo
approved these changes
May 21, 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.
Summary
Fixes T1-3400 — descriptor wallet signing failed with
descriptor wallets require PSBT format transactionseven when handed a valid PSBT. The underlying issue: descriptor flows were doing a wasteful byte round-trip through the wrong PSBT class.decodeTransactionFromPrebuildwithdecodeWith='wasm-utxo'(the default for all coins after 1702a08) returns the fixed-scriptBitGoPsbt. Descriptor signing/parsing/verifying/explaining then had totoWasmPsbt(tx)back to the descriptorPsbtbefore calling the descriptor APIs. Beyond the wasted work, anisUtxoLibPsbt-only guard on the descriptor sign branch silently broke every descriptor signing flow oncewasm-utxobecame the default backend — customers on CoreDAO BTC staking hit this in production (request439fbc81-433e-417f-8c69-b9c005a8df79).Approach
Replace the guard relaxation that was originally proposed with a deeper fix: decode prebuild bytes straight into the wasm-utxo descriptor
Psbt, skippingBitGoPsbtentirely.New helper
decodeDescriptorPsbt:Used in all four descriptor call sites:
signTransaction.tsdescriptor branchdecodeTransactionFromPrebuild→BitGoPsbt→ guard →toWasmPsbt→PsbtdecodeDescriptorPsbt(prebuild)→Psbtdescriptor/parse.tsdescriptor/verifyTransaction.tsabstractUtxoCoin.explainTransaction+explainTxtoWasmPsbt(tx)As a result,
BitGoPsbtis never produced for descriptor flows, andtoWasmPsbt's job is back to handling fixed-script callers only.Commits
test(abstract-utxo): reproduce BitGoPsbt rejection in descriptor sign— failing regression test, asserts the T1-3400 guard message is not thrown.refactor(abstract-utxo): decode descriptor PSBTs directly to wasm Psbt— the actual fix.test(abstract-utxo): add E2E descriptor signTransaction test(T1-3401) — drivescoin.signTransactionend-to-end for a descriptor wallet vianockWalletKeys, asserts the returnedtxHexdeserializes as a PSBT with valid signatures on each input. This is the test that would have caught T1-3400 — the SDK had no top-levelsignTransactioncoverage against any descriptor wallet before this.The regression test (commit 1) verifies the bug; the refactor commit (commit 2) makes it pass; the E2E test (commit 3) locks in the full signing pipeline.
Test plan
npx tsc --noEmitcleanyarn lintcleanyarn unit-test— 1926 passing, 0 failingsignTransactionGuard.tsandsignTransactionE2E.tsagainst the pre-refactor source — both fail withError: descriptor wallets require PSBT format transactions at signTransaction.ts:63:13, exactly the customer-facing error.Linear