Skip to content

Commit c7080ff

Browse files
Merge pull request #6889 from BitGo/WP-5730/algo/verify-consolidation-tx
fix(sdk-coin-ada): verify consolidation txHex
2 parents 822f4e6 + 62876e8 commit c7080ff

File tree

2 files changed

+275
-9
lines changed

2 files changed

+275
-9
lines changed

modules/sdk-coin-ada/src/ada.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class Ada extends BaseCoin {
122122
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
123123
try {
124124
const coinConfig = coins.get(this.getChain());
125-
const { txPrebuild: txPrebuild, txParams: txParams } = params;
125+
const { txPrebuild: txPrebuild, txParams: txParams, verification, wallet } = params;
126126
const transaction = new Transaction(coinConfig);
127127
assert(txPrebuild.txHex, new Error('missing required tx prebuild property txHex'));
128128
const rawTx = txPrebuild.txHex;
@@ -142,6 +142,14 @@ export class Ada extends BaseCoin {
142142
throw new Error('cannot find recipient in expected output');
143143
}
144144
}
145+
} else if (verification?.consolidationToBaseAddress) {
146+
const baseAddress = wallet.coinSpecific()?.baseAddress || wallet.coinSpecific()?.rootAddress;
147+
148+
for (const output of explainedTx.outputs) {
149+
if (output.address !== baseAddress) {
150+
throw new Error('tx outputs does not match with expected address');
151+
}
152+
}
145153
}
146154
} catch (e) {
147155
if (e instanceof NodeEnvironmentError) {

modules/sdk-coin-ada/test/unit/ada.ts

Lines changed: 266 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,27 @@ import should = require('should');
66
import { randomBytes } from 'crypto';
77
import * as sinon from 'sinon';
88
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
9-
import { BitGoAPI } from '@bitgo/sdk-api';
9+
import { BitGoAPI, encrypt } from '@bitgo/sdk-api';
1010
import {
11-
rawTx,
12-
enterpriseAccounts as accounts,
13-
privateKeys,
14-
publicKeys,
15-
wrwUser,
16-
consolidationWrwUser,
1711
address,
12+
consolidationWrwUser,
1813
endpointResponses,
19-
testnetUTXO,
14+
enterpriseAccounts as accounts,
2015
ovcResponse,
2116
ovcResponse2,
17+
privateKeys,
18+
publicKeys,
19+
rawTx,
20+
testnetUTXO,
21+
wrwUser,
2222
} from '../resources';
2323
import * as _ from 'lodash';
2424
import { Ada, KeyPair, Tada } from '../../src';
2525
import { Transaction } from '../../src/lib';
2626
import { TransactionType } from '../../../sdk-core/src/account-lib/baseCoin/enum';
27+
import assert from 'assert';
28+
import { common, Wallet } from '@bitgo/sdk-core';
29+
import nock from 'nock';
2730

2831
describe('ADA', function () {
2932
const coinName = 'ada';
@@ -245,6 +248,174 @@ describe('ADA', function () {
245248
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
246249
validTransaction.should.equal(true);
247250
});
251+
252+
it('should verify a valid consolidation transaction', async () => {
253+
const consolidationTx = {
254+
txRequestId: '1b5c79c5-ab7c-4f47-912b-de6a95fb0779',
255+
walletId: '64fa31a94db65a0007c9691b',
256+
txHex:
257+
'84a40081825820db46c7c76ea54fec2ca0a2f94b77a238931a8dce2387c99a12b08dd8aac21c4f000181825839004601fc016ac38580916d3520bc76b659298156008738d347aa93142f4601fc016ac38580916d3520bc76b659298156008738d347aa93142f1a3b984357021a000286a9031a060e37eea0f5f6',
258+
feeInfo: {
259+
fee: 165545,
260+
feeString: '165545',
261+
},
262+
txInfo: {
263+
minerFee: '165545',
264+
spendAmount: '999834455',
265+
spendAmounts: [
266+
{
267+
coinName: 'tada',
268+
amountString: '999834455',
269+
},
270+
],
271+
payGoFee: '0',
272+
outputs: [
273+
{
274+
address:
275+
'addr_test1qprqrlqpdtpctqy3d56jp0rkkevjnq2kqzrn356842f3gt6xq87qz6krskqfzmf4yz78ddje9xq4vqy88rf5025nzshsjdmg2s',
276+
value: 999834455,
277+
wallet: '64fa31a94db65a0007c9691b',
278+
wallets: ['64fa31a94db65a0007c9691b'],
279+
enterprise: '62cc59b727443a0007089033',
280+
enterprises: ['62cc59b727443a0007089033'],
281+
valueString: '999834455',
282+
coinName: 'tada',
283+
walletType: 'hot',
284+
walletTypes: ['hot'],
285+
},
286+
],
287+
inputs: [
288+
{
289+
value: 1000000000,
290+
address:
291+
'addr_test1qqlzxfl7tlgp999x4a7334pchycpkk72pykrsr3mryl3yj6xq87qz6krskqfzmf4yz78ddje9xq4vqy88rf5025nzshsvpgl8w',
292+
valueString: '1000000000',
293+
},
294+
],
295+
type: '0',
296+
},
297+
consolidateId: '68b9fc18558006aab53785615fea7c28',
298+
coin: 'tada',
299+
};
300+
301+
const mockedWallet = {
302+
coinSpecific: () => {
303+
return {
304+
rootAddress:
305+
'addr_test1qprqrlqpdtpctqy3d56jp0rkkevjnq2kqzrn356842f3gt6xq87qz6krskqfzmf4yz78ddje9xq4vqy88rf5025nzshsjdmg2s',
306+
};
307+
},
308+
};
309+
310+
try {
311+
const isVerified = await basecoin.verifyTransaction({
312+
txParams: {},
313+
txPrebuild: consolidationTx,
314+
wallet: mockedWallet,
315+
verification: {
316+
consolidationToBaseAddress: true,
317+
},
318+
});
319+
isVerified.should.equal(true);
320+
} catch (e) {
321+
assert.fail('Transaction should pass verification');
322+
}
323+
});
324+
325+
it('should fail to sign a spoofed consolidation transaction', async function () {
326+
// Set up wallet data
327+
const walletData = {
328+
id: '5b34252f1bf349930e34020a00000000',
329+
coin: 'tada',
330+
keys: [
331+
'5b3424f91bf349930e34017500000000',
332+
'5b3424f91bf349930e34017600000000',
333+
'5b3424f91bf349930e34017700000000',
334+
],
335+
coinSpecific: {
336+
rootAddress:
337+
'addr_test1qprqrlqpdtpctqy3d56jp0rkkevjnq2kqzrn356842f3gt6xq87qz6krskqfzmf4yz78ddje9xq4vqy88rf5025nzshsjdmg2s',
338+
},
339+
multisigType: 'tss',
340+
};
341+
const fakePrv = encrypt('password', 'prv');
342+
343+
const walletObj = new Wallet(bitgo, basecoin, walletData);
344+
const bgUrl = common.Environments['mock'].uri;
345+
346+
nock(bgUrl)
347+
.get('/api/v2/tada/key/5b3424f91bf349930e34017500000000')
348+
.reply(200, [
349+
{
350+
encryptedPrv: fakePrv,
351+
},
352+
]);
353+
354+
// Mock the API response for buildAccountConsolidations
355+
nock(bgUrl)
356+
.post('/api/v2/tada/wallet/5b34252f1bf349930e34020a00000000/consolidateAccount/build')
357+
.reply(200, [
358+
{
359+
txRequestId: '1b5c79c5-ab7c-4f47-912b-de6a95fb0779',
360+
walletId: '64fa31a94db65a0007c9691b',
361+
txHex:
362+
'84a40081825820db46c7c76ea54fec2ca0a2f94b77a238931a8dce2387c99a12b08dd8aac21c4f01018282581d6033c378cee41b2e15ac848f7f6f1d2f78155ab12d93b713de898d855f1a00989680825839004601fc016ac38580916d3520bc76b659298156008738d347aa93142f4601fc016ac38580916d3520bc76b659298156008738d347aa93142f1b0000000217acebe9021a00028db5031a060e3b41a0f5f6',
363+
feeInfo: {
364+
fee: 165545,
365+
feeString: '165545',
366+
},
367+
txInfo: {
368+
minerFee: '165545',
369+
spendAmount: '999834455',
370+
spendAmounts: [
371+
{
372+
coinName: 'tada',
373+
amountString: '999834455',
374+
},
375+
],
376+
payGoFee: '0',
377+
outputs: [
378+
{
379+
address:
380+
'addr_test1qprqrlqpdtpctqy3d56jp0rkkevjnq2kqzrn356842f3gt6xq87qz6krskqfzmf4yz78ddje9xq4vqy88rf5025nzshsjdmg2s',
381+
value: 999834455,
382+
wallet: '64fa31a94db65a0007c9691b',
383+
wallets: ['64fa31a94db65a0007c9691b'],
384+
enterprise: '62cc59b727443a0007089033',
385+
enterprises: ['62cc59b727443a0007089033'],
386+
valueString: '999834455',
387+
coinName: 'tada',
388+
walletType: 'hot',
389+
walletTypes: ['hot'],
390+
},
391+
],
392+
inputs: [
393+
{
394+
value: 1000000000,
395+
address:
396+
'addr_test1qqlzxfl7tlgp999x4a7334pchycpkk72pykrsr3mryl3yj6xq87qz6krskqfzmf4yz78ddje9xq4vqy88rf5025nzshsvpgl8w',
397+
valueString: '1000000000',
398+
},
399+
],
400+
type: '0',
401+
},
402+
consolidateId: '68b9fc18558006aab53785615fea7c28',
403+
coin: 'tada',
404+
},
405+
]);
406+
407+
// Call the function to test
408+
await assert.rejects(
409+
async () => {
410+
await walletObj.sendAccountConsolidations({
411+
walletPassphrase: 'password',
412+
});
413+
},
414+
{
415+
message: 'tx outputs does not match with expected address',
416+
}
417+
);
418+
});
248419
});
249420

250421
describe('Explain Transactions:', () => {
@@ -640,4 +811,91 @@ describe('ADA', function () {
640811
sandBox.assert.calledTwice(basecoin.getDataFromNode);
641812
});
642813
});
814+
815+
describe('Verify token consolidation transaction:', () => {
816+
it('should fail to verify a spoofed token consolidation transaction with incorrect address', async () => {
817+
const consolidationTx = {
818+
txRequestId: '4fdd0cae-2563-43b1-b5cf-94865158ca10',
819+
walletId: '63068ed4efa63a000877f02fd4b0fa6d',
820+
txHex:
821+
'84a400818258204bd0f991c1532cffe31d4a10db492b43175ec326765b6b29ceee598df2b61f470001818258390087379ebc5533ebe621963c915c3cbc5f08537fcdca4af8f8ae08ed4c87379ebc5533ebe621963c915c3cbc5f08537fcdca4af8f8ae08ed4c1a05f359ff021a00028701031a024972e1a10081825820bbacb13431b99208e6e8cdbf710147feaf06a39d71565e60b411ce9e4fa3f137584001a4ab8236563f69ff309e5786e8f39c629ed57676c692159cb2e0494c9e663355384c13c749d04c17a80ba2a45cc127df480fc64a43199a772f11acd5b14a0ff5f6',
822+
feeInfo: {
823+
fee: 10000,
824+
feeString: '10000',
825+
},
826+
txInfo: {
827+
inputs: [
828+
{
829+
address: '8iLa26KSbdpBUzNK7uYq8FvyuyA5h4k4erDHsDcPbHus',
830+
value: 2.0173228e10,
831+
valueString: '20173228000',
832+
},
833+
{
834+
address: '8P2kX7Tyh9eS3RKdaBhqbEtQGAX58DXzL7mhDQABGX2d',
835+
value: 10000,
836+
valueString: '10000',
837+
},
838+
],
839+
minerFee: '10000',
840+
outputs: [
841+
{
842+
address: 'addr_test1vr8rakm66rcfv4fcxqykg5lf0yv7lsyk9mvapx369jpvtcgfcuk7f',
843+
coinName: 'tada:usdt',
844+
enterprise: {
845+
$oid: '5553ba8ae7a5c77006719661',
846+
},
847+
enterprises: [
848+
{
849+
$oid: '5553ba8ae7a5c77006719661',
850+
},
851+
],
852+
value: 2.0173228e10,
853+
valueString: '20173228000',
854+
wallet: {
855+
$oid: '62f4c3720d92c50008257eb5',
856+
},
857+
walletType: 'hot',
858+
wallets: [
859+
{
860+
$oid: '62f4c3720d92c50008257eb5',
861+
},
862+
],
863+
},
864+
],
865+
payGoFee: '0',
866+
spendAmount: '20173228000',
867+
spendAmounts: [
868+
{
869+
amountString: '20173228000',
870+
coinName: 'tada:usdt',
871+
},
872+
],
873+
type: 'Send',
874+
},
875+
consolidateId: '6712d7fda6de4906d658c04aebbf8f9b',
876+
coin: 'tada',
877+
};
878+
879+
// Mock the wallet with a different address than the transaction's output
880+
const mockedWallet = {
881+
coinSpecific: () => {
882+
return {
883+
rootAddress:
884+
'addr_test1qqnnvptrc3rec64q2n9jh572ncu5wvdtt8uvg4g3aj96s5dwu9nj70mlahzglm9939uevupsmj8dcdqv25d5n5r8vw8sn7prey',
885+
};
886+
},
887+
};
888+
889+
await basecoin
890+
.verifyTransaction({
891+
txParams: {},
892+
txPrebuild: consolidationTx,
893+
wallet: mockedWallet,
894+
verification: {
895+
consolidationToBaseAddress: true,
896+
},
897+
})
898+
.should.be.rejectedWith('tx outputs does not match with expected address');
899+
});
900+
});
643901
});

0 commit comments

Comments
 (0)