【钱包】Tron签名总结
👻前言
我在使用rawDataHex和privateKeyHex进行签名的时候,是可以正常签名的,但是一广播就失败,无法上链,以下是我签名的方法
/// Tron签名函数,返回 r + s + v 的 65字节签名String signTronTx(Uint8List rawData, String privateKeyHex) {// 1. SHA256 哈希final hash = sha256.convert(rawData).bytes;// 2. 私钥 hex -> bytesfinal privateKeyBytes = web3.hexToBytes(privateKeyHex);// 3. 签名final sig = web3.sign(Uint8List.fromList(hash), privateKeyBytes);// 4. 拼接 r(32) + s(32) + v(1)final r = sig.r.toBytesPadded(32);// final r = bigIntToBytes(sig.r, 32);// final s = bigIntToBytes(sig.s, 32);final s = sig.s.toBytesPadded(32);final v = Uint8List.fromList([sig.v]);final sigList = Uint8List.fromList([...r, ...s, ...v]);return hex.encode(sigList);}
后来查询了大量文档才知道,我这个是r + s + v格式的字节签名,能上链的签名是v + r + s 格式的 65 字节签名,所以导致失败。
🧠 背景知识:椭圆曲线签名(ECDSA)
在 ECDSA(用于 Tron、Ethereum 等链)中,签名的结构是:
r(32字节) + s(32字节) + v(1字节)
注意:但是这个顺序并不是所有链都一样,不同链对签名的顺序有细微要求。
✅正确格式:v + r + s
Tron 的签名顺序是:
[1 byte v] + [32 byte r] + [32 byte s] = 65 字节
✏️ 修改 signTronTx 函数
String signTronTx(Uint8List rawData, String privateKeyHex) {// 1. SHA256 哈希final hash = sha256.convert(rawData).bytes;// 2. 私钥 hex -> bytesfinal privateKeyBytes = web3.hexToBytes(privateKeyHex);// 3. 签名final sig = web3.sign(Uint8List.fromList(hash), privateKeyBytes);// 4. 拼接成 [v, r, s]final r = sig.r.toBytesPadded(32);final s = sig.s.toBytesPadded(32);final v = Uint8List.fromList([sig.v]); // 注意,这里是 1 字节// ✅ 顺序改成 v + r + sfinal sigList = Uint8List.fromList([...v, ...r, ...s]);return hex.encode(sigList);
}
🧪 为什么 Tron 使用 v + r + s?
这是 Tron 在其 Protobuf 结构 Transaction.raw_data.contract.parameter 中对签名的要求,确保节点验证签名时能正确地解析和恢复公钥。
不同链对 v 的使用方式略有不同:
链 | v的位置 | v的值 |
---|---|---|
Ethereum | 最后(r+s+v) | 27/28 或 0/1 |
Tron | 最前(v+r+s) | 27/28 |
📌 总结
项目 | 正确方式 | 错误方式 |
---|---|---|
签名顺序 | ✅ v + r + s | ❌ r + s + v |
为什么顺序重要? | Tron 验签是按这个顺序解析的 | 错序会导致签名无效 |
如何修复? | 用 Uint8List.fromList([…v, …r, …s]) 拼接 | - |
v 是否要加 27/28? | 通常要,如果是 0/1 就加 27 | 否则恢复公钥会失败 |