Skip to content

Commit b2eed2b

Browse files
OttoAllmendingerllm-git
andcommitted
feat(abstract-utxo): add extractTransaction option for signing
Add `extractTransaction` option to control whether finalized PSBTs are extracted to legacy transaction format. When false, returns the finalized PSBT, preserving derivation info useful for testing. Default behavior (true) maintains backward compatibility by extracting to legacy format. Issue: BTC-2768 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent 6e0d202 commit b2eed2b

4 files changed

Lines changed: 28 additions & 7 deletions

File tree

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ type UtxoBaseSignTransactionOptions<TNumber extends number | bigint = number> =
338338
*/
339339
returnLegacyFormat?: boolean;
340340
wallet?: UtxoWallet;
341+
/**
342+
* When true (default), extract finalized PSBT to legacy transaction format.
343+
* When false, return finalized PSBT. Useful for testing to keep transactions in PSBT format.
344+
*/
345+
extractTransaction?: boolean;
341346
};
342347

343348
export type SignTransactionOptions<TNumber extends number | bigint = number> = UtxoBaseSignTransactionOptions<TNumber> &

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export async function signTransaction<
6767
allowNonSegwitSigningWithoutPrevTx: boolean;
6868
pubs: string[] | undefined;
6969
cosignerPub: string | undefined;
70+
/** When true (default), extract finalized PSBT to legacy transaction format. When false, return finalized PSBT. */
71+
extractTransaction?: boolean;
7072
}
7173
): Promise<
7274
utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint | number> | fixedScriptWallet.BitGoPsbt | Buffer
@@ -77,6 +79,8 @@ export async function signTransaction<
7779
isLastSignature = params.isLastSignature;
7880
}
7981

82+
const { extractTransaction = true } = params;
83+
8084
if (tx instanceof bitgo.UtxoPsbt) {
8185
const signedPsbt = await signPsbtWithMusig2ParticipantUtxolib(
8286
coin as Musig2Participant<utxolib.bitgo.UtxoPsbt>,
@@ -88,8 +92,12 @@ export async function signTransaction<
8892
}
8993
);
9094
if (isLastSignature) {
91-
signedPsbt.finalizeAllInputs();
92-
return signedPsbt.extractTransaction();
95+
if (extractTransaction) {
96+
signedPsbt.finalizeAllInputs();
97+
return signedPsbt.extractTransaction();
98+
}
99+
// Return signed PSBT without finalizing to preserve derivation info
100+
return signedPsbt;
93101
}
94102
return signedPsbt;
95103
} else if (tx instanceof fixedScriptWallet.BitGoPsbt) {
@@ -110,8 +118,12 @@ export async function signTransaction<
110118
}
111119
);
112120
if (isLastSignature) {
113-
signedPsbt.finalizeAllInputs();
114-
return Buffer.from(signedPsbt.extractTransaction().toBytes());
121+
if (extractTransaction) {
122+
signedPsbt.finalizeAllInputs();
123+
return Buffer.from(signedPsbt.extractTransaction().toBytes());
124+
}
125+
// Return finalized PSBT without extracting to legacy format
126+
return signedPsbt;
115127
}
116128
return signedPsbt;
117129
}

modules/abstract-utxo/src/transaction/signTransaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export async function signTransaction<TNumber extends number | bigint>(
7878
allowNonSegwitSigningWithoutPrevTx: params.allowNonSegwitSigningWithoutPrevTx ?? false,
7979
pubs: params.pubs,
8080
cosignerPub: params.cosignerPub,
81+
extractTransaction: params.extractTransaction,
8182
});
8283

8384
// Convert half-signed PSBT to legacy format when the caller explicitly requested txFormat: 'legacy'

modules/abstract-utxo/test/unit/transaction.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as _ from 'lodash';
55
import * as utxolib from '@bitgo/utxo-lib';
66
import nock = require('nock');
77
import { BIP32Interface, bitgo, testutil } from '@bitgo/utxo-lib';
8-
import { address as wasmAddress } from '@bitgo/wasm-utxo';
8+
import { address as wasmAddress, fixedScriptWallet } from '@bitgo/wasm-utxo';
99
import {
1010
common,
1111
FullySignedTransaction,
@@ -108,6 +108,7 @@ function run<TNumber extends number | bigint = number>(
108108
prv: signer.toBase58(),
109109
pubs: walletKeys.triple.map((k) => k.neutered().toBase58()),
110110
cosignerPub: cosigner.neutered().toBase58(),
111+
extractTransaction: false,
111112
} as WalletSignTransactionOptions;
112113
}
113114

@@ -223,7 +224,7 @@ function run<TNumber extends number | bigint = number>(
223224

224225
function toTransactionStagesObj(stages: TransactionStages): TransactionObjStages {
225226
return _.mapValues(stages, (v) =>
226-
v === undefined || v instanceof utxolib.bitgo.UtxoPsbt
227+
v === undefined || v instanceof utxolib.bitgo.UtxoPsbt || v instanceof fixedScriptWallet.BitGoPsbt
227228
? undefined
228229
: v instanceof utxolib.bitgo.UtxoTransaction
229230
? transactionToObj<TNumber>(v)
@@ -266,7 +267,7 @@ function run<TNumber extends number | bigint = number>(
266267
signedBy: BIP32Interface[],
267268
sign: 'halfsigned' | 'fullsigned'
268269
) {
269-
if (txFormat === 'psbt' && sign === 'halfsigned') {
270+
if (txFormat === 'psbt') {
270271
testPsbtValidSignatures(tx, signedBy);
271272
return;
272273
}
@@ -398,6 +399,8 @@ function run<TNumber extends number | bigint = number>(
398399
const txHex =
399400
stageTx instanceof utxolib.bitgo.UtxoPsbt || stageTx instanceof utxolib.bitgo.UtxoTransaction
400401
? stageTx.toBuffer().toString('hex')
402+
: stageTx instanceof fixedScriptWallet.BitGoPsbt
403+
? Buffer.from(stageTx.serialize()).toString('hex')
401404
: stageTx.txHex;
402405

403406
const pubs = walletKeys.triple.map((k) => k.neutered().toBase58()) as Triple<string>;

0 commit comments

Comments
 (0)