Skip to content

Commit 951fbbc

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

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import nock = require('nock');
2+
import * as sinon from 'sinon';
3+
import * as assert from 'assert';
4+
import 'should';
5+
6+
import { BitGoAPI } from '@bitgo/sdk-api';
7+
import { TestBitGo } from '@bitgo/sdk-test';
8+
import { Tbtc } from '@bitgo/sdk-coin-btc';
9+
import { common, BaseCoin, BitGoBase, Wallet, WalletSignTransactionOptions } from '@bitgo/sdk-core';
10+
11+
describe('Wallet signTransaction with verifyTxParams', function () {
12+
let wallet: Wallet;
13+
let basecoin: BaseCoin;
14+
let verifyTransactionStub: sinon.SinonStub;
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+
// Mock wallet data
23+
const walletData = {
24+
id: 'test-wallet-id',
25+
coin: 'tbtc',
26+
label: 'Test Wallet',
27+
m: 2,
28+
n: 3,
29+
keys: ['key1', 'key2', 'key3'],
30+
multisigType: 'onchain',
31+
type: 'hot',
32+
balance: 100000,
33+
balanceString: '100000',
34+
confirmedBalance: 100000,
35+
confirmedBalanceString: '100000',
36+
spendableBalance: 100000,
37+
spendableBalanceString: '100000',
38+
};
39+
40+
wallet = new Wallet(bitgo as unknown as BitGoBase, basecoin as unknown as BaseCoin, walletData);
41+
42+
// Create stubs for verification
43+
sinon.stub(basecoin, 'signTransaction').resolves({ txHex: 'mock-signed-tx-hex' });
44+
verifyTransactionStub = sinon.stub(basecoin, 'verifyTransaction');
45+
});
46+
47+
afterEach(function () {
48+
sinon.restore();
49+
nock.cleanAll();
50+
});
51+
52+
it('should fail verification when verifyTransaction throws an error', async function () {
53+
// Mock the verification function to throw an error (simulating verification failure)
54+
verifyTransactionStub.throws(new Error('Transaction verification failed'));
55+
56+
const txPrebuild = {
57+
txHex: 'mock-tx-hex',
58+
walletId: 'test-wallet-id',
59+
};
60+
61+
const verifyTxParams = {
62+
txParams: {
63+
recipients: [
64+
{
65+
address: 'test-address',
66+
amount: '10000',
67+
},
68+
],
69+
type: 'send',
70+
},
71+
};
72+
73+
const signParams: WalletSignTransactionOptions = {
74+
txPrebuild,
75+
verifyTxParams,
76+
};
77+
78+
try {
79+
await wallet.signTransaction(signParams);
80+
assert.fail('Should have thrown verification error');
81+
} catch (error) {
82+
assert.ok(
83+
error.message.includes('Transaction verification failed'),
84+
`Error message should contain 'Transaction verification failed', got: ${error.message}`
85+
);
86+
}
87+
88+
// Verify that the verification function was called with the expected parameters
89+
sinon.assert.calledOnce(verifyTransactionStub);
90+
const callArgs = verifyTransactionStub.getCall(0).args;
91+
const verifyParams = callArgs[0];
92+
assert.strictEqual(verifyParams.txPrebuild.txHex, 'mock-tx-hex');
93+
assert.deepStrictEqual(verifyParams.txParams, verifyTxParams.txParams);
94+
});
95+
96+
it('should pass verification when verifyTransaction succeeds', async function () {
97+
// Mock the verification function to succeed (no error thrown)
98+
verifyTransactionStub.returns(true);
99+
100+
// Mock key retrieval endpoints
101+
const bgUrl = common.Environments['mock'].uri;
102+
nock(bgUrl).get('/api/v2/tbtc/key/key1').reply(200, {
103+
id: 'key1',
104+
pub: 'pub',
105+
prv: 'prv',
106+
});
107+
108+
nock(bgUrl).get('/api/v2/tbtc/key/key2').reply(200, {
109+
id: 'key2',
110+
pub: 'pub',
111+
prv: 'prv',
112+
});
113+
114+
nock(bgUrl).get('/api/v2/tbtc/key/key3').reply(200, {
115+
id: 'key3',
116+
pub: 'pub',
117+
});
118+
119+
const txPrebuild = {
120+
txHex: 'mock-tx-hex',
121+
walletId: 'test-wallet-id',
122+
};
123+
124+
const verifyTxParams: WalletSignTransactionOptions['verifyTxParams'] = {
125+
txParams: {
126+
recipients: [
127+
{
128+
address: 'test-address',
129+
amount: '1000',
130+
},
131+
],
132+
type: 'send',
133+
},
134+
};
135+
136+
const signParams: WalletSignTransactionOptions = {
137+
txPrebuild,
138+
verifyTxParams,
139+
prv: 'prv',
140+
};
141+
142+
const result = await wallet.signTransaction(signParams);
143+
144+
// Verify the result
145+
result.should.have.property('txHex', 'mock-signed-tx-hex');
146+
147+
// Verify that the verification function was called with the expected parameters
148+
sinon.assert.calledOnce(verifyTransactionStub);
149+
const callArgs = verifyTransactionStub.getCall(0).args;
150+
const verifyParams = callArgs[0];
151+
assert.strictEqual(verifyParams.txPrebuild.txHex, 'mock-tx-hex');
152+
assert.deepStrictEqual(verifyParams.txParams, verifyTxParams.txParams);
153+
});
154+
});

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) &&

0 commit comments

Comments
 (0)