Skip to content

Commit 048c240

Browse files
feat(abstract-utxo): extract signer keychain earlier
Do the whole decode dance before calling signTransaction Issue: BTC-1450
1 parent 1024bff commit 048c240

File tree

2 files changed

+28
-31
lines changed

2 files changed

+28
-31
lines changed

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

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@ import _ from 'lodash';
33
import { bip32, BIP32Interface, bitgo } from '@bitgo/utxo-lib';
44
import * as utxolib from '@bitgo/utxo-lib';
55
import { isTriple, Triple } from '@bitgo/sdk-core';
6-
import buildDebug from 'debug';
76

87
import { signAndVerifyPsbt, signAndVerifyWalletTransaction } from '../../sign';
98
import { AbstractUtxoCoin, DecodedTransaction, RootWalletKeys } from '../../abstractUtxoCoin';
109

11-
const debug = buildDebug('bitgo:abstract-utxo:signTransaction');
12-
1310
/**
1411
* Key Value: Unsigned tx id => PSBT
1512
* It is used to cache PSBTs with taproot key path (MuSig2) inputs during external express signer is activated.
@@ -23,11 +20,11 @@ const PSBT_CACHE = new Map<string, utxolib.bitgo.UtxoPsbt>();
2320
export async function signTransaction<TNumber extends number | bigint>(
2421
coin: AbstractUtxoCoin,
2522
tx: DecodedTransaction<TNumber>,
23+
signerKeychain: BIP32Interface | undefined,
2624
params: {
2725
walletId: string | undefined;
2826
txInfo: { unspents?: utxolib.bitgo.Unspent<TNumber>[] } | undefined;
2927
isLastSignature: boolean;
30-
prv: string | undefined;
3128
signingStep: 'signerNonce' | 'cosignerNonce' | 'signerSignature' | undefined;
3229
allowNonSegwitSigningWithoutPrevTx: boolean;
3330
pubs: string[] | undefined;
@@ -49,22 +46,6 @@ export async function signTransaction<TNumber extends number | bigint>(
4946
isLastSignature = params.isLastSignature;
5047
}
5148

52-
const getSignerKeychain = (): utxolib.BIP32Interface => {
53-
const userPrv = params.prv;
54-
if (_.isUndefined(userPrv) || !_.isString(userPrv)) {
55-
if (!_.isUndefined(userPrv)) {
56-
throw new Error(`prv must be a string, got type ${typeof userPrv}`);
57-
}
58-
throw new Error('missing prv parameter to sign transaction');
59-
}
60-
const signerKeychain = bip32.fromBase58(userPrv, utxolib.networks.bitcoin);
61-
if (signerKeychain.isNeutered()) {
62-
throw new Error('expected user private key but received public key');
63-
}
64-
debug(`Here is the public key of the xprv you used to sign: ${signerKeychain.neutered().toBase58()}`);
65-
return signerKeychain;
66-
};
67-
6849
const setSignerMusigNonceWithOverride = (
6950
psbt: utxolib.bitgo.UtxoPsbt,
7051
signerKeychain: utxolib.BIP32Interface,
@@ -73,12 +54,10 @@ export async function signTransaction<TNumber extends number | bigint>(
7354
utxolib.bitgo.withUnsafeNonSegwit(psbt, () => psbt.setAllInputsMusig2NonceHD(signerKeychain), nonSegwitOverride);
7455
};
7556

76-
let signerKeychain: utxolib.BIP32Interface | undefined;
77-
7857
if (tx instanceof bitgo.UtxoPsbt && isTxWithKeyPathSpendInput) {
7958
switch (params.signingStep) {
8059
case 'signerNonce':
81-
signerKeychain = getSignerKeychain();
60+
assert(signerKeychain);
8261
setSignerMusigNonceWithOverride(tx, signerKeychain, params.allowNonSegwitSigningWithoutPrevTx);
8362
PSBT_CACHE.set(tx.getUnsignedTx().getId(), tx);
8463
return { txHex: tx.toHex() };
@@ -99,7 +78,7 @@ export async function signTransaction<TNumber extends number | bigint>(
9978
default:
10079
// this instance is not an external signer
10180
assert(params.walletId, 'walletId is required for MuSig2 bitgo nonce');
102-
signerKeychain = getSignerKeychain();
81+
assert(signerKeychain);
10382
setSignerMusigNonceWithOverride(tx, signerKeychain, params.allowNonSegwitSigningWithoutPrevTx);
10483
const response = await coin.signPsbt(tx.toHex(), params.walletId);
10584
tx.combine(bitgo.createPsbtFromHex(response.psbt, coin.network));
@@ -117,12 +96,9 @@ export async function signTransaction<TNumber extends number | bigint>(
11796
}
11897
}
11998

120-
if (signerKeychain === undefined) {
121-
signerKeychain = getSignerKeychain();
122-
}
123-
12499
let signedTransaction: bitgo.UtxoTransaction<bigint> | bitgo.UtxoPsbt;
125100
if (tx instanceof bitgo.UtxoPsbt) {
101+
assert(signerKeychain);
126102
signedTransaction = signAndVerifyPsbt(tx, signerKeychain, {
127103
isLastSignature,
128104
allowNonSegwitSigningWithoutPrevTx: params.allowNonSegwitSigningWithoutPrevTx,
@@ -140,6 +116,7 @@ export async function signTransaction<TNumber extends number | bigint>(
140116
const cosignerPub = params.cosignerPub ?? params.pubs[2];
141117
const cosignerKeychain = bip32.fromBase58(cosignerPub);
142118

119+
assert(signerKeychain);
143120
const walletSigner = new bitgo.WalletUnspentSigner<RootWalletKeys>(keychains, signerKeychain, cosignerKeychain);
144121
signedTransaction = signAndVerifyWalletTransaction(tx, params.txInfo.unspents, walletSigner, {
145122
isLastSignature,

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
import _ from 'lodash';
2+
import { IWallet } from '@bitgo/sdk-core';
3+
import * as utxolib from '@bitgo/utxo-lib';
4+
import { bip32 } from '@bitgo/utxo-lib';
5+
import buildDebug from 'debug';
6+
27
import { AbstractUtxoCoin, SignTransactionOptions } from '../abstractUtxoCoin';
38
import { isDescriptorWallet } from '../descriptor';
49
import * as fixedScript from './fixedScript';
5-
import { IWallet } from '@bitgo/sdk-core';
10+
11+
const debug = buildDebug('bitgo:abstract-utxo:transaction:signTransaction');
12+
13+
function getSignerKeychain(userPrv: unknown): utxolib.BIP32Interface | undefined {
14+
if (userPrv === undefined) {
15+
return undefined;
16+
}
17+
if (typeof userPrv !== 'string') {
18+
throw new Error('expected user private key to be a string');
19+
}
20+
const signerKeychain = bip32.fromBase58(userPrv, utxolib.networks.bitcoin);
21+
if (signerKeychain.isNeutered()) {
22+
throw new Error('expected user private key but received public key');
23+
}
24+
debug(`Here is the public key of the xprv you used to sign: ${signerKeychain.neutered().toBase58()}`);
25+
return signerKeychain;
26+
}
627

728
export async function signTransaction<TNumber extends number | bigint>(
829
coin: AbstractUtxoCoin,
@@ -22,11 +43,10 @@ export async function signTransaction<TNumber extends number | bigint>(
2243
if (params.wallet && isDescriptorWallet(params.wallet as IWallet)) {
2344
throw new Error('Descriptor wallets are not supported');
2445
} else {
25-
return fixedScript.signTransaction(coin, tx, {
46+
return fixedScript.signTransaction(coin, tx, getSignerKeychain(params.prv), {
2647
walletId: params.txPrebuild.walletId,
2748
txInfo: params.txPrebuild.txInfo,
2849
isLastSignature: params.isLastSignature ?? false,
29-
prv: typeof params.prv === 'string' ? params.prv : undefined,
3050
signingStep: params.signingStep,
3151
allowNonSegwitSigningWithoutPrevTx: params.allowNonSegwitSigningWithoutPrevTx ?? false,
3252
pubs: params.pubs,

0 commit comments

Comments
 (0)