@@ -3,7 +3,7 @@ import assert from 'assert';
33import { BIP32Interface } from '@bitgo/utxo-lib' ;
44import { BIP32 , ECPair , fixedScriptWallet } from '@bitgo/wasm-utxo' ;
55
6- import { InputSigningError , TransactionSigningError } from './SigningError' ;
6+ import { BulkSigningError , InputSigningError , TransactionSigningError } from './SigningError' ;
77import { Musig2Participant } from './musig2' ;
88
99export type ReplayProtectionKeys = {
@@ -29,6 +29,7 @@ function hasKeyPathSpendInput(
2929
3030/**
3131 * Sign all inputs of a PSBT and verify signatures after signing.
32+ * Uses bulk signing for performance (signs all matching inputs in one pass).
3233 * Collects and logs signing errors and verification errors, throws error in the end if any of them failed.
3334 */
3435export function signAndVerifyPsbtWasm (
@@ -38,32 +39,24 @@ export function signAndVerifyPsbtWasm(
3839 replayProtection : ReplayProtectionKeys
3940) : fixedScriptWallet . BitGoPsbt {
4041 const wasmSigner = toWasmBIP32 ( signerKeychain ) ;
41- const parsed = tx . parseTransactionWithWalletKeys ( rootWalletKeys , replayProtection ) ;
4242
43- const signErrors : InputSigningError < bigint > [ ] = [ ] ;
43+ // Bulk sign all wallet inputs (ECDSA + MuSig2) - much faster than per-input signing
44+ try {
45+ tx . sign ( wasmSigner ) ;
46+ } catch ( e ) {
47+ throw new BulkSigningError ( e ) ;
48+ }
49+
50+ // Verify signatures for all signed inputs (still per-input for granular error reporting)
51+ const parsed = tx . parseTransactionWithWalletKeys ( rootWalletKeys , replayProtection ) ;
4452 const verifyErrors : InputSigningError < bigint > [ ] = [ ] ;
4553
46- // Sign all inputs (skipping replay protection inputs)
4754 parsed . inputs . forEach ( ( input , inputIndex ) => {
4855 if ( input . scriptType === 'p2shP2pk' ) {
4956 // Skip replay protection inputs - they are platform signed only
5057 return ;
5158 }
5259
53- const outputId = `${ input . previousOutput . txid } :${ input . previousOutput . vout } ` ;
54- try {
55- tx . sign ( inputIndex , wasmSigner ) ;
56- } catch ( e ) {
57- signErrors . push ( new InputSigningError < bigint > ( inputIndex , input . scriptType , { id : outputId } , e ) ) ;
58- }
59- } ) ;
60-
61- // Verify signatures for all signed inputs
62- parsed . inputs . forEach ( ( input , inputIndex ) => {
63- if ( input . scriptType === 'p2shP2pk' ) {
64- return ;
65- }
66-
6760 const outputId = `${ input . previousOutput . txid } :${ input . previousOutput . vout } ` ;
6861 try {
6962 if ( ! tx . verifySignature ( inputIndex , wasmSigner ) ) {
@@ -76,8 +69,8 @@ export function signAndVerifyPsbtWasm(
7669 }
7770 } ) ;
7871
79- if ( signErrors . length || verifyErrors . length ) {
80- throw new TransactionSigningError ( signErrors , verifyErrors ) ;
72+ if ( verifyErrors . length ) {
73+ throw new TransactionSigningError ( [ ] , verifyErrors ) ;
8174 }
8275
8376 return tx ;
0 commit comments