A minimal demonstration of TRON message signing and verification using the signMessageV2 API and a simple verification smart contract built with TIP-191 (“TRON Signed Message”).
This project shows how to:
- Sign messages using TronLink / TronWeb (
signMessageV2) - Verify the signature off-chain
- Verify the signature on-chain via Solidity contract
TRON-SIG-VERIFY-LITE/
├─ contracts/
│ ├─ Migrations.sol
│ └─ TronSigVerifierLite.sol # Core signature verification contract
├─ migrations/
│ ├─ 1_initial_migration.js
│ └─ 2_deploy_tron_sig_verifier_lite.js
├─ web/
│ └─ index.html # Frontend demo with TronLink
├─ tronbox.js # Network configuration for TronBox
├─ package.json # dotenv dependency for TronBox
├─ .env.sample # Template for private key and node URLs
└─ README.md # This file
This project provides a self-contained reference for developers who want to verify messages signed with signMessageV2 in TRON wallets such as TronLink.
The contract supports:
- HEX-as-text signatures —
"0x" + 64 hexstrings (the most compatible format) - Bytes32 signatures — for raw binary signing (developer tools)
Both methods implement TRON’s standard prefix used for personal-style messages (\x19TRON Signed Message:\n...).
| Function | Type | Description |
|---|---|---|
recoverBytes32(bytes32, bytes) |
pure | Recover signer from a raw bytes32 message |
verifyBytes32(bytes32, bytes, address) |
pure | Verify signature matches expected signer |
recoverHexText(bytes32, bytes) |
pure | Recover signer from “0x” + 64 hex text |
verifyHexText(bytes32, bytes, address) |
pure | Verify hex-text signature matches signer |
toHexString(bytes32) |
pure | Convert bytes32 → lowercase “0x” + 64 string |
- Only uses pure and view functions — safe for constant calls (
.call()), no Energy cost. - Uses
ecrecoverdirectly for signer derivation.
The web page demonstrates the full flow:
- Connects to TronLink
- Generates
keccak256("demo-payload") - Signs it as HEX text using
signMessageV2 - Verifies the signature off-chain
- Verifies it on-chain using
verifyHexText(...)
- Deploy the contract (see below).
- Run a simple web server:
npx http-server web -p 8080 # or python3 -m http.server --directory web 8080 - Open http://localhost:8080 in Chrome/Brave.
- Unlock TronLink and connect.
- Paste your deployed contract T-address.
- Click “Sign keccak256('demo-payload')”.
Connected: TNd2F... | node: https://nile.trongrid.io
hexText: 0x348c...d5a3
Signature: 0x3b8f...a07b
Recovered (off-chain): TNd2F...
verifyHexText (on-chain) => true
signMessageV2 applies the TRON prefix:
"\x19TRON Signed Message:\n66" + "0x" + 64 hex chars
Then hashes the entire string using keccak256 before signing.
The contract reproduces this process:
bytes32 digest = keccak256(
abi.encodePacked("\x19TRON Signed Message:\n66", hexText)
);
address signer = ecrecover(digest, v, r, s);When calling verifyHexText(...) or verifyBytes32(...) with .call(),
no transaction is broadcast → no Energy / no TRX spent.
npm install
cp .env.sample .env
# Edit .env with your private keys and full node URLsExample .env file:
PRIVATE_KEY_NILE=your_nile_private_key_here
FULL_NODE_NILE=https://nile.trongrid.io
PRIVATE_KEY_DEVELOPMENT=your_local_private_key_here
FULL_NODE_DEVELOPMENT=http://127.0.0.1:9090
tronbox compile
tronbox migrate --network nile
# or
tronbox migrate --network developmentThe deployed contract address will appear in your console.
const hexText = tronWeb.utils.ethersUtils.keccak256(
tronWeb.utils.ethersUtils.toUtf8Bytes('demo-payload')
);
const sig = await tronWeb.trx.signMessageV2(hexText);
const who = await tronWeb.trx.verifyMessageV2(hexText, sig);
console.log('Recovered off-chain:', who);
const contract = await tronWeb.contract().at('<T-address>');
const ok = await contract.verifyHexText(hexText, sig, tronWeb.defaultAddress.hex).call();
console.log('On-chain verified:', ok);| Package | Description |
|---|---|
dotenv |
Loads environment variables for TronBox |
TronWeb |
Injected by TronLink; used in the web demo |
MIT License © 2025