Skip to content

Commit fa8f983

Browse files
feat(sdk-core): add verification options for signing txHex
TICKET: WP-6188
1 parent 60cdd6a commit fa8f983

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-1
lines changed

modules/sdk-core/src/bitgo/wallet/iWallet.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Message,
55
SignedMessage,
66
SignedTransaction,
7+
TransactionParams,
78
TransactionPrebuild,
89
VerificationOptions,
910
TypedData,
@@ -274,6 +275,14 @@ export interface WalletSignTransactionOptions extends WalletSignBaseOptions {
274275
apiVersion?: ApiVersion;
275276
multisigTypeVersion?: 'MPCv2';
276277
walletPassphrase?: string;
278+
/**
279+
* Optional transaction verification parameters. When provided, the transaction will be verified
280+
* using verifyTransaction before signing.
281+
*/
282+
verifyTxParams?: {
283+
txParams: TransactionParams;
284+
verification?: VerificationOptions;
285+
};
277286
[index: string]: unknown;
278287
}
279288

modules/sdk-core/src/bitgo/wallet/wallet.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import {
8585
GetTransactionOptions,
8686
GetTransferOptions,
8787
GetUserPrvOptions,
88-
IWallet,
88+
type IWallet,
8989
ManageUnspentReservationOptions,
9090
MaximumSpendable,
9191
MaximumSpendableOptions,
@@ -1953,6 +1953,9 @@ export class Wallet implements IWallet {
19531953
* - txPrebuild
19541954
* - [keychain / key] (object) or prv (string)
19551955
* - walletPassphrase
1956+
* - verifyTxParams (optional) - when provided, the transaction will be verified before signing
1957+
* - txParams: transaction parameters used for verification
1958+
* - verification: optional verification options
19561959
* @return {*}
19571960
*/
19581961
async signTransaction(params: WalletSignTransactionOptions = {}): Promise<SignedTransaction | TxRequest> {
@@ -1997,6 +2000,20 @@ export class Wallet implements IWallet {
19972000
params.txPrebuild = { txRequestId };
19982001
}
19992002

2003+
// Verify transaction if verifyTxParams is provided
2004+
if (params.verifyTxParams && txPrebuild?.txHex) {
2005+
const verifyParams = {
2006+
txPrebuild: { ...txPrebuild },
2007+
txParams: params.verifyTxParams.txParams,
2008+
wallet: this as IWallet,
2009+
verification: params.verifyTxParams.verification,
2010+
reqId: params.reqId,
2011+
walletType: this.multisigType() as 'onchain' | 'tss',
2012+
};
2013+
2014+
await this.baseCoin.verifyTransaction(verifyParams);
2015+
}
2016+
20002017
if (
20012018
params.walletPassphrase &&
20022019
!(params.keychain || params.key) &&
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { BitGoAPI } from '@bitgo/sdk-api';
2+
import { TestBitGo } from '@bitgo/sdk-test';
3+
import * as assert from 'assert';
4+
import 'should';
5+
import { Wallet } from '../../../../src/bitgo/wallet/wallet';
6+
import { WalletSignTransactionOptions } from '../../../../src/bitgo/wallet/iWallet';
7+
import { Tbtc } from '@bitgo/sdk-coin-btc';
8+
import nock from 'nock';
9+
import * as sinon from 'sinon';
10+
import { common, BaseCoin, BitGoBase } from '@bitgo/sdk-core';
11+
12+
describe('Wallet signTransaction with verifyTxParams', function () {
13+
let realWallet: Wallet;
14+
let basecoin: BaseCoin;
15+
16+
beforeEach(function () {
17+
const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' });
18+
bitgo.initializeTestVars();
19+
bitgo.safeRegister('tbtc', Tbtc.createInstance);
20+
basecoin = bitgo.coin('tbtc');
21+
22+
// Real wallet data from tbtc testnet
23+
const realWalletData = {
24+
id: '6840948b17e91662b782d55bbf988c4e',
25+
coin: 'tbtc',
26+
label: 'Test: User & Backup Signing',
27+
m: 2,
28+
n: 3,
29+
keys: [
30+
'6840947d037fdb798e0bf860e52cc4a8',
31+
'6840947e7c18efe3b0b77e9a75308aab',
32+
'68409480bdf143f4a1d32474acc09baa',
33+
],
34+
multisigType: 'onchain',
35+
type: 'hot',
36+
balance: 329034,
37+
balanceString: '329034',
38+
confirmedBalance: 329034,
39+
confirmedBalanceString: '329034',
40+
spendableBalance: 329034,
41+
spendableBalanceString: '329034',
42+
};
43+
sinon.stub(basecoin, 'signTransaction').resolves({ txHex: 'signed' });
44+
45+
realWallet = new Wallet(bitgo as unknown as BitGoBase, basecoin as unknown as BaseCoin, realWalletData);
46+
});
47+
48+
afterEach(function () {
49+
nock.cleanAll();
50+
});
51+
52+
it('should fail verification when expected recipient does not match actual transaction recipient', async function () {
53+
// Create a spy for verifyUserPublicKey to always return true
54+
55+
// Real transaction hex that sends to tb1p3dcj78muwwac8fvas43zr48vca70pue3eku6txvju0nxnhm0rgwsgv0k3v
56+
const realTxHex =
57+
'70736274ff0100890100000001e082ee5f3be60a260bd181d86cbc3ed1f2f53f6e33572138c3220a461d64e13d0100000000fdffffff0210270000000000002251208b712f1f7c73bb83a59d856221d4ecc77cf0f331cdb9a59992e3e669df6f1a1de3d5040000000000225120135ddb1d9e6e1987adacd341cb2e146544e69e9b96eed7aec27a4a03d83ac324000000004f010488b21e000000000000000000940a6f6c2d84214ba69e48354858dd8e4df2b0f36d51b6721516172c4b56922402c8d26710504a5a7965c8fce430057418802bc5a06e1987ede6897fb40e0a66b5044b8de8914f010488b21e0000000000000000009c6426c55cb8e0b186f7f776b42cb4de8118be401cce288bcb1ef70457f4072c0397f25ebd3f03c85333b1ffc14e04d051b476a5e48e4e0a5e999c0cf163c3178704758af64b4f010488b21e000000000000000000ab9a6eea233e963b74fd79afd9c71d467fb1ce1d91fe1681e2b7332e203baae6033e70fb09c6eb45d08aff2f067060c6345143918961d5bee0e1d8037b77001925047b156bd00001012b8e02050000000000225120a385cb8dc799daaffb15a3e311e1465b3ce990017034ec4e1e2056575a3f609001030400000000211686f3450713d04e8ac343e576c1df80c5a1fca4e45aff6ee6447ceeb4d0d51d671500758af64b000000000000000029000000110000002116b7d96086e8b6763162b7deb2a0149d104d01a2ddad547f290822d304dd6bd14815004b8de89100000000000000002900000011000000011720cd77b37b43fe3ae9cdcb266faaf4b443766269f3182f6bb40f3a9db93f8384d7011820b558f0176c61865e960bbd14bff1e251b99cebb81fea61c0f931452dc029778648fc05424954474f01a385cb8dc799daaffb15a3e311e1465b3ce990017034ec4e1e2056575a3f6090cd77b37b43fe3ae9cdcb266faaf4b443766269f3182f6bb40f3a9db93f8384d7420286f3450713d04e8ac343e576c1df80c5a1fca4e45aff6ee6447ceeb4d0d51d6703b7d96086e8b6763162b7deb2a0149d104d01a2ddad547f290822d304dd6bd148000001052054977fb64552af2ac9901f8ca6d43e9980c64af82fa0bc24f01a7a95328f4d6901068e01c044202a24989f02cd1e9cf2560b48b3c7122c8a643e9ce22c6406a0d65b8f75053c9fad200665676f5990a63c2c73e626b3fc74156c4ef9a909ebd66ca83f7797602dcd26ac01c044200665676f5990a63c2c73e626b3fc74156c4ef9a909ebd66ca83f7797602dcd26ad2002617797bd49c31b5aa0c954b88b6f0cf366d876863db7a94285a2ed1f22b3c3ac210702617797bd49c31b5aa0c954b88b6f0cf366d876863db7a94285a2ed1f22b3c33501df60ca0827941d266e210000354802a610ce1c59b91616c99c795c74eb230ef94b8de8910000000000000000290000001500000021070665676f5990a63c2c73e626b3fc74156c4ef9a909ebd66ca83f7797602dcd26550246660e42faa7f1e691d65610dca0ca5e16a9c2230ac1188e164539092e81d885df60ca0827941d266e210000354802a610ce1c59b91616c99c795c74eb230ef97b156bd00000000000000000290000001500000021072a24989f02cd1e9cf2560b48b3c7122c8a643e9ce22c6406a0d65b8f75053c9f350146660e42faa7f1e691d65610dca0ca5e16a9c2230ac1188e164539092e81d885758af64b0000000000000000290000001500000000';
58+
59+
const txPrebuild = {
60+
txHex: realTxHex,
61+
walletId: '6840948b17e91662b782d55bbf988c4e',
62+
};
63+
64+
// Verification parameters with wrong expected recipient
65+
const verifyTxParams = {
66+
txParams: {
67+
recipients: [
68+
{
69+
address: '2Muux9UnVFCiGaYbX8D8FTsKaErkhLXRX5n', // Expected recipient
70+
amount: '10000', // Expected amount
71+
},
72+
],
73+
type: 'send',
74+
},
75+
};
76+
const bgUrl = common.Environments['mock'].uri;
77+
nock(bgUrl).get(`/api/v2/tbtc/key/6840947d037fdb798e0bf860e52cc4a8`).reply(200, {
78+
id: '6840947d037fdb798e0bf860e52cc4a8',
79+
pub: 'xpub661MyMwAqRbcG6k1Jmauo273gsqQSv8z94FHSkkPVbxZCPcCkVvCTVCBe1VF6A8rc4Dzo31LCB8rx2L3LedefymRcsmQTVNbH7QNvj2HY9B',
80+
});
81+
82+
nock(bgUrl).get(`/api/v2/tbtc/key/6840947e7c18efe3b0b77e9a75308aab`).reply(200, {
83+
id: '6840947e7c18efe3b0b77e9a75308aab',
84+
pub: 'xpub661MyMwAqRbcGFXVKNXUMN5UEy7kYYGjg8AN4HXVndnkXRrFnWPGtagUmcFDUhMy9RcxC65eQp5cSaGMhRfEfkt7sfU6cYmiakafzwGLRkq',
85+
});
86+
87+
nock(bgUrl).get(`/api/v2/tbtc/key/68409480bdf143f4a1d32474acc09baa`).reply(200, {
88+
id: '68409480bdf143f4a1d32474acc09baa',
89+
pub: 'xpub661MyMwAqRbcG1vLCx1SHbu94HL7LwAtguFD7bcwYSPbPMexPZbKEgNcWk3QREc8s4mV5MPh9ZX8UbQVxzY3no9GUo4HwCQdY8u3U4Hx5vR',
90+
});
91+
92+
const signParams: WalletSignTransactionOptions = {
93+
txPrebuild,
94+
verifyTxParams,
95+
};
96+
97+
try {
98+
await realWallet.signTransaction(signParams);
99+
assert.fail('Should have thrown verification error');
100+
} catch (error) {
101+
assert.ok(
102+
error.message.includes('expected outputs missing in transaction prebuild'),
103+
`Error message should contain 'expected outputs missing in transaction prebuild', got: ${error.message}`
104+
);
105+
}
106+
});
107+
108+
it('should pass verification when expected recipient matches actual transaction recipient', async function () {
109+
// Same real transaction hex
110+
const realTxHex =
111+
'70736274ff0100890100000001c30076456d211a7c951bdfce1ee3c51d4fbba63effe7b72a7e598355360258810100000000fdffffff02e8030000000000002251203c8294de47961d271c0cc3a1d2a45f9debb4ff1414a58b1c63fb685ab27e47bd60cc0400000000002251206c59f342df3d02bdc20820489c963d38ac799f7559d6fa66635a65b017b51a55000000004f010488b21e000000000000000000940a6f6c2d84214ba69e48354858dd8e4df2b0f36d51b6721516172c4b56922402c8d26710504a5a7965c8fce430057418802bc5a06e1987ede6897fb40e0a66b5044b8de8914f010488b21e0000000000000000009c6426c55cb8e0b186f7f776b42cb4de8118be401cce288bcb1ef70457f4072c0397f25ebd3f03c85333b1ffc14e04d051b476a5e48e4e0a5e999c0cf163c3178704758af64b4f010488b21e000000000000000000ab9a6eea233e963b74fd79afd9c71d467fb1ce1d91fe1681e2b7332e203baae6033e70fb09c6eb45d08aff2f067060c6345143918961d5bee0e1d8037b77001925047b156bd00001012be3d504000000000022512056300043d90ac87b8cec52c013e4f4e005673efdb4f81187a686ab2e5312e013010304000000002116174f8b9e8e39aa567741a082d6aa3dc1c54eeef21a129cb6c835d0d00cb8f6f715004b8de89100000000000000002900000016000000211669a5e6ddfd5c5b7ef2ab67f945f2f80b4bb31975d66835dd388812bf76a2c1f91500758af64b00000000000000002900000016000000011720674c90f97968e7cb8ea607836bd032c5091ad04f6705b017c4fa640e0b977528011820b13af832b77b05ad8ec03628e865fe1543666d1db165c2c5be0eee2f2be911c048fc05424954474f0156300043d90ac87b8cec52c013e4f4e005673efdb4f81187a686ab2e5312e013674c90f97968e7cb8ea607836bd032c5091ad04f6705b017c4fa640e0b977528420269a5e6ddfd5c5b7ef2ab67f945f2f80b4bb31975d66835dd388812bf76a2c1f903174f8b9e8e39aa567741a082d6aa3dc1c54eeef21a129cb6c835d0d00cb8f6f7000001052058d7526ec06446670ed4a962867a23107515aba6c112d492a7f020eea7a85cdb01068e01c04420b7a3d89bb4d477aa4b47e393bace553aefd17bd1f8c51f1810dac3b8aa517f4ead204532ea3e0b6d89457d1ffe4b44cd9f0028f43a1f40f0e91e8306745fbcf86fb8ac01c044204532ea3e0b6d89457d1ffe4b44cd9f0028f43a1f40f0e91e8306745fbcf86fb8ad2087c6ec2bd90ee3141494456ff1525f5bf62381e9a66630ee2ce2f71398b1b4c0ac21074532ea3e0b6d89457d1ffe4b44cd9f0028f43a1f40f0e91e8306745fbcf86fb855020e054d24e61547905ebdad16202ad61cdc52ff424001d211bfec1c954f6cbcee793c053d56fe57a1d343047cb823edb85ba32c742fc0e39d921edf776a38427b7b156bd000000000000000002900000017000000210787c6ec2bd90ee3141494456ff1525f5bf62381e9a66630ee2ce2f71398b1b4c03501793c053d56fe57a1d343047cb823edb85ba32c742fc0e39d921edf776a38427b4b8de891000000000000000029000000170000002107b7a3d89bb4d477aa4b47e393bace553aefd17bd1f8c51f1810dac3b8aa517f4e35010e054d24e61547905ebdad16202ad61cdc52ff424001d211bfec1c954f6cbcee758af64b0000000000000000290000001700000000';
112+
const bgUrl = common.Environments['mock'].uri;
113+
nock(bgUrl).get(`/api/v2/tbtc/key/6840947d037fdb798e0bf860e52cc4a8`).reply(200, {
114+
id: '6840947d037fdb798e0bf860e52cc4a8',
115+
pub: 'xpub661MyMwAqRbcG6k1Jmauo273gsqQSv8z94FHSkkPVbxZCPcCkVvCTVCBe1VF6A8rc4Dzo31LCB8rx2L3LedefymRcsmQTVNbH7QNvj2HY9B',
116+
});
117+
118+
nock(bgUrl).get(`/api/v2/tbtc/key/6840947e7c18efe3b0b77e9a75308aab`).reply(200, {
119+
id: '6840947e7c18efe3b0b77e9a75308aab',
120+
pub: 'xpub661MyMwAqRbcGFXVKNXUMN5UEy7kYYGjg8AN4HXVndnkXRrFnWPGtagUmcFDUhMy9RcxC65eQp5cSaGMhRfEfkt7sfU6cYmiakafzwGLRkq',
121+
});
122+
123+
nock(bgUrl).get(`/api/v2/tbtc/key/68409480bdf143f4a1d32474acc09baa`).reply(200, {
124+
id: '68409480bdf143f4a1d32474acc09baa',
125+
pub: 'xpub661MyMwAqRbcG1vLCx1SHbu94HL7LwAtguFD7bcwYSPbPMexPZbKEgNcWk3QREc8s4mV5MPh9ZX8UbQVxzY3no9GUo4HwCQdY8u3U4Hx5vR',
126+
});
127+
128+
const txPrebuild = {
129+
txHex: realTxHex,
130+
walletId: '6840948b17e91662b782d55bbf988c4e',
131+
};
132+
133+
// Verification parameters with correct expected recipient
134+
const verifyTxParams: WalletSignTransactionOptions['verifyTxParams'] = {
135+
txParams: {
136+
recipients: [
137+
{
138+
address: 'tb1p8jpffhj8jcwjw8qvcwsa9fzlnh4mflc5zjjck8rrld594vn7g77slm250x', // Correct recipient
139+
amount: '1000', // Expected amount
140+
},
141+
],
142+
type: 'send',
143+
},
144+
};
145+
146+
const signParams: WalletSignTransactionOptions = {
147+
txPrebuild,
148+
verifyTxParams,
149+
pubs: [
150+
'xpub661MyMwAqRbcG6k1Jmauo273gsqQSv8z94FHSkkPVbxZCPcCkVvCTVCBe1VF6A8rc4Dzo31LCB8rx2L3LedefymRcsmQTVNbH7QNvj2HY9B',
151+
'xpub661MyMwAqRbcGFXVKNXUMN5UEy7kYYGjg8AN4HXVndnkXRrFnWPGtagUmcFDUhMy9RcxC65eQp5cSaGMhRfEfkt7sfU6cYmiakafzwGLRkq',
152+
'xpub661MyMwAqRbcG1vLCx1SHbu94HL7LwAtguFD7bcwYSPbPMexPZbKEgNcWk3QREc8s4mV5MPh9ZX8UbQVxzY3no9GUo4HwCQdY8u3U4Hx5vR',
153+
],
154+
prv: 'prv',
155+
};
156+
157+
const result = await realWallet.signTransaction(signParams);
158+
159+
// Verify the result
160+
result.should.have.property('txHex', 'signed');
161+
});
162+
});

0 commit comments

Comments
 (0)