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/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; 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"