Skip to content

Commit 15b50c8

Browse files
OttoAllmendingerllm-git
andcommitted
feat(abstract-utxo): default to wasm-utxo on testnet
Change the default SDK backend to wasm-utxo for testnet coins, keeping utxolib for mainnet. Also enhance PSBT transaction handling to support key path spend input detection in both backends. Issue: BTC-3018 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent 945a272 commit 15b50c8

4 files changed

Lines changed: 68 additions & 16 deletions

File tree

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import {
5757
v1Sweep,
5858
V1SweepParams,
5959
} from './recovery';
60-
import { isReplayProtectionUnspent } from './transaction/fixedScript/replayProtection';
60+
import { getReplayProtectionPubkeys, isReplayProtectionUnspent } from './transaction/fixedScript/replayProtection';
6161
import { supportedCrossChainRecoveries } from './config';
6262
import {
6363
assertValidTransactionRecipient,
@@ -81,6 +81,7 @@ import {
8181
getMainnetCoinName,
8282
getNetworkFromCoinName,
8383
isTestnetCoin,
84+
isUtxoCoinNameMainnet,
8485
UtxoCoinName,
8586
UtxoCoinNameMainnet,
8687
} from './names';
@@ -143,6 +144,28 @@ type UtxoCustomSigningFunction<TNumber extends number | bigint> = {
143144

144145
const { isChainCode, scriptTypeForChain, outputScripts } = bitgo;
145146

147+
/**
148+
* Check if a decoded transaction has at least one taproot key path spend (MuSig2) input.
149+
* Works for both utxolib UtxoPsbt and wasm-utxo BitGoPsbt.
150+
*/
151+
function hasKeyPathSpendInput<TNumber extends number | bigint>(
152+
tx: DecodedTransaction<TNumber>,
153+
pubs: string[] | undefined,
154+
coinName: UtxoCoinName
155+
): boolean {
156+
if (tx instanceof bitgo.UtxoPsbt) {
157+
return bitgo.isTransactionWithKeyPathSpendInput(tx);
158+
}
159+
if (tx instanceof fixedScriptWallet.BitGoPsbt) {
160+
assert(pubs && isTriple(pubs), 'pub triple is required to check for key path spend inputs in wasm-utxo PSBT');
161+
const rootWalletKeys = fixedScriptWallet.RootWalletKeys.fromXpubs(pubs);
162+
const replayProtection = { publicKeys: getReplayProtectionPubkeys(coinName) };
163+
const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection);
164+
return parsed.inputs.some((input) => input.scriptType === 'p2trMusig2KeyPath');
165+
}
166+
return false;
167+
}
168+
146169
/**
147170
* Convert ValidationError to TxIntentMismatchRecipientError with structured data
148171
*
@@ -379,14 +402,17 @@ export abstract class AbstractUtxoCoin
379402

380403
public altScriptHash?: number;
381404
public supportAltScriptDestination?: boolean;
382-
public defaultSdkBackend: SdkBackend = 'utxolib';
383405
public readonly amountType: 'number' | 'bigint';
384406

385407
protected constructor(bitgo: BitGoBase, amountType: 'number' | 'bigint' = 'number') {
386408
super(bitgo);
387409
this.amountType = amountType;
388410
}
389411

412+
get defaultSdkBackend(): SdkBackend {
413+
return isUtxoCoinNameMainnet(this.name) ? 'utxolib' : 'wasm-utxo';
414+
}
415+
390416
/**
391417
* @deprecated - will be removed when we drop support for utxolib
392418
* Use `name` property instead.
@@ -814,7 +840,7 @@ export abstract class AbstractUtxoCoin
814840

815841
const tx = this.decodeTransaction(txHex);
816842

817-
const isTxWithKeyPathSpendInput = tx instanceof bitgo.UtxoPsbt && bitgo.isTransactionWithKeyPathSpendInput(tx);
843+
const isTxWithKeyPathSpendInput = hasKeyPathSpendInput(tx, signTransactionParams.pubs, this.name);
818844

819845
if (!isTxWithKeyPathSpendInput) {
820846
return await customSigningFunction({ ...signTransactionParams, coin: this });

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ describe('UTXO Custom Signer Function', function () {
4343
});
4444

4545
function nocks(txPrebuild: { txHex: string }) {
46+
const pubs = rootWalletKey.triple.map((k) => k.neutered().toBase58());
4647
nock(bgUrl)
4748
.post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`)
4849
.reply(200, { ...txPrebuild, txInfo: {} });
4950
nock(bgUrl).get(`/api/v2/${wallet.coin()}/public/block/latest`).reply(200, { height: 1000 });
50-
nock(bgUrl).persist().get(`/api/v2/${wallet.coin()}/key/${wallet.keyIds()[0]}`).reply(200, { pub: 'pub' });
51-
nock(bgUrl).persist().get(`/api/v2/${wallet.coin()}/key/${wallet.keyIds()[1]}`).reply(200, { pub: 'pub' });
52-
nock(bgUrl).persist().get(`/api/v2/${wallet.coin()}/key/${wallet.keyIds()[2]}`).reply(200, { pub: 'pub' });
51+
nock(bgUrl).persist().get(`/api/v2/${wallet.coin()}/key/${wallet.keyIds()[0]}`).reply(200, { pub: pubs[0] });
52+
nock(bgUrl).persist().get(`/api/v2/${wallet.coin()}/key/${wallet.keyIds()[1]}`).reply(200, { pub: pubs[1] });
53+
nock(bgUrl).persist().get(`/api/v2/${wallet.coin()}/key/${wallet.keyIds()[2]}`).reply(200, { pub: pubs[2] });
5354
return nock(bgUrl).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/send`).reply(200, { ok: true });
5455
}
5556

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,46 @@
11
import assert from 'assert';
22

3-
import { Triple } from '@bitgo/sdk-core';
3+
import { common, Triple, Wallet } from '@bitgo/sdk-core';
4+
import nock = require('nock');
45

56
import { bip322Fixtures } from './fixtures/bip322/fixtures';
67
import { psbtTxHex } from './fixtures/psbtHexProof';
7-
import { getUtxoCoin } from './util';
8+
import { defaultBitGo, getUtxoCoin } from './util';
9+
10+
nock.disableNetConnect();
11+
12+
const bgUrl = common.Environments[defaultBitGo.getEnv()].uri;
813

914
describe('Explain Transaction', function () {
15+
afterEach(function () {
16+
nock.cleanAll();
17+
});
18+
1019
describe('Verify paygo output when explaining psbt transaction', function () {
1120
const coin = getUtxoCoin('tbtc4');
1221

22+
const pubs: Triple<string> = [
23+
'xpub661MyMwAqRbcFaKvNBFdV6HY7ibXxFSbL7rDjY1cVM8s3pGPTNKfjTu8SmatNZ7AcZQehSqcEnC7vezMoprQvhqQUszLhuY4G8ruv6PGEr7',
24+
'xpub661MyMwAqRbcGkAVVQVHrEYQA4hfbDW9Rpn35b6sXA9TSBd5Qzjwz7F6Weje57kBVeVfimfJjXutwUDBSMz5yRwsWik9gNyxrdvSaJbjgi6',
25+
'xpub661MyMwAqRbcGCCL3GYNbvKs1t5k5yeKZcV5smto9T5Z17zkcgRF4X9uzDfPxMHHedwF4JcJ6kpg8M2NWHEFC5LMSv1t3nMMm1GC9PcVmq5',
26+
];
27+
28+
const keyIds = ['key-user', 'key-backup', 'key-bitgo'];
29+
30+
function nockKeyFetch(): void {
31+
keyIds.forEach((id, i) => {
32+
nock(bgUrl).get(`/api/v2/${coin.getChain()}/key/${id}`).reply(200, { pub: pubs[i] });
33+
});
34+
}
35+
1336
it('should detect and verify paygo address proof in PSBT', async function () {
14-
// Call explainTransaction
15-
await coin.explainTransaction(psbtTxHex);
37+
nockKeyFetch();
38+
const wallet = new Wallet(defaultBitGo, coin, {
39+
id: 'mock-wallet-id',
40+
coin: coin.getChain(),
41+
keys: keyIds,
42+
});
43+
await coin.explainTransaction(psbtTxHex, wallet);
1644
});
1745
});
1846

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ describe('manage unspents', function () {
5252
);
5353

5454
nocks.push(
55-
...psbts.map((psbt) =>
55+
...psbts.map(() =>
5656
nock(bgUrl)
5757
.post(
5858
`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/send`,
59-
_.matches({ txHex: psbt.signAllInputsHD(rootWalletKey.user).toHex() })
59+
_.matches({ type: 'consolidate', bulk: true })
6060
)
6161
.reply(200)
6262
)
@@ -92,10 +92,7 @@ describe('manage unspents', function () {
9292

9393
nocks.push(
9494
nock(bgUrl)
95-
.post(
96-
`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/send`,
97-
_.matches({ txHex: psbt.signAllInputsHD(rootWalletKey.user).toHex() })
98-
)
95+
.post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/send`, _.matches({ type: 'consolidate' }))
9996
.reply(200)
10097
);
10198

0 commit comments

Comments
 (0)