From 4a4f74798f430ebe15b1bde1eb930642be17e276 Mon Sep 17 00:00:00 2001 From: Bhuvan R Date: Tue, 7 Apr 2026 14:14:47 +0530 Subject: [PATCH] fix: handle serializedTxHex format in TSS verifyTransaction TICKET: CHALO-380 --- modules/sdk-coin-trx/src/trx.ts | 19 ++++- .../test/unit/verifyTransaction.ts | 73 +++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/modules/sdk-coin-trx/src/trx.ts b/modules/sdk-coin-trx/src/trx.ts index 7096e3836c..08c9b5f371 100644 --- a/modules/sdk-coin-trx/src/trx.ts +++ b/modules/sdk-coin-trx/src/trx.ts @@ -379,10 +379,21 @@ export class Trx extends BaseCoin { } if (walletType === 'tss') { - // For TSS wallets, txHex is the signableHex (raw_data_hex protobuf bytes), - // not a full transaction JSON. Decode it directly via protobuf. - // Note: decodeTransaction already validates exactly 1 contract exists. - const decodedTx = Utils.decodeTransaction(txPrebuild.txHex); + // For TSS wallets, verifyTransaction is called from two places: + // 1. prebuildAndSignTransaction (wallet.ts) — txHex is serializedTxHex, a full JSON string + // containing { txID, raw_data, raw_data_hex }. + // 2. ECDSA signing flow (ecdsa.ts) — txHex is signableHex, the raw protobuf bytes (raw_data_hex). + // We need to extract the raw_data_hex in case 1 before decoding. + let rawDataHex: string; + try { + // serializedTxHex: full JSON string — extract the raw_data_hex field + rawDataHex = JSON.parse(txPrebuild.txHex).raw_data_hex; + } catch { + // signableHex: already raw protobuf hex (raw_data_hex) + console.debug(`Could not parse txHex as JSON for coin ${this.getChain()}, using txHex directly`); + rawDataHex = txPrebuild.txHex; + } + const decodedTx = Utils.decodeTransaction(rawDataHex); // decodedTx uses a numeric enum for contract type (from protobuf decoding), // unlike the multisig path which checks the string 'TransferContract' from node JSON. diff --git a/modules/sdk-coin-trx/test/unit/verifyTransaction.ts b/modules/sdk-coin-trx/test/unit/verifyTransaction.ts index c25becdacf..188ce6553a 100644 --- a/modules/sdk-coin-trx/test/unit/verifyTransaction.ts +++ b/modules/sdk-coin-trx/test/unit/verifyTransaction.ts @@ -574,5 +574,78 @@ describe('TRON Verify Transaction:', function () { message: 'missing txHex in txPrebuild', }); }); + + describe('serializedTxHex (JSON string) path', () => { + // prebuildAndSignTransaction passes txHex as serializedTxHex — a full JSON string + // containing { txID, raw_data, raw_data_hex }. The TSS branch must handle this format + // by extracting raw_data_hex before protobuf decoding. + + it('should validate TSS TransferContract when txHex is a JSON string (serializedTxHex)', async function () { + const ownerHex = '4173a5993cd182ae152adad8203163f780c65a8aa5'; + const toHex = '41d6cd6a2c0ff35a319e6abb5b9503ba0278679882'; + const amount = 1000000; + + const rawDataHex = buildTssTransferTxHex({ ownerAddress: ownerHex, toAddress: toHex, amount }); + const txID = createHash('sha256').update(Buffer.from(rawDataHex, 'hex')).digest('hex'); + + // Simulate the serializedTxHex format from BitGo API (JSON string with txID + raw_data_hex) + const serializedTxHex = JSON.stringify({ + txID, + raw_data_hex: rawDataHex, + raw_data: {}, + }); + + const params = { + txParams: { + recipients: [ + { + address: Utils.getBase58AddressFromHex(toHex), + amount: amount.toString(), + }, + ], + }, + txPrebuild: { + txHex: serializedTxHex, + }, + wallet: {}, + walletType: 'tss', + }; + + const result = await basecoin.verifyTransaction(params); + assert.strictEqual(result, true); + }); + + it('should fail when amount mismatches with JSON txHex', async function () { + const ownerHex = '4173a5993cd182ae152adad8203163f780c65a8aa5'; + const toHex = '41d6cd6a2c0ff35a319e6abb5b9503ba0278679882'; + + const rawDataHex = buildTssTransferTxHex({ ownerAddress: ownerHex, toAddress: toHex, amount: 2000000 }); + const serializedTxHex = JSON.stringify({ + txID: createHash('sha256').update(Buffer.from(rawDataHex, 'hex')).digest('hex'), + raw_data_hex: rawDataHex, + raw_data: {}, + }); + + const params = { + txParams: { + recipients: [ + { + address: Utils.getBase58AddressFromHex(toHex), + amount: '1000000', // mismatch + }, + ], + }, + txPrebuild: { + txHex: serializedTxHex, + }, + wallet: {}, + walletType: 'tss', + }; + + await assert.rejects(basecoin.verifyTransaction(params), { + message: 'transaction amount in txPrebuild does not match the value given by client', + }); + }); + }); }); });