From 4f6474ebd4745799b366704a624e9f9b8e4f55d9 Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Thu, 23 Apr 2026 11:19:59 +0200 Subject: [PATCH 1/3] chore: bump @bitgo/wasm-utxo to 4.7.0 Update @bitgo/wasm-utxo dependency from 4.1.0 to 4.7.0 across multiple modules (abstract-utxo, utxo-bin, utxo-core, utxo-ord, utxo-staking) and update yarn.lock accordingly. Issue: BTC-0 Co-authored-by: llm-git --- modules/abstract-utxo/package.json | 2 +- modules/utxo-bin/package.json | 2 +- modules/utxo-core/package.json | 2 +- modules/utxo-ord/package.json | 2 +- modules/utxo-staking/package.json | 2 +- yarn.lock | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/abstract-utxo/package.json b/modules/abstract-utxo/package.json index 530d50b9d9..d2f32b9685 100644 --- a/modules/abstract-utxo/package.json +++ b/modules/abstract-utxo/package.json @@ -66,7 +66,7 @@ "@bitgo/utxo-core": "^1.36.0", "@bitgo/utxo-lib": "^11.22.0", "@bitgo/utxo-ord": "^1.29.0", - "@bitgo/wasm-utxo": "^4.1.0", + "@bitgo/wasm-utxo": "^4.7.0", "@types/lodash": "^4.14.121", "@types/superagent": "4.1.15", "bignumber.js": "^9.0.2", diff --git a/modules/utxo-bin/package.json b/modules/utxo-bin/package.json index 1828c51c91..d8ef5963c3 100644 --- a/modules/utxo-bin/package.json +++ b/modules/utxo-bin/package.json @@ -31,7 +31,7 @@ "@bitgo/unspents": "^0.51.3", "@bitgo/utxo-core": "^1.36.0", "@bitgo/utxo-lib": "^11.22.0", - "@bitgo/wasm-utxo": "^4.1.0", + "@bitgo/wasm-utxo": "^4.7.0", "@noble/curves": "1.8.1", "archy": "^1.0.0", "bech32": "^2.0.0", diff --git a/modules/utxo-core/package.json b/modules/utxo-core/package.json index c766d91698..bac833a3cc 100644 --- a/modules/utxo-core/package.json +++ b/modules/utxo-core/package.json @@ -81,7 +81,7 @@ "@bitgo/secp256k1": "^1.11.0", "@bitgo/unspents": "^0.51.3", "@bitgo/utxo-lib": "^11.22.0", - "@bitgo/wasm-utxo": "^4.1.0", + "@bitgo/wasm-utxo": "^4.7.0", "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", "fast-sha256": "^1.3.0" }, diff --git a/modules/utxo-ord/package.json b/modules/utxo-ord/package.json index 25f492976d..57f0e65d16 100644 --- a/modules/utxo-ord/package.json +++ b/modules/utxo-ord/package.json @@ -45,7 +45,7 @@ "directory": "modules/utxo-ord" }, "dependencies": { - "@bitgo/wasm-utxo": "^4.1.0" + "@bitgo/wasm-utxo": "^4.7.0" }, "devDependencies": { "@bitgo/utxo-lib": "^11.22.0" diff --git a/modules/utxo-staking/package.json b/modules/utxo-staking/package.json index bca5650f95..c5bc71a5f9 100644 --- a/modules/utxo-staking/package.json +++ b/modules/utxo-staking/package.json @@ -63,7 +63,7 @@ "@bitgo/babylonlabs-io-btc-staking-ts": "^3.5.0", "@bitgo/utxo-core": "^1.36.0", "@bitgo/utxo-lib": "^11.22.0", - "@bitgo/wasm-utxo": "^4.1.0", + "@bitgo/wasm-utxo": "^4.7.0", "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", "bip322-js": "^2.0.0", "bitcoinjs-lib": "^6.1.7", diff --git a/yarn.lock b/yarn.lock index a98a9c49dc..b7906a70c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1030,10 +1030,10 @@ resolved "https://registry.npmjs.org/@bitgo/wasm-ton/-/wasm-ton-1.1.1.tgz" integrity sha512-Y4x2V2ZcYWlmx42v7dlrKDtT2DuUt8smk8E98mh7RhpiifJhLk2v5RmXDwBl0A3v9TzUOU6qMOnSS/iZ8Pq52w== -"@bitgo/wasm-utxo@^4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-4.1.0.tgz" - integrity sha512-J6tKdfhJggt8LHKSh+KScz6/Q6VcX59D/1ycbUw/w+zWVOSKt+Z2+FFbYTJIVatxc4gA2bGqvHftC9dSbZBAwA== +"@bitgo/wasm-utxo@^4.7.0": + version "4.7.0" + resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-4.7.0.tgz#2ec1103c840b3be1a2ed29fae4ebc03fd57160a6" + integrity sha512-7T1vZNxM1dGPi2EqbWAFzHN0A8uWlR05c9Q7UAmZv1dQt6SBTsGc5rPyoEmwvkyPJSdbvcPS3NCoTyWIcbqUUA== "@brandonblack/musig@^0.0.1-alpha.0": version "0.0.1-alpha.1" From 64e82386af37c07a9f83fb59d7d8cb25eb798d89 Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Mon, 27 Apr 2026 11:39:56 +0200 Subject: [PATCH 2/3] fix(utxo-staking): update error message expectation for wasm-utxo 4.7.0 The error message format changed in wasm-utxo 4.7.0. Update the test to match the new error message format that includes 'Could not satisfy Tr descriptor' instead of 'CouldNotSatisfyTr'. Issue: BTC-0 --- modules/utxo-staking/test/unit/babylon/bug71.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/utxo-staking/test/unit/babylon/bug71.ts b/modules/utxo-staking/test/unit/babylon/bug71.ts index 25532396cb..9a69e509b9 100644 --- a/modules/utxo-staking/test/unit/babylon/bug71.ts +++ b/modules/utxo-staking/test/unit/babylon/bug71.ts @@ -34,7 +34,7 @@ describe('btc-staking-ts bug #71', function () { const psbt = wasmMiniscript.Psbt.deserialize(buf); assert.throws(() => { psbt.finalize(); - }, /CouldNotSatisfyTr/); + }, /Could not satisfy Tr descriptor/); }); it('cannot finalize with bitcoind', async function (this: Mocha.Context) { From d09ec7a1471f8dd271bf1b7b147323fb9b28bade Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Mon, 27 Apr 2026 10:51:07 +0200 Subject: [PATCH 3/3] feat(abstract-utxo): add chain code validation for legacy signing Add validation to ensure only supported chain codes (0, 1, 10, 11, 20, 21, 30, 31, 40, 41) are used in legacy transaction signing and verification. This prevents runtime errors from invalid chain codes. Improve type safety by casting to utxolib types only when needed and removing unnecessary WalletUnspent type imports in test utilities. Issue: BTC-0 Co-authored-by: llm-git --- .../fixedScript/signLegacyTransaction.ts | 37 ++++++++++++++++--- modules/abstract-utxo/src/unspent.ts | 7 ++++ .../test/unit/util/transaction.ts | 4 +- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts index 95fcfae2a5..304e012740 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts @@ -7,7 +7,7 @@ import { BIP32, bip32 } from '@bitgo/wasm-utxo'; import debugLib from 'debug'; import { UtxoCoinName } from '../../names'; -import type { Unspent, WalletUnspent } from '../../unspent'; +import { isWalletUnspent, type Unspent } from '../../unspent'; import { toUtxolibBIP32 } from '../../wasmUtil'; import { getReplayProtectionAddresses } from './replayProtection'; @@ -15,10 +15,12 @@ import { InputSigningError, TransactionSigningError } from './SigningError'; const debug = debugLib('bitgo:v2:utxo'); -const { isWalletUnspent, signInputWithUnspent, toOutput } = utxolib.bitgo; +const { signInputWithUnspent, toOutput } = utxolib.bitgo; type RootWalletKeys = utxolib.bitgo.RootWalletKeys; +const UTXOLIB_VALID_CHAIN_CODES = new Set([0, 1, 10, 11, 20, 21, 30, 31, 40, 41] as const); + /** * Sign all inputs of a wallet transaction and verify signatures after signing. * Collects and logs signing errors and verification errors, throws error in the end if any of them @@ -71,8 +73,21 @@ export function signAndVerifyWalletTransaction( if (!isWalletUnspent(unspent)) { return InputSigningError.expectedWalletUnspent(inputIndex, null, unspent); } + if (!UTXOLIB_VALID_CHAIN_CODES.has(unspent.chain as utxolib.bitgo.ChainCode)) { + return new InputSigningError( + inputIndex, + null, + unspent, + new Error(`Chain code ${unspent.chain} is not supported for legacy signing`) + ); + } try { - signInputWithUnspent(txBuilder, inputIndex, unspent as WalletUnspent, walletSigner); + signInputWithUnspent( + txBuilder, + inputIndex, + unspent as unknown as utxolib.bitgo.WalletUnspent, + walletSigner + ); debug('Successfully signed input %d of %d', inputIndex + 1, unspents.length); } catch (e) { return new InputSigningError(inputIndex, null, unspent, e); @@ -96,10 +111,20 @@ export function signAndVerifyWalletTransaction( if (!isWalletUnspent(unspent)) { return InputSigningError.expectedWalletUnspent(inputIndex, null, unspent); } - const walletUnspent = unspent as WalletUnspent; + if (!UTXOLIB_VALID_CHAIN_CODES.has(unspent.chain as utxolib.bitgo.ChainCode)) { + return new InputSigningError( + inputIndex, + null, + unspent, + new Error(`Chain code ${unspent.chain} is not supported for legacy verification`) + ); + } + const walletUnspent = unspent; try { - const publicKey = walletSigner.deriveForChainAndIndex(walletUnspent.chain, walletUnspent.index).signer - .publicKey; + const publicKey = walletSigner.deriveForChainAndIndex( + walletUnspent.chain as utxolib.bitgo.ChainCode, + walletUnspent.index + ).signer.publicKey; if ( !utxolib.bitgo.verifySignatureWithPublicKey(signedTransaction, inputIndex, prevOutputs, publicKey) ) { diff --git a/modules/abstract-utxo/src/unspent.ts b/modules/abstract-utxo/src/unspent.ts index 1773b336d0..4422833492 100644 --- a/modules/abstract-utxo/src/unspent.ts +++ b/modules/abstract-utxo/src/unspent.ts @@ -1,5 +1,12 @@ import { fixedScriptWallet } from '@bitgo/wasm-utxo'; +/** + * Type guard to check if an Unspent is a WalletUnspent + */ +export function isWalletUnspent(u: Unspent): u is WalletUnspent { + return 'chain' in u && 'index' in u && fixedScriptWallet.ChainCode.is((u as WalletUnspent).chain); +} + /** * Unspent transaction output (UTXO) type definition * diff --git a/modules/abstract-utxo/test/unit/util/transaction.ts b/modules/abstract-utxo/test/unit/util/transaction.ts index 95688fc688..1c8de5564d 100644 --- a/modules/abstract-utxo/test/unit/util/transaction.ts +++ b/modules/abstract-utxo/test/unit/util/transaction.ts @@ -4,7 +4,7 @@ import * as utxolib from '@bitgo/utxo-lib'; import { ECPair, fixedScriptWallet, hasPsbtMagic, address as wasmAddress } from '@bitgo/wasm-utxo'; import type { UtxoCoinName } from '../../../src/names'; -import type { Unspent, WalletUnspent } from '../../../src/unspent'; +import type { Unspent } from '../../../src/unspent'; import { getCoinNameForNetwork } from './utxoCoins'; const { isWalletUnspent, signInputWithUnspent } = utxolib.bitgo; @@ -123,7 +123,7 @@ function createTransactionBuilderWithSignedInputs { if (isWalletUnspent(u)) { - signInputWithUnspent(txBuilder, inputIndex, u as WalletUnspent, signer); + signInputWithUnspent(txBuilder, inputIndex, u, signer); } }); return txBuilder;