Skip to content

Commit b3626a7

Browse files
OttoAllmendingerllm-git
andcommitted
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 <llm-git@ttll.de>
1 parent a6f9c5c commit b3626a7

2 files changed

Lines changed: 20 additions & 20 deletions

File tree

modules/abstract-utxo/src/transaction/fixedScript/SigningError.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ export class InputSigningError<TNumber extends number | bigint = number> extends
2828
}
2929
}
3030

31+
export class BulkSigningError extends Error {
32+
constructor(public reason: Error | string) {
33+
const reasonMessage = reason instanceof Error ? reason.message : reason;
34+
super(`bulk signing error: ${reasonMessage}`);
35+
}
36+
}
37+
3138
export class TransactionSigningError<TNumber extends number | bigint = number> extends Error {
3239
constructor(signErrors: InputSigningError<TNumber>[], verifyError: InputSigningError<TNumber>[]) {
3340
super(

modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import assert from 'assert';
33
import { BIP32Interface } from '@bitgo/utxo-lib';
44
import { BIP32, ECPair, fixedScriptWallet } from '@bitgo/wasm-utxo';
55

6-
import { InputSigningError, TransactionSigningError } from './SigningError';
6+
import { BulkSigningError, InputSigningError, TransactionSigningError } from './SigningError';
77
import { Musig2Participant } from './musig2';
88

99
export 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
*/
3435
export 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

Comments
 (0)