Skip to content

fix(sdk-coin-trx): validate TRC20 recipient and amount for TSS transctions#8767

Open
bhuvanr159 wants to merge 1 commit into
masterfrom
CHALO-448-fixes
Open

fix(sdk-coin-trx): validate TRC20 recipient and amount for TSS transctions#8767
bhuvanr159 wants to merge 1 commit into
masterfrom
CHALO-448-fixes

Conversation

@bhuvanr159
Copy link
Copy Markdown
Contributor

@bhuvanr159 bhuvanr159 commented May 13, 2026

Summary

TRX TSS token verification was unconditionally returning true before validating any recipient or amount for TRC20 transfers. This meant a compromised BitGo platform or substituted prebuild could
redirect a TRC20 transfer (e.g. USDT-TRON) and the client SDK would still sign it without detecting the mismatch.

There are two bugs, one compounding the other:

Bug 1 — trxToken.ts:105-107 (primary)

TrxToken.verifyTransaction short-circuited to return true for any TSS wallet, skipping all recipient and amount validation. The comment justified this by claiming "the TSS signing protocol itself
provides cryptographic guarantees" — but TSS only guarantees the math (no single party can sign alone). It does not guarantee that what's being signed matches the user's intent. Intent verification is a
completely separate layer and is exactly what verifyTransaction exists to do.

Bug 2 — trx.ts:414 (defense-in-depth)

Even if Bug 1 didn't exist, the parent Trx.verifyTransaction correctly validates native TRX Transfer contracts but falls through with return true for TriggerSmartContract — the protobuf type
used for all TRC20 transfers. So there was no safety net.

What this PR does

  • Removes the TSS shortcut in TrxToken.verifyTransaction and replaces it with real validation: decodes the TriggerSmartContract protobuf, extracts the ABI-encoded transfer(address, uint256)
    call data using the existing Utils.decodeDataParams utility, converts the decoded address to base58, and compares both destination and amount against txParams.recipients before signing. Fails closed
    on any decode failure or mismatch.

  • Adds a hard failure in Trx.verifyTransaction for TriggerSmartContract in TSS mode. Native TRX should never receive a TRC20 transaction — if it does, we now throw instead of silently returning
    true. Other non-Transfer contract types (freeze, vote, delegate, etc.) are legitimate native TRX operations and continue to pass through.

  • Adds unit tests for the TrxToken TSS verification path covering: valid transfer, JSON txHex format (prebuildAndSignTransaction path), amount mismatch, address mismatch, empty recipients, wrong
    contract type, and the existing non-TSS builder path. Also adds a test asserting native Trx now throws on TriggerSmartContract in TSS mode.

No new utilities needed

All the building blocks already existed in the codebase:

  • Utils.decodeTransaction(rawDataHex) — used in trx.ts to decode the protobuf
  • Utils.decodeDataParams(['address', 'uint256'], data) — used in transaction.ts to decode TRC20 ABI data
  • Utils.getBase58AddressFromHex(hex) — used in transaction.ts to convert the decoded address

Impact

All TSS Tron TRC20 transactions, including high-volume assets such as USDT-TRON, were affected. This fix ensures recipient and amount are verified against client-supplied txParams before the signing
protocol proceeds.

Ticket: CHALO-349
Parent: WAL-1449

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 13, 2026

CHALO-448

@bhuvanr159 bhuvanr159 marked this pull request as ready for review May 13, 2026 15:43
@bhuvanr159 bhuvanr159 requested a review from a team as a code owner May 13, 2026 15:43
@at31416
Copy link
Copy Markdown
Contributor

at31416 commented May 14, 2026

@claude review

const contractData = Buffer.from(triggerContract.parameter.value.data, 'base64').toString('hex');

const recipients = txParams.recipients || (txPrebuild.txInfo as TronTxInfo).recipients;
if (!recipients || recipients.length !== 1) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will a trc20 transaction initiated by bitgo will always have a single recipient? if at all recipient is present.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a TRC20 transfer on Tron will always have exactly one recipient per transaction. The ABI signature transfer(address, uint256) only accepts one destination address and one amount, it's enforced by the protocol itself.

Comment thread modules/sdk-coin-trx/src/trxToken.ts Outdated
throw new Error('missing or invalid required property recipients');
}

let decodedParams: any[];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the usage of any be avoided?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants