Skip to content

Commit 4a5ba60

Browse files
fix(sdk-core): add validation for unsigned txHex
Ticket: COIN-3222 TICKET: COIN-3222
1 parent 1f4cf69 commit 4a5ba60

File tree

8 files changed

+87
-10
lines changed

8 files changed

+87
-10
lines changed

modules/abstract-cosmos/src/cosmosCoin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,10 @@ export class CosmosCoin extends BaseCoin {
351351
if (!rawTx) {
352352
throw new Error('missing required tx prebuild property txHex');
353353
}
354-
const transaction = await this.getBuilder().from(rawTx).build();
355-
const explainedTx = transaction.explainTransaction();
356354

357355
if (txParams.recipients && txParams.recipients.length > 0) {
356+
const transaction = await this.getBuilder().from(rawTx).build();
357+
const explainedTx = transaction.explainTransaction();
358358
const filteredRecipients = txParams.recipients?.map((recipient) => _.pick(recipient, ['address', 'amount']));
359359
const filteredOutputs = explainedTx.outputs.map((output) => _.pick(output, ['address', 'amount']));
360360

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,13 +2299,13 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
22992299
* @param {Wallet} params.wallet - Wallet object to obtain keys to verify against
23002300
* @returns {boolean}
23012301
*/
2302-
verifyTssTransaction(params: VerifyEthTransactionOptions): boolean {
2302+
async verifyTssTransaction(params: VerifyEthTransactionOptions): Promise<boolean> {
23032303
const { txParams, txPrebuild, wallet } = params;
23042304
if (
23052305
!txParams?.recipients &&
23062306
!(
23072307
txParams.prebuildTx?.consolidateId ||
2308-
(txParams.type && ['acceleration', 'fillNonce', 'transferToken'].includes(txParams.type))
2308+
(txParams.type && ['acceleration', 'fillNonce', 'transferToken', 'transfer'].includes(txParams.type))
23092309
)
23102310
) {
23112311
throw new Error(`missing txParams`);
@@ -2316,6 +2316,28 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
23162316
if (txParams.hop && txParams.recipients && txParams.recipients.length > 1) {
23172317
throw new Error(`tx cannot be both a batch and hop transaction`);
23182318
}
2319+
2320+
if (txParams.type && ['transfer', 'transferToken'].includes(txParams.type)) {
2321+
if (txParams.recipients && txParams.recipients.length > 0) {
2322+
const recipients = txParams.recipients;
2323+
2324+
if (recipients.length !== 1) {
2325+
throw new Error(`tss transaction only supports 1 recipient but ${recipients.length} found`);
2326+
}
2327+
const txBuilder = this.getTransactionBuilder();
2328+
txBuilder.from(txPrebuild.txHex);
2329+
const tx = await txBuilder.build();
2330+
const txJson = tx.toJson();
2331+
const expectedAmount = recipients[0].amount;
2332+
if (expectedAmount !== txJson.value) {
2333+
throw new Error('the transaction amount in txPrebuild does not match the value given by client');
2334+
}
2335+
if (recipients[0].address !== txJson.to) {
2336+
throw new Error('destination address does not match with the recipient address');
2337+
}
2338+
}
2339+
}
2340+
23192341
return true;
23202342
}
23212343

modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -659,8 +659,11 @@ describe('TSS Ecdsa Utils:', async function () {
659659
transactions: [
660660
{
661661
unsignedTx: {
662-
serializedTxHex: 'TOO MANY SECRETS',
663-
signableHex: 'TOO MANY SECRETS',
662+
// hteth txid: 0xc5a7bfe6b13ceae563da0f9feaa9c4ad1c101a15366a2a488828a5dd27cb9da3
663+
serializedTxHex:
664+
'02f38242688084448b9b8084448b9b908301637894a1cfb9d51c0af191ff21c5f0f01723e056f7dc12865af3107a400080c0808080',
665+
signableHex:
666+
'02f08242688084448b9b8084448b9b908301637894a1cfb9d51c0af191ff21c5f0f01723e056f7dc12865af3107a400080c0',
664667
derivationPath: '', // Needs this when key derivation is supported
665668
},
666669
state: 'pendingSignature',
@@ -669,8 +672,11 @@ describe('TSS Ecdsa Utils:', async function () {
669672
],
670673
unsignedTxs: [
671674
{
672-
serializedTxHex: 'TOO MANY SECRETS',
673-
signableHex: 'TOO MANY SECRETS',
675+
// hteth txid: 0xc5a7bfe6b13ceae563da0f9feaa9c4ad1c101a15366a2a488828a5dd27cb9da3
676+
serializedTxHex:
677+
'02f38242688084448b9b8084448b9b908301637894a1cfb9d51c0af191ff21c5f0f01723e056f7dc12865af3107a400080c0808080',
678+
signableHex:
679+
'02f38242688084448b9b8084448b9b908301637894a1cfb9d51c0af191ff21c5f0f01723e056f7dc12865af3107a400080c0808080',
674680
derivationPath: '', // Needs this when key derivation is supported
675681
},
676682
],
@@ -933,6 +939,41 @@ describe('TSS Ecdsa Utils:', async function () {
933939
userGpgActual.should.startWith('-----BEGIN PGP PUBLIC KEY BLOCK-----');
934940
});
935941

942+
it('signTxRequest should fail with wrong recipient', async function () {
943+
await setupSignTxRequestNocks(true, userSignShare, aShare, dShare, enterpriseData);
944+
await tssUtils
945+
.signTxRequest({
946+
txRequest: txRequestId,
947+
prv: JSON.stringify({
948+
pShare: userKeyShare.pShare,
949+
bitgoNShare: bitgoKeyShare.nShares[1],
950+
backupNShare: backupKeyShare.nShares[1],
951+
}),
952+
reqId,
953+
txParams: { recipients: [{ address: '0x1234', amount: '100000000000000' }], type: 'transfer' },
954+
})
955+
.should.be.rejectedWith('destination address does not match with the recipient address');
956+
});
957+
958+
it('signTxRequest should fail with incorrect value', async function () {
959+
await setupSignTxRequestNocks(true, userSignShare, aShare, dShare, enterpriseData);
960+
await tssUtils
961+
.signTxRequest({
962+
txRequest: txRequestId,
963+
prv: JSON.stringify({
964+
pShare: userKeyShare.pShare,
965+
bitgoNShare: bitgoKeyShare.nShares[1],
966+
backupNShare: backupKeyShare.nShares[1],
967+
}),
968+
reqId,
969+
txParams: {
970+
recipients: [{ address: '0xa1cfb9d51c0af191ff21c5f0f01723e056f7dc12', amount: '1' }],
971+
type: 'transfer',
972+
},
973+
})
974+
.should.be.rejectedWith('the transaction amount in txPrebuild does not match the value given by client');
975+
});
976+
936977
it('getOfflineSignerPaillierModulus should succeed', async function () {
937978
const paillierModulus = tssUtils.getOfflineSignerPaillierModulus({
938979
prv: JSON.stringify({

modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export interface VerificationOptions {
152152
export interface VerifyTransactionOptions {
153153
txPrebuild: TransactionPrebuild;
154154
txParams: TransactionParams;
155-
wallet: Wallet;
155+
wallet: IWallet;
156156
verification?: VerificationOptions;
157157
reqId?: IRequestTracer;
158158
walletType?: 'onchain' | 'tss';

modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Key, SerializedKeyPair } from 'openpgp';
22
import { IRequestTracer } from '../../../api';
3-
import { KeychainsTriplet, ParsedTransaction } from '../../baseCoin';
3+
import { KeychainsTriplet, ParsedTransaction, TransactionParams } from '../../baseCoin';
44
import { ApiKeyShare, Keychain } from '../../keychain';
55
import { ApiVersion, Memo, WalletType } from '../../wallet';
66
import { EDDSA, GShare, Signature, SignShare } from '../../../account-lib/mpc/tss';
@@ -422,6 +422,7 @@ export type TSSParams = {
422422
txRequest: string | TxRequest; // can be either a string or TxRequest
423423
reqId: IRequestTracer;
424424
apiVersion?: ApiVersion;
425+
txParams?: TransactionParams;
425426
};
426427

427428
export type TSSParamsForMessage = TSSParams & {

modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,12 @@ export class EcdsaUtils extends BaseEcdsaUtils {
900900
assert(txRequest.transactions || txRequest.unsignedTxs, 'Unable to find transactions in txRequest');
901901
const unsignedTx =
902902
txRequest.apiVersion === 'full' ? txRequest.transactions![0].unsignedTx : txRequest.unsignedTxs[0];
903+
await this.baseCoin.verifyTransaction({
904+
txPrebuild: { txHex: unsignedTx.signableHex },
905+
txParams: params.txParams || { recipients: [] },
906+
wallet: this.wallet,
907+
walletType: this.wallet.multisigType(),
908+
});
903909
signablePayload = Buffer.from(unsignedTx.signableHex, 'hex');
904910
derivationPath = unsignedTx.derivationPath;
905911
} else if (requestType === RequestType.message) {

modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,12 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
731731
assert(txRequest.transactions || txRequest.unsignedTxs, 'Unable to find transactions in txRequest');
732732
const unsignedTx =
733733
txRequest.apiVersion === 'full' ? txRequest.transactions![0].unsignedTx : txRequest.unsignedTxs[0];
734+
await this.baseCoin.verifyTransaction({
735+
txPrebuild: { txHex: unsignedTx.signableHex },
736+
txParams: params.txParams || { recipients: [] },
737+
wallet: this.wallet,
738+
walletType: this.wallet.multisigType(),
739+
});
734740
txOrMessageToSign = unsignedTx.signableHex;
735741
derivationPath = unsignedTx.derivationPath;
736742
bufferContent = Buffer.from(txOrMessageToSign, 'hex');

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3426,6 +3426,7 @@ export class Wallet implements IWallet {
34263426
try {
34273427
return await this.tssUtils!.signTxRequest({
34283428
txRequest: params.txPrebuild.txRequestId,
3429+
txParams: params.txPrebuild.buildParams,
34293430
prv: params.prv,
34303431
reqId: params.reqId || new RequestTracer(),
34313432
apiVersion: params.apiVersion,

0 commit comments

Comments
 (0)