Skip to content

Commit c941b08

Browse files
OttoAllmendingerllm-git
andcommitted
feat(abstract-utxo): enable signing psbt with wasm implementation
Update signTransaction to support wasm PSBT signing. Return Buffer instead of Uint8Array for extracted transactions. Fix type signatures and parameter structure in signPsbtWithMusig2ParticipantWasm to match other implementations. Issue: BTC-2806 Co-authored-by: llm-git <[email protected]>
1 parent 19cd235 commit c941b08

File tree

4 files changed

+47
-19
lines changed

4 files changed

+47
-19
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const PSBT_CACHE_WASM = new Map<string, fixedScriptWallet.BitGoPsbt>();
2020

2121
function hasKeyPathSpendInput(
2222
tx: fixedScriptWallet.BitGoPsbt,
23-
rootWalletKeys: fixedScriptWallet.IWalletKeys,
23+
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
2424
replayProtection: ReplayProtectionKeys
2525
): boolean {
2626
const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection);
@@ -36,10 +36,10 @@ function hasKeyPathSpendInput(
3636
export function signAndVerifyPsbtWasm(
3737
tx: fixedScriptWallet.BitGoPsbt,
3838
signerKeychain: BIP32Interface,
39-
rootWalletKeys: fixedScriptWallet.IWalletKeys,
39+
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
4040
replayProtection: ReplayProtectionKeys,
4141
{ isLastSignature }: { isLastSignature: boolean }
42-
): fixedScriptWallet.BitGoPsbt | Uint8Array {
42+
): fixedScriptWallet.BitGoPsbt | Buffer {
4343
const wasmSigner = toWasmBIP32(signerKeychain);
4444
const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection);
4545

@@ -85,7 +85,7 @@ export function signAndVerifyPsbtWasm(
8585

8686
if (isLastSignature) {
8787
tx.finalizeAllInputs();
88-
return tx.extractTransaction();
88+
return Buffer.from(tx.extractTransaction());
8989
}
9090

9191
return tx;
@@ -100,17 +100,17 @@ export async function signPsbtWithMusig2ParticipantWasm(
100100
coin: Musig2Participant<fixedScriptWallet.BitGoPsbt>,
101101
tx: fixedScriptWallet.BitGoPsbt,
102102
signerKeychain: BIP32Interface | undefined,
103-
rootWalletKeys: fixedScriptWallet.IWalletKeys,
104-
replayProtection: ReplayProtectionKeys,
103+
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
105104
params: {
105+
replayProtection: ReplayProtectionKeys;
106106
isLastSignature: boolean;
107107
signingStep: 'signerNonce' | 'cosignerNonce' | 'signerSignature' | undefined;
108108
walletId: string | undefined;
109109
}
110-
): Promise<fixedScriptWallet.BitGoPsbt | Uint8Array> {
110+
): Promise<fixedScriptWallet.BitGoPsbt | Buffer> {
111111
const wasmSigner = signerKeychain ? toWasmBIP32(signerKeychain) : undefined;
112112

113-
if (hasKeyPathSpendInput(tx, rootWalletKeys, replayProtection)) {
113+
if (hasKeyPathSpendInput(tx, rootWalletKeys, params.replayProtection)) {
114114
// We can only be the first signature on a transaction with taproot key path spend inputs because
115115
// we require the secret nonce in the cache of the first signer, which is impossible to retrieve if
116116
// deserialized from a hex.
@@ -162,7 +162,7 @@ export async function signPsbtWithMusig2ParticipantWasm(
162162
}
163163

164164
assert(signerKeychain);
165-
return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, replayProtection, {
165+
return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, params.replayProtection, {
166166
isLastSignature: params.isLastSignature,
167167
});
168168
}

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

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1+
import assert from 'assert';
2+
3+
import { isTriple } from '@bitgo/sdk-core';
14
import _ from 'lodash';
25
import { BIP32Interface } from '@bitgo/secp256k1';
36
import { bitgo } from '@bitgo/utxo-lib';
47
import * as utxolib from '@bitgo/utxo-lib';
5-
6-
import { DecodedTransaction } from '../types';
8+
import { fixedScriptWallet } from '@bitgo/wasm-utxo';
79

810
import { Musig2Participant } from './musig2';
911
import { signLegacyTransaction } from './signLegacyTransaction';
1012
import { signPsbtWithMusig2Participant } from './signPsbt';
13+
import { signPsbtWithMusig2ParticipantWasm } from './signPsbtWasm';
14+
import { getReplayProtectionPubkeys } from './replayProtection';
1115

12-
export async function signTransaction(
13-
coin: Musig2Participant<utxolib.bitgo.UtxoPsbt>,
14-
tx: DecodedTransaction<bigint | number>,
16+
export async function signTransaction<
17+
T extends utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint | number> | fixedScriptWallet.BitGoPsbt
18+
>(
19+
coin: Musig2Participant<utxolib.bitgo.UtxoPsbt> | Musig2Participant<fixedScriptWallet.BitGoPsbt>,
20+
tx: T,
1521
signerKeychain: BIP32Interface | undefined,
1622
network: utxolib.Network,
1723
params: {
@@ -24,19 +30,39 @@ export async function signTransaction(
2430
pubs: string[] | undefined;
2531
cosignerPub: string | undefined;
2632
}
27-
): Promise<utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint | number>> {
33+
): Promise<
34+
utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint | number> | fixedScriptWallet.BitGoPsbt | Buffer
35+
> {
2836
let isLastSignature = false;
2937
if (_.isBoolean(params.isLastSignature)) {
3038
// if build is called instead of buildIncomplete, no signature placeholders are left in the sig script
3139
isLastSignature = params.isLastSignature;
3240
}
3341

3442
if (tx instanceof bitgo.UtxoPsbt) {
35-
return signPsbtWithMusig2Participant(coin, tx, signerKeychain, {
43+
return signPsbtWithMusig2Participant(coin as Musig2Participant<utxolib.bitgo.UtxoPsbt>, tx, signerKeychain, {
3644
isLastSignature,
3745
signingStep: params.signingStep,
3846
walletId: params.walletId,
3947
});
48+
} else if (tx instanceof fixedScriptWallet.BitGoPsbt) {
49+
assert(params.pubs, 'pubs are required for fixed script signing');
50+
assert(isTriple(params.pubs), 'pubs must be a triple');
51+
const rootWalletKeys = fixedScriptWallet.RootWalletKeys.fromXpubs(params.pubs);
52+
return signPsbtWithMusig2ParticipantWasm(
53+
coin as Musig2Participant<fixedScriptWallet.BitGoPsbt>,
54+
tx,
55+
signerKeychain,
56+
rootWalletKeys,
57+
{
58+
replayProtection: {
59+
publicKeys: getReplayProtectionPubkeys(network),
60+
},
61+
isLastSignature,
62+
signingStep: params.signingStep,
63+
walletId: params.walletId,
64+
}
65+
);
4066
}
4167

4268
return signLegacyTransaction(tx, signerKeychain, {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { fetchKeychains, toBip32Triple } from '../keychains';
1010

1111
import * as fixedScript from './fixedScript';
1212
import * as descriptor from './descriptor';
13+
import { encodeTransaction } from './decode';
1314

1415
const debug = buildDebug('bitgo:abstract-utxo:transaction:signTransaction');
1516

@@ -72,6 +73,7 @@ export async function signTransaction<TNumber extends number | bigint>(
7273
pubs: params.pubs,
7374
cosignerPub: params.cosignerPub,
7475
});
75-
return { txHex: signedTx.toBuffer().toString('hex') };
76+
const buffer = Buffer.isBuffer(signedTx) ? signedTx : encodeTransaction(signedTx);
77+
return { txHex: buffer.toString('hex') };
7678
}
7779
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ function describeSignPsbtWithMusig2Participant(
130130
getMockCoinWasm(acidTest.rootWalletKeys, acidTest.network),
131131
psbt,
132132
acidTest.rootWalletKeys.user,
133-
wasmWalletKeys,
134-
replayProtection,
133+
fixedScriptWallet.RootWalletKeys.from(acidTest.rootWalletKeys),
135134
{
135+
replayProtection,
136136
isLastSignature: false,
137137
signingStep: undefined,
138138
walletId: 'test-wallet-id',

0 commit comments

Comments
 (0)