Skip to content

Commit 34ec728

Browse files
Merge pull request #6819 from BitGo/WP-3968/fix/near-consolidation-blind-sign
fix(sdk-coin-near): verify platform built consolidation tx
2 parents a6f979b + 813abe0 commit 34ec728

File tree

6 files changed

+509
-2
lines changed

6 files changed

+509
-2
lines changed

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as request from 'superagent';
1111
import { auditEddsaPrivateKey } from '@bitgo/sdk-lib-mpc';
1212
import {
1313
AuditDecryptedKeyParams,
14+
BaseAddress,
1415
BaseCoin,
1516
BaseTransaction,
1617
BitGoBase,
@@ -1040,6 +1041,11 @@ export class Near extends BaseCoin {
10401041
throw new Error('Tx total amount does not match with expected total amount field');
10411042
}
10421043
}
1044+
1045+
if (params.verification?.consolidationToBaseAddress) {
1046+
await this.verifyConsolidationToBaseAddress(params, explainedTx);
1047+
}
1048+
10431049
return true;
10441050
}
10451051

@@ -1054,4 +1060,21 @@ export class Near extends BaseCoin {
10541060
}
10551061
auditEddsaPrivateKey(prv, publicKey ?? '');
10561062
}
1063+
1064+
protected async verifyConsolidationToBaseAddress(
1065+
params: VerifyTransactionOptions,
1066+
explainedTx: TransactionExplanation
1067+
): Promise<void> {
1068+
const baseAddresses: BaseAddress[] | undefined = await params.wallet.addresses({ sort: -1, limit: 1 });
1069+
if (!baseAddresses || baseAddresses.length === 0) {
1070+
throw new Error('No base address found on wallet');
1071+
}
1072+
const baseAddress = baseAddresses[0];
1073+
1074+
for (const output of explainedTx.outputs) {
1075+
if (output.address !== baseAddress.address) {
1076+
throw new Error('tx outputs does not match with expected address');
1077+
}
1078+
}
1079+
}
10571080
}

modules/sdk-coin-near/src/nep141Token.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ export class Nep141Token extends Near {
104104
throw new Error('Tx total amount does not match with expected total amount field');
105105
}
106106
}
107+
108+
if (params.verification?.consolidationToBaseAddress) {
109+
await this.verifyConsolidationToBaseAddress(params, explainedTx);
110+
}
111+
107112
return true;
108113
}
109114
}

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

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import { randomBytes } from 'crypto';
33
import should from 'should';
44
import _ from 'lodash';
55
import sinon from 'sinon';
6+
import nock from 'nock';
7+
import assert from 'assert';
68

79
import { BitGoAPI } from '@bitgo/sdk-api';
810
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
911
import { coins } from '@bitgo/statics';
12+
import { common, TransactionPrebuild, Wallet } from '@bitgo/sdk-core';
1013

1114
import { KeyPair, Near, TNear, Transaction } from '../../src';
1215
import nearUtils from '../../src/lib/utils';
@@ -422,6 +425,204 @@ describe('NEAR:', function () {
422425
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
423426
validTransaction.should.equal(true);
424427
});
428+
429+
it('should verify a spoofed consolidation transaction', async function () {
430+
// Set up wallet data
431+
const walletData = {
432+
id: '62e156dbd641c000076bbabe04041a90',
433+
coin: 'tnear',
434+
keys: [
435+
'5b3424f91bf349930e34017500000000',
436+
'5b3424f91bf349930e34017600000000',
437+
'5b3424f91bf349930e34017700000000',
438+
],
439+
coinSpecific: {
440+
rootAddress: '3a1b77653ea1705ad297db7abe259953b4ad5d2ecc5b50bee9a486f785dd90db',
441+
},
442+
multisigType: 'tss',
443+
};
444+
445+
const consolidationTx = {
446+
txRequestId: '03fdf51a-28e1-4268-b5be-a4afc030ff64',
447+
walletId: '62e156dbd641c000076bbabe',
448+
txHex:
449+
'400000006562376433623333313166616261653338393062363731383536343838383066663831383766353465623565626665343336646131313338333130396638623500eb7d3b3311fabae3890b67185648880ff8187f54eb5ebfe436da11383109f8b5c8a6d8a6f86100004000000061393465333937306165633436626262313536393331636130393065643735666633616164653439373966666566366437346337326435613234376365393466ad19fa7faa45643200f99a992715a02f9806bcbf0c5737ccdf5d61172d61a4f901000000030038bf94dd153d9d7fa7010000000000',
450+
feeInfo: {
451+
fee: 85332111947887500000,
452+
feeString: '85332111947887500377',
453+
},
454+
txInfo: {
455+
minerFee: '0',
456+
spendAmount: '1999915088987500000000000',
457+
spendAmounts: [
458+
{
459+
coinName: 'tnear',
460+
amountString: '1999915088987500000000000',
461+
},
462+
],
463+
payGoFee: '0',
464+
outputs: [
465+
{
466+
address: 'a94e3970aec46bbb156931ca090ed75ff3aade4979ffef6d74c72d5a247ce94f',
467+
value: 1.9999150889875e24,
468+
wallet: '62e156dbd641c000076bbabe',
469+
wallets: ['62e156dbd641c000076bbabe'],
470+
enterprise: '6111785f59548d0007a4d13c',
471+
enterprises: ['6111785f59548d0007a4d13c'],
472+
valueString: '1999915088987500000000000',
473+
coinName: 'tnear',
474+
walletType: 'hot',
475+
walletTypes: ['hot'],
476+
},
477+
],
478+
inputs: [
479+
{
480+
value: 1.9999150889875e24,
481+
address: 'eb7d3b3311fabae3890b67185648880ff8187f54eb5ebfe436da11383109f8b5',
482+
valueString: '1999915088987500000000000',
483+
},
484+
],
485+
type: '0',
486+
},
487+
consolidateId: '68ae77ec62346a69d0aee5a2dda69c8c',
488+
coin: 'tnear',
489+
};
490+
const bgUrl = common.Environments['mock'].uri;
491+
const walletObj = new Wallet(bitgo, basecoin, walletData);
492+
493+
nock(bgUrl)
494+
.post('/api/v2/tnear/wallet/62e156dbd641c000076bbabe04041a90/consolidateAccount/build')
495+
.reply(200, [
496+
{
497+
...consolidationTx,
498+
txHex:
499+
'400000006139346533393730616563343662626231353639333163613039306564373566663361616465343937396666656636643734633732643561323437636539346600a94e3970aec46bbb156931ca090ed75ff3aade4979ffef6d74c72d5a247ce94f1f3dec154a5700004000000066326137386638303336663861343266383730313962316431646336336131623337623139333365653632646464353365373438633530323266316435373961fb6130e6cc30c926fccde8043ce4e43a810ea63b4d3f93623a54176ff8db1cd001000000030000004a480114169545080000000000',
500+
},
501+
]);
502+
503+
nock(bgUrl)
504+
.get('/api/v2/tnear/key/5b3424f91bf349930e34017500000000')
505+
.reply(200, [
506+
{
507+
encryptedPrv: 'fakePrv',
508+
},
509+
]);
510+
511+
nock(bgUrl)
512+
.get('/api/v2/tnear/wallet/62e156dbd641c000076bbabe04041a90/addresses?sort=-1&limit=1')
513+
.reply(200, [
514+
{
515+
address: 'a94e3970aec46bbb156931ca090ed75ff3aade4979ffef6d74c72d5a247ce94f',
516+
},
517+
]);
518+
519+
// Call the function to test
520+
await assert.rejects(
521+
async () => {
522+
await walletObj.sendAccountConsolidations({
523+
walletPassphrase: 'password',
524+
verification: {
525+
consolidationToBaseAddress: true,
526+
},
527+
});
528+
},
529+
{
530+
message: 'tx outputs does not match with expected address',
531+
}
532+
);
533+
});
534+
535+
it('should verify valid a consolidation transaction', async () => {
536+
// Set up wallet data
537+
const walletData = {
538+
id: '62e156dbd641c000076bbabe04041a90',
539+
coin: 'tnear',
540+
keys: [
541+
'5b3424f91bf349930e34017500000000',
542+
'5b3424f91bf349930e34017600000000',
543+
'5b3424f91bf349930e34017700000000',
544+
],
545+
coinSpecific: {
546+
rootAddress: '3a1b77653ea1705ad297db7abe259953b4ad5d2ecc5b50bee9a486f785dd90db',
547+
},
548+
multisigType: 'tss',
549+
};
550+
551+
const consolidationTx = {
552+
txRequestId: '03fdf51a-28e1-4268-b5be-a4afc030ff64',
553+
walletId: '62e156dbd641c000076bbabe',
554+
txHex:
555+
'400000006562376433623333313166616261653338393062363731383536343838383066663831383766353465623565626665343336646131313338333130396638623500eb7d3b3311fabae3890b67185648880ff8187f54eb5ebfe436da11383109f8b5c8a6d8a6f86100004000000061393465333937306165633436626262313536393331636130393065643735666633616164653439373966666566366437346337326435613234376365393466ad19fa7faa45643200f99a992715a02f9806bcbf0c5737ccdf5d61172d61a4f901000000030038bf94dd153d9d7fa7010000000000',
556+
feeInfo: {
557+
fee: 85332111947887500000,
558+
feeString: '85332111947887500377',
559+
},
560+
txInfo: {
561+
minerFee: '0',
562+
spendAmount: '1999915088987500000000000',
563+
spendAmounts: [
564+
{
565+
coinName: 'tnear',
566+
amountString: '1999915088987500000000000',
567+
},
568+
],
569+
payGoFee: '0',
570+
outputs: [
571+
{
572+
address: 'a94e3970aec46bbb156931ca090ed75ff3aade4979ffef6d74c72d5a247ce94f',
573+
value: 1.9999150889875e24,
574+
wallet: '62e156dbd641c000076bbabe',
575+
wallets: ['62e156dbd641c000076bbabe'],
576+
enterprise: '6111785f59548d0007a4d13c',
577+
enterprises: ['6111785f59548d0007a4d13c'],
578+
valueString: '1999915088987500000000000',
579+
coinName: 'tnear',
580+
walletType: 'hot',
581+
walletTypes: ['hot'],
582+
},
583+
],
584+
inputs: [
585+
{
586+
value: 1.9999150889875e24,
587+
address: 'eb7d3b3311fabae3890b67185648880ff8187f54eb5ebfe436da11383109f8b5',
588+
valueString: '1999915088987500000000000',
589+
},
590+
],
591+
type: '0',
592+
},
593+
consolidateId: '68ae77ec62346a69d0aee5a2dda69c8c',
594+
coin: 'tnear',
595+
};
596+
const bgUrl = common.Environments['mock'].uri;
597+
598+
nock(bgUrl)
599+
.get('/api/v2/tnear/wallet/62e156dbd641c000076bbabe04041a90/addresses?sort=-1&limit=1')
600+
.reply(200, [
601+
{
602+
address: 'a94e3970aec46bbb156931ca090ed75ff3aade4979ffef6d74c72d5a247ce94f',
603+
},
604+
]);
605+
606+
try {
607+
if (
608+
!(await basecoin.verifyTransaction({
609+
blockhash: '',
610+
feePayer: '',
611+
txParams: {},
612+
txPrebuild: consolidationTx as unknown as TransactionPrebuild,
613+
walletType: 'tss',
614+
wallet: new Wallet(bitgo, basecoin, walletData),
615+
verification: {
616+
consolidationToBaseAddress: true,
617+
},
618+
}))
619+
) {
620+
assert.fail('Transaction should pass verification');
621+
}
622+
} catch (e) {
623+
assert.fail('Transaction should pass verification');
624+
}
625+
});
425626
});
426627

427628
describe('Explain Transactions:', () => {

0 commit comments

Comments
 (0)