From a6f9c5c53fe87d36c231fbdc2f9f09b4e0549ca4 Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Thu, 29 Jan 2026 13:56:03 +0100 Subject: [PATCH 1/2] feat(abstract-utxo): update wasm-utxo dependency to 1.29.0 Update wasm-utxo dependency across multiple modules from 1.27.0 to 1.29.0. Issue: BTC-2980 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 ba4c579db4..76a41ce940 100644 --- a/modules/abstract-utxo/package.json +++ b/modules/abstract-utxo/package.json @@ -68,7 +68,7 @@ "@bitgo/utxo-core": "^1.31.0", "@bitgo/utxo-lib": "^11.19.1", "@bitgo/utxo-ord": "^1.24.1", - "@bitgo/wasm-utxo": "^1.27.0", + "@bitgo/wasm-utxo": "^1.29.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 ca6c58d88a..8b1dd677bd 100644 --- a/modules/utxo-bin/package.json +++ b/modules/utxo-bin/package.json @@ -31,7 +31,7 @@ "@bitgo/unspents": "^0.50.14", "@bitgo/utxo-core": "^1.31.0", "@bitgo/utxo-lib": "^11.19.1", - "@bitgo/wasm-utxo": "^1.27.0", + "@bitgo/wasm-utxo": "^1.29.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 9a431f2c3f..03a7c41a7c 100644 --- a/modules/utxo-core/package.json +++ b/modules/utxo-core/package.json @@ -81,7 +81,7 @@ "@bitgo/secp256k1": "^1.9.0", "@bitgo/unspents": "^0.50.14", "@bitgo/utxo-lib": "^11.19.1", - "@bitgo/wasm-utxo": "^1.27.0", + "@bitgo/wasm-utxo": "^1.29.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 3ef7faf078..ce9912f963 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": "^1.27.0" + "@bitgo/wasm-utxo": "^1.29.0" }, "devDependencies": { "@bitgo/utxo-lib": "^11.19.1" diff --git a/modules/utxo-staking/package.json b/modules/utxo-staking/package.json index 5644ac62b1..21cb2549a3 100644 --- a/modules/utxo-staking/package.json +++ b/modules/utxo-staking/package.json @@ -63,7 +63,7 @@ "@bitgo/babylonlabs-io-btc-staking-ts": "^3.3.0", "@bitgo/utxo-core": "^1.31.0", "@bitgo/utxo-lib": "^11.19.1", - "@bitgo/wasm-utxo": "^1.27.0", + "@bitgo/wasm-utxo": "^1.29.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 07195de0fe..88afc9b143 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,10 +996,10 @@ monocle-ts "^2.3.13" newtype-ts "^0.3.5" -"@bitgo/wasm-utxo@^1.27.0": - version "1.27.0" - resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-1.27.0.tgz#c8ebe108ce8b55d3df70cd3968211a6ef3001bef" - integrity sha512-gX0YemHbSBOKQ/nKaBsZKI3cJzgkrLXFWuMsPJ7t2oDzE3ggfgVF3ulGFsuaQ8WQT4rOsZ7FZz2f+C9mStXAeA== +"@bitgo/wasm-utxo@^1.29.0": + version "1.29.0" + resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-1.29.0.tgz#75be3668bd972a3ff9aae07aed84916bfceb870d" + integrity sha512-eWYM7/me8bg+oqw6lEVMdmR1eUVuGzOoyKgSm1QAZUe41qR8IjMyyWjtWI1s5XExfAOXnZuSss/8QJEPOjXhzg== "@brandonblack/musig@^0.0.1-alpha.0": version "0.0.1-alpha.1" From b3626a71a17915836b50f8748798ec12f563069f Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Thu, 29 Jan 2026 15:19:58 +0100 Subject: [PATCH 2/2] feat(abstract-utxo): optimize tx signing with bulk approach Use bulk signing for all inputs in one operation instead of signing each input individually. Added BulkSigningError class to provide better error reporting when the bulk signing operation fails. Issue: BTC-2980 Co-authored-by: llm-git --- .../transaction/fixedScript/SigningError.ts | 7 ++++ .../transaction/fixedScript/signPsbtWasm.ts | 33 ++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/abstract-utxo/src/transaction/fixedScript/SigningError.ts b/modules/abstract-utxo/src/transaction/fixedScript/SigningError.ts index 9d1826db0f..b137a1f446 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/SigningError.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/SigningError.ts @@ -28,6 +28,13 @@ export class InputSigningError extends } } +export class BulkSigningError extends Error { + constructor(public reason: Error | string) { + const reasonMessage = reason instanceof Error ? reason.message : reason; + super(`bulk signing error: ${reasonMessage}`); + } +} + export class TransactionSigningError extends Error { constructor(signErrors: InputSigningError[], verifyError: InputSigningError[]) { super( diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts b/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts index 8e391b6877..24b8c989fa 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts @@ -3,7 +3,7 @@ import assert from 'assert'; import { BIP32Interface } from '@bitgo/utxo-lib'; import { BIP32, ECPair, fixedScriptWallet } from '@bitgo/wasm-utxo'; -import { InputSigningError, TransactionSigningError } from './SigningError'; +import { BulkSigningError, InputSigningError, TransactionSigningError } from './SigningError'; import { Musig2Participant } from './musig2'; export type ReplayProtectionKeys = { @@ -29,6 +29,7 @@ function hasKeyPathSpendInput( /** * Sign all inputs of a PSBT and verify signatures after signing. + * Uses bulk signing for performance (signs all matching inputs in one pass). * Collects and logs signing errors and verification errors, throws error in the end if any of them failed. */ export function signAndVerifyPsbtWasm( @@ -38,32 +39,24 @@ export function signAndVerifyPsbtWasm( replayProtection: ReplayProtectionKeys ): fixedScriptWallet.BitGoPsbt { const wasmSigner = toWasmBIP32(signerKeychain); - const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection); - const signErrors: InputSigningError[] = []; + // Bulk sign all wallet inputs (ECDSA + MuSig2) - much faster than per-input signing + try { + tx.sign(wasmSigner); + } catch (e) { + throw new BulkSigningError(e); + } + + // Verify signatures for all signed inputs (still per-input for granular error reporting) + const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection); const verifyErrors: InputSigningError[] = []; - // Sign all inputs (skipping replay protection inputs) parsed.inputs.forEach((input, inputIndex) => { if (input.scriptType === 'p2shP2pk') { // Skip replay protection inputs - they are platform signed only return; } - const outputId = `${input.previousOutput.txid}:${input.previousOutput.vout}`; - try { - tx.sign(inputIndex, wasmSigner); - } catch (e) { - signErrors.push(new InputSigningError(inputIndex, input.scriptType, { id: outputId }, e)); - } - }); - - // Verify signatures for all signed inputs - parsed.inputs.forEach((input, inputIndex) => { - if (input.scriptType === 'p2shP2pk') { - return; - } - const outputId = `${input.previousOutput.txid}:${input.previousOutput.vout}`; try { if (!tx.verifySignature(inputIndex, wasmSigner)) { @@ -76,8 +69,8 @@ export function signAndVerifyPsbtWasm( } }); - if (signErrors.length || verifyErrors.length) { - throw new TransactionSigningError(signErrors, verifyErrors); + if (verifyErrors.length) { + throw new TransactionSigningError([], verifyErrors); } return tx;