Skip to content

Commit 6766023

Browse files
test(abstract-utxo): add E2E descriptor signTransaction test
No test in the monorepo exercised the top-level signTransaction against a descriptor wallet, leaving the decode-and-route logic in the SDK entry point unprotected. T1-3400 lived in that gap for the full lifetime of the wasm-utxo backend default. Add an end-to-end test that drives a real PSBT through coin.signTransaction with decodeWith='wasm-utxo', mocks the keychain fetch via nock, and asserts the returned txHex deserializes as a signed PSBT with valid user-key signatures on each input. Refs: T1-3401
1 parent 43218b9 commit 6766023

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'mocha';
2+
import assert from 'assert';
3+
4+
import { Psbt } from '@bitgo/wasm-utxo';
5+
import * as testutils from '@bitgo/wasm-utxo/testutils';
6+
7+
import type { UtxoWallet } from '../../../../src/wallet';
8+
import { getUtxoCoin } from '../../util/utxoCoins';
9+
import { nockBitGo } from '../../util/nockBitGo';
10+
11+
const { getDescriptorMap, mockPsbtDefaultWithDescriptorTemplate } = testutils.descriptor;
12+
const { getKeyTriple } = testutils;
13+
14+
// End-to-end coverage for descriptor wallet signing through the
15+
// top-level coin.signTransaction entry point (T1-3401). Locks in the
16+
// wasm-utxo decode path that T1-3400 broke.
17+
describe('signTransaction E2E: descriptor wallet (wasm-utxo backend)', function () {
18+
it('produces a signed PSBT with valid user signatures on every input', async function () {
19+
const coin = getUtxoCoin('btc');
20+
// mockPsbtDefaultWithDescriptorTemplate uses getDefaultXPubs('a') —
21+
// i.e. getKeyTriple('a') — so we sign with the same triple.
22+
const keychain = getKeyTriple('a');
23+
const userKey = keychain[0];
24+
const descriptorMap = getDescriptorMap('Wsh2Of3', keychain);
25+
26+
const unsignedPsbt = mockPsbtDefaultWithDescriptorTemplate('Wsh2Of3');
27+
const psbtHex = Buffer.from(unsignedPsbt.serialize()).toString('hex');
28+
29+
const keyIds = ['kU', 'kB', 'kG'];
30+
const wallet = {
31+
coinSpecific: () => ({
32+
descriptors: [...descriptorMap.entries()].map(([name, descriptor]) => ({
33+
name,
34+
value: descriptor.toString(),
35+
})),
36+
}),
37+
keyIds: () => keyIds,
38+
} as unknown as UtxoWallet;
39+
40+
// Mock the keychain fetch — fetchKeychains pulls each key by id.
41+
keyIds.forEach((id, i) => {
42+
nockBitGo().get(`/api/v2/${coin.getChain()}/key/${id}`).reply(200, { pub: keychain[i].neutered().toBase58() });
43+
});
44+
45+
// decodeWith: 'wasm-utxo' is explicit to lock in the BitGoPsbt
46+
// decode path that T1-3400 broke; this is also the production
47+
// default after 1702a08009.
48+
const result = await coin.signTransaction({
49+
txPrebuild: { txHex: psbtHex, decodeWith: 'wasm-utxo' },
50+
prv: userKey.toBase58(),
51+
wallet,
52+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
53+
} as any);
54+
55+
assert.ok('txHex' in result, 'expected signTransaction to return { txHex }');
56+
const signedPsbt = Psbt.deserialize(Buffer.from(result.txHex, 'hex'));
57+
58+
const inputs = signedPsbt.getInputs();
59+
assert.ok(inputs.length > 0, 'expected at least one input');
60+
inputs.forEach((_input, vin) => {
61+
assert.ok(signedPsbt.hasPartialSignatures(vin), `input ${vin} has no partial signatures`);
62+
const sigs = signedPsbt.getPartialSignatures(vin);
63+
assert.ok(sigs.length > 0, `input ${vin} returned empty partial signatures`);
64+
// Pubkeys in partial sigs are the descriptor-derived child keys, not
65+
// the user master pubkey; assert that each sig validates at its claimed
66+
// pubkey, which is the strongest "signing actually worked" check.
67+
for (const sig of sigs) {
68+
assert.ok(
69+
signedPsbt.validateSignatureAtInput(vin, sig.pubkey),
70+
`input ${vin} has an invalid signature for pubkey ${Buffer.from(sig.pubkey).toString('hex')}`
71+
);
72+
}
73+
});
74+
});
75+
});

0 commit comments

Comments
 (0)