跳至主要内容

TRON(tron-sign-request)

ur:tron-sign-requestur:tron-signatureraw_data protobufTIP-191

BitTool Signer 使用公開的 Keystone TRON UR 類型。請勿傳送 TronLink 或 TokenPocket 的 action JSON、URL scheme 或舊版的 keystone-sign-request;這些都會被拒絕。

tron-sign-request​

鍵欄位類型內容
1request-id37(bytes)正好 16 個隨機位元組
2sign-databytesdata-type 1:raw_data protobuf 位元組 · data-type 2:訊息位元組
3data-typeuint1 交易 · 2 個人訊息
4derivation-path304(crypto-keypath)m/44'/195'/0'/0/i,並附上主指紋
5addressbytes(21)0x41 ‖ 20 位元組帳戶 ID:經 Base58Check 解碼後的地址,而非文字
6origintext向使用者顯示

data-type 1:交易​

sign-data 是交易中 以 protobuf 序列化的 raw_data:也就是 TronWeb 或 /wallet/createtransaction 與 /wallet/triggersmartcontract API 所傳回之 raw_data_hex 的位元組。它不是完整的 Transaction 訊息(該訊息包住 raw_data 與 signature),不是 hex 文字,也不是 txID。

簽名器會計算 txID = SHA-256(raw_data) 並簽署這個摘要。規則如下:

規則細節
單一合約raw_data.contract 中恰好只有一個項目。
時間欄位ref_block_bytes(2 位元組)、ref_block_hash(8 位元組)、timestamp > 0,且 expiration > timestamp。
Fee limitTriggerSmartContract 必填(> 0)。過高的 fee limit 會顯示警告。
權限Permission_id 為 0(owner)或某個 active 權限 ID;1(witness)會被拒絕。
嚴格的 protobuf未知欄位、重複欄位與非 canonical 的 varint 都會被拒絕。
TRC-10不支援,包括合約呼叫中的 call_token_value。

支援的合約:

類型合約審核內容
1TransferContractTRX 金額與收款人
31TriggerSmartContract會解碼 TRC-20、TRC-721 與 TRC-1155 的轉帳與授權(例如 USDT transfer);其他呼叫則以原始 calldata 顯示,並需要使用者明確同意
46AccountPermissionUpdateContractOwner/active 權限、金鑰、權重、門檻值;一律為高風險
54–59Stake 2.0 與資源:FreezeBalanceV2、UnfreezeBalanceV2、WithdrawExpireUnfreeze、DelegateResource、UnDelegateResource、CancelAllUnfreezeV2金額、資源類型、接收者、鎖定

data-type 2:訊息​

sign-data 是訊息位元組(1–8,192)。簽名器遵循 TIP-191,也就是 TronWeb signMessageV2 所使用的方案:

digest = Keccak-256( 0x19 ‖ "TRON Signed Message:\n" ‖ decimal(length of message) ‖ message )

文字會以文字顯示;其他位元組則以 hex 顯示、標示為不透明內容,並需要使用者明確同意。

tron-signature​

tron-signature = {
1: 37(request-id bytes),
2: signature ; 65 bytes: r (32) ‖ s (32) ‖ recovery id (0 or 1)
}

復原位元組是 0 或 1,而不是 27/28。對於交易,請將這 65 個位元組以 hex 形式附加到交易的 signature 陣列,保持 raw_data 與 raw_data_hex 不變,並透過 /wallet/broadcasttransaction(TronWeb:trx.sendRawTransaction)廣播。

對於訊息,許多 TRON 工具(TronWeb 的 verifyMessageV2、透過 WalletConnect 連線的 dApp)預期最後一個位元組為 27/28:將簽名轉交出去時請加上 27。不要變更交易簽名的格式。

程式碼​

TRON 沒有廣泛使用的 UR registry 函式庫,因此這個範例使用 cborg 直接寫出 CBOR,並搭配 keypath 輔助函式:

examples/tron.mjs
import { randomBytes } from 'node:crypto';
import { UR, UREncoder, URDecoder } from '@ngraveio/bc-ur';
import { base58check } from '@scure/base';
import { sha256 } from '@noble/hashes/sha2.js';
import { encode, decode, Tagged } from 'cborg';
import { SigningKey, computeAddress } from 'ethers';
import { keypath } from './keypath.mjs';

const b58 = base58check(sha256);

// `tx` is an unsigned transaction from TronWeb / the TRON HTTP API: { txID, raw_data, raw_data_hex }
export function buildTronRequest(tx, { path, masterFingerprint, address }) {
const requestId = randomBytes(16);
const cbor = encode(new Map([
[1, new Tagged(37, requestId)], // request-id (uuid)
[2, Buffer.from(tx.raw_data_hex, 'hex')], // raw_data protobuf bytes
[3, 1], // data-type: 1 transaction, 2 message
[4, keypath(path, masterFingerprint)], // "m/44'/195'/0'/0/0"
[5, b58.decode(address)], // 21 bytes: 0x41 ‖ 20-byte account ID
[6, 'BitTool Wallet'], // origin
]));
const encoder = new UREncoder(new UR(Buffer.from(cbor), 'tron-sign-request'), 400);
const frames = [];
for (let i = 0; i < encoder.fragmentsLength; i++) frames.push(encoder.nextPart().toUpperCase());
return { requestId, frames };
}
examples/tron.mjs
const tags = { 37: (decode) => decode() }; // tag 37 (uuid): keep the raw bytes

export function attachTronSignature(frames, tx, { requestId, address }) {
const decoder = new URDecoder();
for (const frame of frames) decoder.receivePart(frame);
if (!decoder.isSuccess()) throw new Error('Incomplete or invalid QR');
const ur = decoder.resultUR();
if (ur.type !== 'tron-signature') throw new Error(`Expected tron-signature, got ${ur.type}`);
const response = decode(ur.cbor, { useMaps: true, tags, strict: true });
if (!Buffer.from(response.get(1)).equals(requestId)) throw new Error('Signature is for another request');

const sig = Buffer.from(response.get(2)); // r (32) ‖ s (32) ‖ recovery id (0 or 1)
if (sig.length !== 65 || sig[64] > 1) throw new Error('Unexpected signature format');
const txID = Buffer.from(sha256(Buffer.from(tx.raw_data_hex, 'hex'))).toString('hex');
if (txID !== tx.txID) throw new Error('txID does not match raw_data');

const evmStyle = { r: '0x' + sig.subarray(0, 32).toString('hex'), s: '0x' + sig.subarray(32, 64).toString('hex'), v: 27 + sig[64] };
const recovered = computeAddress(SigningKey.recoverPublicKey('0x' + txID, evmStyle)); // 0x + 20 bytes
if (b58.encode(Buffer.from('41' + recovered.slice(2), 'hex')) !== address) throw new Error('Signed by an unexpected key');

return { ...tx, signature: [...(tx.signature ?? []), sig.toString('hex')] }; // broadcast with sendRawTransaction
}

測試向量​

全部來自測試助記詞的 m/44'/195'/0'/0/0(TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH)。參考區塊、時間戳記與 txID 均為合成資料;這些交易從未被廣播。

傳送 1.5 TRX(1,500,000 sun)到 TJRabPrwbZy45sbavfcjinPJC18kjpRTv8。

未簽名的交易(來自你的節點或 TronWeb)
{
"txID": "b396c11e25482bc348d39e620000ef16290d0d17b7649cb94fee63d0203695eb",
"raw_data": {
"contract": [
{
"parameter": {
"value": {
"amount": 1500000,
"owner_address": "41c8599111f29c1e1e061265b4af93ea1f274ad78a",
"to_address": "415cbdd86a2fa8dc4bddd8a8f69dba48572eec07fb"
},
"type_url": "type.googleapis.com/protocol.TransferContract"
},
"type": "TransferContract"
}
],
"ref_block_bytes": "a1b2",
"ref_block_hash": "0123456789abcdef",
"expiration": 1792310460000,
"timestamp": 1792310400000
},
"raw_data_hex": "0a02a1b222080123456789abcdef40e0fc9cf094345a67080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a1541c8599111f29c1e1e061265b4af93ea1f274ad78a1215415cbdd86a2fa8dc4bddd8a8f69dba48572eec07fb18e0c65b7080a899f09434"
}

請求(你的錢包 → BitTool Signer)

tron-sign-request2 幀
UR:TRON-SIGN-REQUEST/1-2/LPADAOCSURCYBDFNCLLAHDJOOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTAYAOHDLPBKAOOYPRCPAYADCNFEIOLDPYSNWSFZVTZTNSWTMWEEHTIOAYADBGIABKDPJYKKJOIHDMIOJLJLIOJZIHHSJOINJKDMIAJLJNDLJOJPJLJYJLIAJLJZDMGHJPHSJTJKIYIHJPFXJLJTJYJPHSIAJYBGEYBKBZFPSPHKMEBYWZNSCKCKAMKIYKMUHK
1 / 2
CBOR 欄位
{
"1 request-id": {
"uuid (tag 37)": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a08"
},
"2 sign-data": "0a02a1b222080123456789abcdef40e0fc9cf094345a67080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a1541c8599111f29c1e1e061265b4af93ea1f274ad78a1215415cbdd86a2fa8dc4bddd8a8f69dba48572eec07fb18e0c65b7080a899f09434",
"3 data-type": 1,
"4 derivation-path": {
"crypto-keypath (tag 304)": {
"1 components": [
44,
true,
195,
true,
0,
true,
0,
false,
0,
false
],
"2 source-fingerprint": 1942346250
}
},
"5 address": "41c8599111f29c1e1e061265b4af93ea1f274ad78a",
"6 origin": "BitTool Wallet"
}
顯示 QR 文字與原始 CBOR
QR 文字 · ur:tron-sign-request
UR:TRON-SIGN-REQUEST/1-2/LPADAOCSURCYBDFNCLLAHDJOOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTAYAOHDLPBKAOOYPRCPAYADCNFEIOLDPYSNWSFZVTZTNSWTMWEEHTIOAYADBGIABKDPJYKKJOIHDMIOJLJLIOJZIHHSJOINJKDMIAJLJNDLJOJPJLJYJLIAJLJZDMGHJPHSJTJKIYIHJPFXJLJTJYJPHSIAJYBGEYBKBZFPSPHKMEBYWZNSCKCKAMKIYKMUHK
UR:TRON-SIGN-REQUEST/2-2/LPAOAOCSURCYBDFNCLLAHDJOBGIHQZPEMUWDCTDIGETSLEBGBZFPHHRYTPIMDLPDUOGRUTTPPDYNNTRDFDHGDMWPATZOCSVTSWHPJOLAPDNLWTMWEEAXADAATAADDYOEADLECSDWYKCSSRYKAEYKAEWKAEWKAOCYJKSKTNBKAHGOFPSPHKMEBYWZNSCKCKAMBGIHQZPEMUWDCTDIGETSLEAMJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAECEGYGWVW
UR CBOR(hex)
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a080258850a02a1b222080123456789abcdef40e0fc9cf094345a67080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a1541c8599111f29c1e1e061265b4af93ea1f274ad78a1215415cbdd86a2fa8dc4bddd8a8f69dba48572eec07fb18e0c65b7080a899f09434030104d90130a2018a182cf518c3f500f500f400f4021a73c5da0a055541c8599111f29c1e1e061265b4af93ea1f274ad78a066e426974546f6f6c2057616c6c6574

回應(BitTool Signer → 你的錢包)

解碼後的回應 · ur:tron-signature
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a08"
},
"signature": "32c82fe12ddef67b967c836692f7a729003481e6b0f070b1ac6ee7742ac27e18611174e6a54efb7f5f5e14adfbb9118bb137f064881777316ed593ea5da4a25d01"
}
顯示 QR 文字與原始 CBOR
QR 文字 · ur:tron-signature
UR:TRON-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTAYAOHDFPEYSPDLVYDPUEYNKGMTKELSIYMOYLOSDTAEEELYVAPFWTJOPAPSJTVDJYDRSAKBCSHSBYJYVAONGLZOLBHEHYBBPMZORHBYLUPAEMWTIELOCHKTEHJTTLMUWDHLOXOEHLADEEFEIELE
UR CBOR(hex)
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0802584132c82fe12ddef67b967c836692f7a729003481e6b0f070b1ac6ee7742ac27e18611174e6a54efb7f5f5e14adfbb9118bb137f064881777316ed593ea5da4a25d01

由你的錢包組裝

signedTransaction
{
"txID": "b396c11e25482bc348d39e620000ef16290d0d17b7649cb94fee63d0203695eb",
"raw_data": {
"contract": [
{
"parameter": {
"value": {
"amount": 1500000,
"owner_address": "41c8599111f29c1e1e061265b4af93ea1f274ad78a",
"to_address": "415cbdd86a2fa8dc4bddd8a8f69dba48572eec07fb"
},
"type_url": "type.googleapis.com/protocol.TransferContract"
},
"type": "TransferContract"
}
],
"ref_block_bytes": "a1b2",
"ref_block_hash": "0123456789abcdef",
"expiration": 1792310460000,
"timestamp": 1792310400000
},
"raw_data_hex": "0a02a1b222080123456789abcdef40e0fc9cf094345a67080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a1541c8599111f29c1e1e061265b4af93ea1f274ad78a1215415cbdd86a2fa8dc4bddd8a8f69dba48572eec07fb18e0c65b7080a899f09434",
"signature": [
"32c82fe12ddef67b967c836692f7a729003481e6b0f070b1ac6ee7742ac27e18611174e6a54efb7f5f5e14adfbb9118bb137f064881777316ed593ea5da4a25d01"
]
}

多重簽名帳戶​

對於具有多重簽名權限的帳戶,每位成員分別簽名:

  • 在鍵 4 與 5 中放入 成員 的路徑與地址。owner_address 中的帳戶可能是另一個地址,而 BitTool Signer 絕不會只因為某個地址以 owner 或權限成員的身分出現,就選它作為簽名者。
  • 將合約中的 Permission_id 設為所使用的權限。
  • BitTool Signer 只看得到 raw_data。你的錢包要保存已收集的簽名、加入每個新簽名、移除重複項,並在廣播前檢查鏈上權限(成員、權重、門檻值、操作)。簽名器無法在離線狀態下驗證目前的鏈上權限。