Overview
Tron uses the secp256k1 elliptic curve with keccak256 hashing — the same cryptographic scheme as Ethereum. Tron addresses are derived directly from the EVM address without key recovery: swap the 0x prefix for 41 and base58check-encode.
| Property | Value |
|---|
| Curve | secp256k1 |
| Root Wallet | EVM |
| Address Format | base58check (T...) |
| Hashing | Keccak-256 |
| Serialization | TronWeb SDK |
| Smallest Unit | Sun (1 TRX = 10^6 sun) |
Dependencies
Derive Address
Swap the EVM address prefix from 0x to 41 and base58check-encode:
import { TronWeb } from "tronweb";
function deriveTronAddress(evmAddress: string): string {
const hex = "41" + evmAddress.slice(2).toLowerCase();
return TronWeb.address.fromHex(hex);
}
This is the simplest derivation of all secp256k1 chains — no key recovery needed. The last 20 bytes of keccak256(uncompressed_pubkey) are identical for both EVM and Tron.
Sign a Message
import { keccak256, toUtf8Bytes, concat } from "tronweb/utils";
async function signTronMessage(message: string): Promise<string> {
const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "EVM")!;
const messageBytes = toUtf8Bytes(message);
const prefix = toUtf8Bytes("\x19TRON Signed Message:\n");
const lengthStr = toUtf8Bytes(String(messageBytes.length));
const messageDigest = keccak256(concat([prefix, lengthStr, messageBytes]));
const signature = await dynamicClient.wallets.waas.signRawMessage(wallet.id, {
accountAddress: wallet.address,
message: messageDigest.replace(/^0x/, ""),
});
return signature;
}
Sign a Transaction
Build the unsigned transaction with TronWeb, then sign the transaction ID and broadcast:
import { TronWeb } from "tronweb";
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
async function sendTrxTransfer(
to: string,
amount: number,
tronAddress: string,
): Promise<string> {
const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "EVM")!;
const amountSun = BigInt(Math.round(amount * 1_000_000));
const unsignedTx = await tw.transactionBuilder.sendTrx(
to,
Number(amountSun),
tronAddress,
);
// The transaction ID is the hash to sign
const txId = unsignedTx.txID.replace(/^0x/, "");
const signature = await dynamicClient.wallets.waas.signRawMessage(wallet.id, {
accountAddress: wallet.address,
message: txId,
});
// Normalize the v-byte to Tron's expected range (27 or 28)
const sigBytes = hexToBytes(signature.replace(/^0x/, ""));
const sig65 = new Uint8Array(65);
sig65.set(sigBytes.slice(0, 64));
let v = sigBytes[64];
if (v < 27) v += 27;
sig65[64] = v;
const signedTx = { ...unsignedTx, signature: [bytesToHex(sig65)] };
const result = await tw.trx.broadcast(signedTx);
if (!result.result) throw new Error(`Broadcast failed: ${result.message}`);
return result.txid;
}
Tron signing is simpler than other chains because TronWeb handles transaction construction and serialization. You only need to sign the transaction ID (a SHA-256 hash) using the EVM wallet’s raw signing capability.
Verify a Signature
import { TronWeb } from "tronweb";
async function verifyTronSignature(message: string, signature: string): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
return tw.trx.verifyMessageV2(message, signature);
}
Check Balance
async function getTronBalance(tronAddress: string): Promise<string> {
const tw = new TronWeb({ fullHost: "https://nile.trongrid.io" });
const sunBalance = await tw.trx.getBalance(tronAddress);
return (Number(sunBalance) / 1_000_000).toString();
}
For full implementation details including TRC20 transfers see the JavaScript Tron guide. The only difference in React Native is the signing call.