Signature validation

How TRON validates transaction signatures using ECDSA over secp256k1, and how multi-signature accounts use weight thresholds.

Every transaction on TRON includes one or more signatures. Validating a signature confirms that the transaction was authorized by the holder of the private key (or, for accounts with active permissions, by enough authorized parties). The verification logic is:

  1. Recompute SHA-256(raw_data) — the message hash
  2. Recover the public key from the signature using ECDSA over secp256k1
  3. Derive the address from the public key
  4. Check that the derived address has the right permission and weight to authorize this transaction

This page walks through each step and shows how multi-signature accounts aggregate signature weights against a configured threshold.

📘

Prerequisites


The signed message

The message that gets signed is SHA-256(raw_data). The raw_data field contains the contract body, TAPOS reference, expiration, fee_limit, and timestamp — see Transactions for the full layout.

This means a signature commits to all the data in raw_data but not to the signatures themselves (so multi-sig signatures can be added one at a time without invalidating each other).


ECDSA verification

TRON uses ECDSA over the secp256k1 curve — the same scheme as Bitcoin and Ethereum. The canonical signature is 65 bytes in r || s || v order:

BytesContent
0–31r value (32 bytes)
32–63s value (32 bytes)
64recovery byte v (1 byte, value 0 or 1)

In GreatVoyage-v4.8.2, broadcast APIs and P2P admission accept signatures from 65 through 68 bytes for compatibility with historical data carrying trailing bytes. Before validation, wallet/getsignweight and wallet/getapprovedlist truncate signatures longer than 65 bytes to their first 65 bytes. Newly constructed transactions should still use the canonical 65-byte form and must not rely on compatibility padding.

⚠️

Wire byte order is r || s || v

The recovery byte comes last. Some Ethereum tooling expresses signatures as v || r || s (with v + 27 as the leading byte); when porting code, convert to r || s || v before writing into transaction.signature[]. Reversing the order produces an invalid signature that recovers the wrong address.

Verification uses the recoverable form: given the message hash and the signature, the verifier recovers the public key directly, derives the corresponding address, and compares against the expected signer.

📘

Signature recovery byte

The recovery byte v lets the verifier recover the public key from the signature alone — no need for the public key to be transmitted alongside the transaction. This saves bytes and makes signature aggregation cheaper.


Single-signature validation

For an account with default permissions (a regular EOA), the validator:

  1. Recovers the address from the single signature in the transaction
  2. Confirms the recovered address matches the owner_address in raw_data.contract

If they match, the signature is valid and the transaction proceeds. If they don't, the transaction is rejected.


Multi-signature validation

An account whose Permission has been configured for multi-party control requires multiple signatures. The validator:

  1. Recovers an address from each signature
  2. Looks up each address in the relevant permission's keys list
  3. Sums the weights
  4. Compares the sum to the permission's threshold

The transaction is authorized when the sum of recovered weights is ≥ threshold.

⚠️

Multi-sig validation is strict.

Any of the following conditions rejects the entire transaction with PermissionException:

  • Too many signaturessignature[] size exceeds the permission's keys count
  • Unknown signer — a signature recovers to an address that is not in the permission's keys list (it does not contribute zero weight — it rejects the transaction outright)
  • Duplicate signer — the same address appears twice in signature[]

All three conditions abort validation before the threshold check runs.

Worked example

A 2-of-3 owner permission on an account:

threshold = 2
keys:
  - address: TKey1...AA, weight: 1
  - address: TKey2...BB, weight: 1
  - address: TKey3...CC, weight: 1
  • Signed by Key1 + Key3 (sum = 2 ≥ threshold) → authorized
  • Signed by Key1 alone (sum = 1 < threshold) → rejected with insufficient weight
  • Signed by Key1 + an unknown key → rejected with PermissionException because the unknown signature is not in the keys list (not because the sum is too low)
  • Signed by Key1 + Key1 (duplicate) → rejected with "has signed twice"
  • Four signatures sent for a 3-key permission → rejected with "Signature count is N more than key counts of permission"

For weighted permissions where one party has more authority:

threshold = 3
keys:
  - address: TFounder...A, weight: 2
  - address: TCofnd1...B, weight: 1
  - address: TCofnd2...C, weight: 1

A transaction needs the founder plus at least one cofounder (2 + 1 = 3) — or both cofounders working together with the founder absent (1 + 1 = 2, which fails). For setup details, see Account Permission Management.


Permission selection

Each transaction declares which permission level it is signed under via Permission_id in raw_data.contract:

Permission_idPermission
0Owner permission (default if omitted)
1Witness permission — reserved for Super Representative block production; cannot authorize normal contract transactions
2–9Active permissions (up to 8 per account, per-operation roles)

The validator looks up the permission by ID, then runs the multi-signature check above. Each permission has its own threshold and keys list. For any Permission_id != 0, the permission's type field must equal Active — supplying Permission_id = 1 (Witness) on a normal contract call rejects the transaction with "Permission type is wrong!". Active permissions additionally carry an operations bitmap restricting which contract types they may execute; a contract type not authorized by the bitmap is rejected with "Permission denied!".


Cancelling a transaction

TRON has no native transaction cancellation or replacement mechanism. A higher fee or another transaction with the same TAPOS reference cannot supersede the original transaction. For a multi-signature transaction that has not been broadcast and remains under the signers' control, stop collecting or distributing signatures and let it expire. Once a signed transaction has been shared externally or broadcast, it cannot be revoked; monitor the original txID until it is included or expires. The default validity period is about 60 seconds and can be set up to approximately 24 hours from the chain head.


APIs

APIDescription
wallet/getsignweightInspect signature weight on a partially-signed transaction
wallet/getapprovedlistList addresses that have signed
wallet/broadcasttransactionSubmit a fully-signed transaction

Related resources