TON Raw Tx Breakdown
1. TON 交易
在 TON 区块链中,通过 RPC sendBocReturnHash(或者 sendBoc)可以把签名后的 Tx 提交到链上。
1.1. Tx 解析实例 1(V4R2 钱包原生币转帐)
下面以 TON 测试网上的 Tx _6N7CAGyVt9UO_c10dgJHsS6x6SYey7kRJtU32DXGsg= 为例介绍一下这个 Tx 的细节。
这个交易的功能是测试网上 V4R2 钱包进行原生币 TON 的转帐:
0QCM8Tds9jQwLbxt-nASe4v7PRwOirxczwE1Yni1TdMYNHij (V4R2 wallet) --- 0.123456 TON --> 0QCc6ls--YZh8WpSr6m1B9kX4EsNfmE3xDGJ93xxaRdOxAhM
这个 Tx 是通过 RPC sendBocReturnHash 提交上链的(注:这个 RPC 返回的是 Message Hash V8Y4wRJOdKphKnSjkBcWYTbIMTTcPrOq/1VA0zH1o4c=,不是 Tx Hash),提交 Tx 时的具体参数如下:
$ curl https://testnet.toncenter.com/api/v2/sendBocReturnHash -X POST -H "Content-Type: application/json" -d '{ "boc": "te6cckEBAgEAswAB4YgBGeJu2exoYFt42/TgJPcX9no4HRV4uZ4CasTxapumMGgC7o4rP3HdzxLAj0eioBQ5O3hk4wCk884JPnEN2UBWdILxynlD6bzD7EctVOl8OqSn9oa3tqtRlpJpS7itpE54CU1NGLsyjn4gAAAAGAAcAQB6QgBOdS2ffMMw+LUpV9Tag+yL8CWGvzCb4hjE+744tIunYiA63lAAAAAAAAAAAAAAAAAAAAAAAABIZWxsb86+YLo=" }' {"ok":true,"result":{"@type":"raw.extMessageInfo","hash":"V8Y4wRJOdKphKnSjkBcWYTbIMTTcPrOq/1VA0zH1o4c=","@extra":"1716637630.853718:6:0.03332699648976334"}}
上面命令中提到的 Base64 编码的 Tx 数据可以分解为下面这种更清晰的表达方式:
magic prefix | b5ee9c72 | 魔法数字,参考 https://docs.ton.org/tblkch.pdf 5.3.9 节 | ||
flags and size | 41 | 后面会介绍 | ||
off bytes | 01 | number of bytes to store the size of the serialized cells | ||
number of cells | 02 | 这个例子是 2 个 cell | ||
number of root cells | 01 | 只有 1 个 root cell | ||
absent | 00 | always 0 in current implementations | ||
size of serialized cells | b3 | size of the serialized cells,这里是 179 字节 | ||
root cell list | 00 | root cell should have index 0 in case of 1 root cell | ||
serialized cells |
cell 0 |
refs descriptor | 01 | 当前 cell 有一个引用,当前 cell 是 ordinary cell |
bits descriptor | e1 | floor(901 / 8) + ceil (901 / 8) = 225 = 0xe1 | ||
cell data |
880119e26ed9ec68605b78dbf4e024f717f67a381d1578b99e026ac4f16a9ba6306802ee8e2b3f71ddcf12c08f47a2a01439 3b7864e300a4f3ce093e710dd940567482f1ca7943e9bcc3ec472d54e97c3aa4a7f686b7b6ab519692694bb8ada44e78094d 4d18bb328e7e2000000018001c |
cell data。cell 中有 901 bits 数据 序列化时按 8 bits 倍数补齐为了 113 字节(904 bits) |
||
cell refs | 01 | 所引用的 cell index 为 1 | ||
cell 1 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 7a | floor(488 / 8) + ceil (488 / 8) = 122 = 0x7a | ||
cell data | 42004e752d9f7cc330f8b52957d4da83ec8bf02586bf309be218c4fbbe38b48ba762203ade5000000000000000000000000000000000000048656c6c6f | cell data。cell 中有 488 bits 数据,不需要补齐 | ||
crc32c | cebe60ba | crc32c 校验码 |
注 1:flags and size(在上面例子中就是二进制 01000001)的前 3 bits 表示 has_idx/has_crc32c/has_cache_bits(如果 has_idx 为 1,则在 root cell list 后面会在额外的 index 数据。has_cache_bits 仅当 has_idx 为 1 时才有意义);中间 2 bits 均为 0;最后 3 bits 表示编码 Cell 的数量所需的最小字节数(上面例子中为 1)。
注 2:refs descriptor 的计算公式为:
注 2:bits descriptor 的计算公式为:
1.1.1. Cell 0
前面介绍中提到了 Cell 0 由下面数据组成(下面十六进制数据共有 904 bits,需要根据 Cell 的 bits descriptor 信息才知道 padding 的比特位数是 3,才能知道 Cell 0 真正比特位数是 901:
0x880119e26ed9ec68605b78dbf4e024f717f67a381d1578b99e026ac4f16a9ba6306802ee8e2b3f71ddcf12c08f47a2a014393b7864e300a4f3ce093e710dd940567482f1ca7943e9bcc3ec472d54e97c3aa4a7f686b7b6ab519692694bb8ada44e78094d4d18bb328e7e2000000018001c
Cell 0 数据可分解为下面更易读的形式:
external message header (275 bits) |
magic bits | 0b10 | 10 表示 ext_in_msg_info,https://ton.org/tblkch.pdf | |||
src addr | 0b00 | 00 表示 none addr | ||||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |||
anycast | 0b0 | |||||
workchain | 0b00000000 | workchain | ||||
addr hash | 0x8cf1376cf634302dbc6dfa70127b8bfb3d1c0e8abc5ccf01356278b54dd31834 | 发送者的 raw 地址 | ||||
import fee | 0b0000 | |||||
stateInit present flag (1 bit) | 0b0 | 0 没有 stateInit;1 有 stateInit。首次提交 Tx 创建钱包合约时,它才是 1 | ||||
body ref flag (1 bit) | 0b0 | 0 表示 Cell 剩下空间可放下整个 body;1 表示 Cell 空间不够,body 需要放在 ref 中 | ||||
body (624 bits) |
signature (512 bits) |
0x5dd1c567ee3bb9e25811e8f4540287276f0c9c60149e79c127ce21bb280ace90 0x5e394f287d37987d88e5aa9d2f875494fed0d6f6d56a32d24d297715b489cf01 |
Ed25519 签名数据,后文会介绍它是如何计算的 |
|||
signing message |
wallet id | 0x29a9a317 | ||||
valid until | 0x6651cfc4 | 交易过期时间 | ||||
seqno | 0x00000003 | 类似于 Ethereum 的 nonce 值 | ||||
op | 0x00 | 对于 V4R2 版本的钱包来说,op 为 0 表示原生币发送 | ||||
send mode | 0x03 | 参考 https://docs.ton.org/develop/func/stdlib#send_raw_message | ||||
cell padding | 0b100 | 按字节补齐。如果 Cell 的 bits descriptor 为奇数,表示有 padding |
注 1:TON 的钱包合约是在用户自己首次提交 Tx 时才会被部署(别人给你转币的场景,并不会帮你部署合约),上面例子中,由于 seqno 为 3,所以不是首次提交 Tx 了,钱包合约早就已经部署了。
注 2:对于 V4R2 版本的钱包来说,op 为 0 表示原生币发送,参考:https://github.com/ton-blockchain/wallet-contract/blob/4111fd9e3313ec17d99ca9b5b1656445b5b49d8f/func/wallet-v4-code.fc#L94
注 3:对于 V4R2 版本的钱包来说,body 中的 signature 在 signing message 的前面(V5R1 钱包恰恰相反,signature 在 signing message 的后面)。这是由 V4R2/V5R1 合约中的代码决定的,参考:https://github.com/ton-blockchain/wallet-contract/blob/4111fd9e3313ec17d99ca9b5b1656445b5b49d8f/func/wallet-v4-code.fc#L73 和 https://github.com/ton-blockchain/wallet-contract-v5/blob/88557ebc33047a95207f6e47ac8aadb102dff744/contracts/wallet_v5.fc#L165
注 4:上面数据中既有二进制表示,又有十六进制表示。并且这些数据没有按字节(或半字节)对齐,所以如果直接在 Tx 的十六进制表达中搜索签名数据,会找不到(因为没有按半字节对齐,它们错位了)。
1.1.2. Cell 1
Cell 0 有且只有一个引用,就是 Cell 1。前面介绍中提到了 Cell 1 由下面 488 bits 数据组成:
0x42004e752d9f7cc330f8b52957d4da83ec8bf02586bf309be218c4fbbe38b48ba762203ade5000000000000000000000000000000000000048656c6c6f
Cell 1 数据可分解为下面更易读的形式:
internal message header (414 bits) |
magic bits | 0b0 | 0 表示 int_msg_info,https://ton.org/tblkch.pdf | |
ihrDisabled | 0b1 | 目前总是 1。因为 Instant Hypercube Routing 还没有被完全实现 | ||
isBounceable | 0b0 | 0,因为接收地址 0QCc6ls--YZh8WpSr6m1B9kX4EsNfmE3xDGJ93xxaRdOxAhM 为 Non-bounceable | ||
bounced | 0b0 | 0 表示没有被弹回 | ||
src addr | 0b00 | 00 表示 none addr | ||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |
anycast | 0b0 | |||
workchain | 0b00000000 | workchain | ||
addr hash | 0x9cea5b3ef98661f16a52afa9b507d917e04b0d7e6137c43189f77c7169174ec4 | 接收者的 raw 地址 | ||
currency collect- tion |
size | 0b0100 | 转帐数量占 4 字节 | |
value | 0x075bca00 | 转帐数量 123456000 | ||
other | 0b0 | 0 means no other (i.e. ExtraCurrencyCollection) in CurrencyCollection | ||
ihr fee (4 bits) | 0b0000 | |||
forward fee (4 bits) | 0b0000 | |||
created lt (64 bits) | 0x0000000000000000 | |||
created at (32 bits) | 0x00000000 | |||
message init present flag (1 bit) | 0b0 | 0 表示 message 没有 init 数据 | ||
message body ref flag (1 bit) | 0b0 | 0 表示 Cell 剩下空间可放下整个 body;1 表示 Cell 空间不够,body 需要放在 ref 中 | ||
body (72 bits) |
type of payload | 0x00000000 | 32 bits 0 表示 string | |
payload | 0x48656c6c6f | 交易备注 Hello |
注:Cell 1 一共有 414+1+1+72 = 488 bits 数据。
1.2. Tx 解析实例 2(V5R1 钱包部署及原生币转帐)
下面介绍测试网上另一个 Tx b35192b5d7c975d3c86903059964e38775aa6cf271cd6923d0c9ef21e79e2413 的细节。
这个交易的功能是测试网上 V5R1 钱包进行原生币 TON 的转帐(注:由于这是帐户 0QBu6dyecFJBVzjtTEUTTPcZpdJhCDnIMQ3To4PZS0DrVqp_ 的首个 Tx,所以这个 Tx 中还会部署钱包合约):
0QBu6dyecFJBVzjtTEUTTPcZpdJhCDnIMQ3To4PZS0DrVqp_ (V5R1 wallet) --- 0.1234 TON --> 0QASPuJu9dYvKyQuV-vQL4YKOEa9uXNkslh0qRbU5quI7TTU
这个 Tx 是通过 RPC sendBoc 提交上链的,提交 Tx 时的具体参数如下:
$ curl 'https://testnet.toncenter.com/api/v2/jsonRPC' \ -X POST \ -H 'Content-Type: application/json' \ --data-raw '{"id":"1","jsonrpc":"2.0","method":"sendBoc","params":{"boc":"te6cckECGQEAA3UAA+eIAN3TuTzgpIKucdqYiiaZ7jNLpMIQc5BiG6dHB7KWgdasEY5tLO3P////v////+AAAAAW8m9QlO0hQ8Io17rpq2NooiJiF5nEC4ADFcikdU2QvJ/x6LwsurKdCxkgJdhRsQbnbQY7ERPoMFSpNLGeXLsoPAEVFgEU/wD0pBP0vPLICwICASADDgIBSAQFAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hEQAgEgBg0CASAHCgIBbggJABmtznaiaEAg65Drhf/AABmvHfaiaEAQ65DrhY/AAgFICwwAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgABm+Xw9qJoQICg65D6AsAQLyDwEeINcLH4IQc2lnbrry4Ip/EAHmjvDtou37IYMI1yICgwjXIyCAINch0x/TH9Mf7UTQ0gDTHyDTH9P/1woACvkBQMz5EJoolF8K2zHh8sCH3wKzUAew8tCEUSW68uCFUDa68uCG+CO78tCIIpL4AN4BpH/IygDLHwHPFsntVCCS+A/ecNs82BED9u2i7fsC9AQhbpJsIY5MAiHXOTBwlCHHALOOLQHXKCB2HkNsINdJwAjy4JMg10rAAvLgkyDXHQbHEsIAUjCw8tCJ10zXOTABpOhsEoQHu/Lgk9dKwADy4JPtVeLSAAHAAJFb4OvXLAgUIJFwlgHXLAgcEuJSELHjDyDXShITFACWAfpAAfpE+Cj6RDBYuvLgke1E0IEBQdcY9AUEnX/IygBABIMH9FPy4IuOFAODB/Rb8uCMItcKACFuAbOw8tCQ4shQA88WEvQAye1UAHIw1ywIJI4tIfLgktIA7UTQ0gBRE7ry0I9UUDCRMZwBgQFA1yHXCgDy4I7iyMoAWM8Wye1Uk/LAjeIAEJNb2zHh10zQAFGAAAAAP////tlBsCnz1yWP/sjO5CFA7bkxE/CSaZC7YJQuMiwpiYnQoAIKDsPIbQIXGAAAAIZCAAkfcTd66xeVkhcr9egXwwUcI17cubJZLDpUi2pzVcR2okxqxAAAAAAAAAAAAAAAAAAAAAAAAEhlbGxvIHdvcmxkv4KlZg=="}}' { "ok": true, "result": { "@type": "ok", "@extra": "1722675676.5674324:1:0.5131636309140121" }, "id": "1", "jsonrpc": "2.0" }
上面命令中提到的 Base64 编码的 Tx 数据可以分解为下面这种更清晰的表达方式:
magic prefix | b5ee9c72 | 魔法数字,参考 https://docs.ton.org/tblkch.pdf 5.3.9 节 | ||
flags and size | 41 | 后面会介绍 | ||
off bytes | 02 | number of bytes to store the size of the serialized cells | ||
number of cells | 19 | 这个例子是 25 个 cell | ||
number of root cells | 01 | 只有 1 个 root cell | ||
absent | 00 | always 0 in current implementations | ||
size of serialized cells | 0375 | size of the serialized cells,这里是 885 字节 | ||
root cell list | 00 | root cell should have index 0 in case of 1 root cell | ||
serialized cells |
cell 0 |
refs descriptor | 03 | 当前 cell 有 3 个引用,当前 cell 是 ordinary cell |
bits descriptor | e7 | 序列化后的 cell data 占用 ceil(231/2) = 116 字节 | ||
cell data |
8800ddd3b93ce0a482ae71da988a2699ee334ba4c2107390621ba74707b29681d6ac118e6d2cedcfffffffbfffffffe00000 0016f26f5094ed2143c228d7bae9ab6368a222621799c40b800315c8a4754d90bc9ff1e8bc2cbab29d0b192025d851b106e7 6d063b1113e83054a934b19e5cbb283c |
cell data。由于 0xe7 为奇数,有补齐 |
||
cell refs | 011516 | 所引用的 cell index 为 0x01/0x15/0x16 | ||
cell 1 |
refs descriptor | 01 | 当前 cell 有 1 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 14 | 序列化后的 cell data 占用 ceil(20/2) = 10 字节 | ||
cell data | ff00f4a413f4bcf2c80b | cell data。由于 0xe7 为偶数,没有补齐 | ||
cell refs | 02 | 所引用的 cell index 为 0x02 | ||
cell 2 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 01 | 序列化后的 cell data 占用 ceil(1/2) = 1 字节 | ||
cell data | 20 | cell data。由于 0x01 为奇数,有补齐 | ||
cell refs | 030e | 所引用的 cell index 为 0x03/0x0e | ||
cell 3 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 01 | 序列化后的 cell data 占用 ceil(1/2) = 1 字节 | ||
cell data | 48 | cell data。由于 0x01 为奇数,有补齐 | ||
cell refs | 0405 | 所引用的 cell index 为 0x04/0x05 | ||
cell 4 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | dc | 序列化后的 cell data 占用 ceil(220/2) = 110 字节 | ||
cell data |
d020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d0 74d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d7 49810280b99130e070e2 |
cell data。由于 0xdc 为偶数,没有补齐 |
||
cell refs | 1110 | 所引用的 cell index 为 0x11/0x10 | ||
cell 5 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 01 | 序列化后的 cell data 占用 ceil(1/2) = 1 字节 | ||
cell data | 20 | cell data。由于 0x01 为奇数,有补齐 | ||
cell refs | 060d | 所引用的 cell index 为 0x06/0x0d | ||
cell 6 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 01 | 序列化后的 cell data 占用 ceil(1/2) = 1 字节 | ||
cell data | 20 | cell data。由于 0x01 为奇数,有补齐 | ||
cell refs | 070a | 所引用的 cell index 为 0x07/0x0a | ||
cell 7 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 01 | 序列化后的 cell data 占用 ceil(1/2) = 1 字节 | ||
cell data | 6e | cell data。由于 0x01 为奇数,有补齐 | ||
cell refs | 0809 | 所引用的 cell index 为 0x08/0x09 | ||
cell 8 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 19 | 序列化后的 cell data 占用 ceil(25/2) = 13 字节 | ||
cell data | adce76a2684020eb90eb85ffc0 | cell data。由于 0x19 为奇数,有补齐 | ||
cell 9 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 19 | 序列化后的 cell data 占用 ceil(25/2) = 13 字节 | ||
cell data | af1df6a2684010eb90eb858fc0 | cell data。由于 0x19 为奇数,有补齐 | ||
cell 10 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 01 | 序列化后的 cell data 占用 ceil(1/2) = 1 字节 | ||
cell data | 48 | cell data。由于 0x01 为奇数,有补齐 | ||
cell refs | 0b0c | 所引用的 cell index 为 0x0b/0x0c | ||
cell 11 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 17 | 序列化后的 cell data 占用 ceil(23/2) = 12 字节 | ||
cell data | b325fb51341c75c875c2c7e0 | cell data。由于 0x17 为奇数,有补齐 | ||
cell 12 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 11 | 序列化后的 cell data 占用 ceil(17/2) = 9 字节 | ||
cell data | b262fb513435c28020 | cell data。由于 0x11 为奇数,有补齐 | ||
cell 13 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 19 | 序列化后的 cell data 占用 ceil(25/2) = 13 字节 | ||
cell data | be5f0f6a2684080a0eb90fa02c | cell data。由于 0x19 为奇数,有补齐 | ||
cell 14 |
refs descriptor | 01 | 当前 cell 有 1 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 02 | 序列化后的 cell data 占用 ceil(2/2) = 1 字节 | ||
cell data | f2 | cell data。由于 0x02 为偶数,没有补齐 | ||
cell refs | 0f | 所引用的 cell index 为 0x0f | ||
cell 15 |
refs descriptor | 01 | 当前 cell 有 1 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 1e | 序列化后的 cell data 占用 ceil(30/2) = 15 字节 | ||
cell data | 20d70b1f82107369676ebaf2e08a7f | cell data。由于 0x1e 为偶数,没有补齐 | ||
cell refs | 10 | 所引用的 cell index 为 0x10 | ||
cell 16 |
refs descriptor | 01 | 当前 cell 有 1 个引用,当前 cell 是 ordinary cell | |
bits descriptor | e6 | 序列化后的 cell data 占用 ceil(230/2) = 115 字节 | ||
cell data |
8ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a 28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f 01cf16c9ed542092f80fde70db3cd8 |
cell data。由于 0xe6 为偶数,没有补齐 |
||
cell refs | 11 | 所引用的 cell index 为 0x11 | ||
cell 17 |
refs descriptor | 03 | 当前 cell 有 3 个引用,当前 cell 是 ordinary cell | |
bits descriptor | f6 | 序列化后的 cell data 占用 ceil(246/2) = 123 字节 | ||
cell data |
eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e0 9320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0eb d72c08142091709601d72c081c12e25210b1e30f20d74a |
cell data。由于 0xf6 为偶数,没有补齐 |
||
cell refs | 121314 | 所引用的 cell index 为 0x12/0x13/0x14 | ||
cell 18 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 96 | 序列化后的 cell data 占用 ceil(150/2) = 75 字节 | ||
cell data |
01fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e0 8c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54 |
cell data。由于 0x96 为偶数,没有补齐 |
||
cell 19 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 72 | 序列化后的 cell data 占用 ceil(114/2) = 57 字节 | ||
cell data |
30d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9 ed5493f2c08de2 |
cell data。由于 0x72 为偶数,没有补齐 |
||
cell 20 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 10 | 序列化后的 cell data 占用 ceil(16/2) = 8 字节 | ||
cell data | 935bdb31e1d74cd0 | cell data。由于 0x10 为偶数,没有补齐 | ||
cell 21 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 51 | 序列化后的 cell data 占用 ceil(81/2) = 41 字节 | ||
cell data | 800000003ffffffed941b029f3d7258ffec8cee42140edb93113f0926990bb60942e322c298989d0a0 | cell data。由于 0x51 为奇数,有补齐 | ||
cell 22 |
refs descriptor | 02 | 当前 cell 有 2 个引用,当前 cell 是 ordinary cell | |
bits descriptor | 0a | 序列化后的 cell data 占用 ceil(10/2) = 5 字节 | ||
cell data | 0ec3c86d02 | cell data。由于 0x0a 为偶数,没有补齐 | ||
cell refs | 1718 | 所引用的 cell index 为 0x17/0x18 | ||
cell 23 |
refs descriptor | 00 | 当前 cell 没个引用,当前 cell 是 ordinary cell | |
bits descriptor | 00 | 序列化后的 cell data 占用 ceil(0/2) = 0 字节 | ||
cell 24 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 86 | 序列化后的 cell data 占用 ceil(134/2) = 67 字节 | ||
cell data |
4200091f71377aeb179592172bf5e817c3051c235edcb9b2592c3a548b6a7355c476a24c6ac4000000000000000000000000 00000000000048656c6c6f20776f726c64 |
cell data。由于 0x86 为偶数,没有补齐 |
||
crc32c | bf82a566 | crc32c 校验码 |
可见,这个 Tx 中一共编码了 25 个 Cell,为什么这么多呢?这是由于这个 Tx 是帐户 0QBu6dyecFJBVzjtTEUTTPcZpdJhCDnIMQ3To4PZS0DrVqp_ 的首个 Tx,所以这个 Tx 中还包含了钱包合约的 Init Code 和 Init Data,所以导致 Cell 会比较多。
1.2.1. Cell 0
前面介绍中提到了 Cell 0 由下面数据组成:
8800ddd3b93ce0a482ae71da988a2699ee334ba4c2107390621ba74707b29681d6ac118e6d2cedcfffffffbfffffffe00000 0016f26f5094ed2143c228d7bae9ab6368a222621799c40b800315c8a4754d90bc9ff1e8bc2cbab29d0b192025d851b106e7 6d063b1113e83054a934b19e5cbb283c
下面把 Cell 0 数据分解为更易读的形式:
external message header (275 bits) |
magic bits | 0b10 | 10 表示 ext_in_msg_info,https://ton.org/tblkch.pdf | |||
src addr | 0b00 | 00 表示 none addr | ||||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |||
anycast | 0b0 | |||||
workchain | 0b00000000 | workchain | ||||
addr hash | 0x6ee9dc9e7052415738ed4c45134cf719a5d2610839c8310dd3a383d94b40eb56 | 发送者的 raw 地址 | ||||
import fee | 0b0000 | |||||
stateInit present flag (1 bit) | 0b1 | 0 没有 stateInit;1 有 stateInit。这里是首次提交 Tx 会创建钱包合约,所以它是 1 | ||||
stateInit ref flag (1 bit) | 0b0 | 0 表示 Cell 剩下空间可放下 stateInit;1 表示 Cell 空间不够(需要放在 ref 中) | ||||
stateInit |
split depth | 0b0 | 0 表示 no splitDepth in StateInit | |||
sepcial | 0b0 | 0 表示 no special in StateInit | ||||
init code ref flag | 0b1 | 1 表示 init code 保存在当前 Cell 的 ref 中。第 1 个 ref | ||||
init data ref flag | 0b1 | 1 表示 init data 保存在当前 Cell 的 ref 中。第 2 个 ref | ||||
libraries | 0b0 | 0 表示 no libraries in StateInit | ||||
body ref flag (1 bit) | 0b0 | 0 表示 Cell 剩下空间可放下整个 body;1 表示 Cell 空间不够,body 需要放在 ref 中 | ||||
body |
signing message |
V5R1 opcode | 0x7369676e | 0x7369676e/0x73696e74/0x6578746e 分别为 auth_signed_external/auth_signed_internal/auth_extension | ||
wallet id | 0x7ffffffd | 0x7ffffffd(2147483645) 测试网,0x7fffff11(2147483409) 主网 | ||||
valid until | 0xffffffff | 交易过期时间,这里设置了全 1 的值(几乎不会过期) | ||||
seqno | 0x00000000 | 类似于 Ethereum 的 nonce 值 | ||||
basic actions | 0b1 | 1 表示 basic actions(send message) 保存在当前 Cell 的 ref 中。第 3 个 ref | ||||
extended actions | 0b0 | 0 表示没有 extended actions(add extension/remove extension/set signature allowed) | ||||
signature (512 bits) |
0xde4dea129da42878451af75d356c6d14444c42f33881700062b9148ea9b21793 0xfe3d1785975653a1632404bb0a3620dceda0c762227d060a95269633cb976507 |
Ed25519 签名数据 |
||||
cell padding | 0b100 | 按字节补齐。如果 Cell 的 bits descriptor 为奇数,表示有 padding |
从上面的分析中可知 Cell 0 共有 3 个 refs(Cell 0 的 cell refs 数据为 0x011516):
- Init Code,对应的 Cell Index 是 0x01,即 Cell 1
- Init Data,对应的 Cell Index 是 0x15,即 Cell 21
- Basic Actions,对应的 Cell Index 是 0x16,即 Cell 22
下面我们重点分析一下 Cell 22。
1.2.2. Cell 22/23/24
通过前面的介绍可知 Cell 22 保存着 Wallet V5R1 钱包的 Basic Actions,这节将分析它。
Basic Actions 是 Out List 结构,它在 @ton/core 中是这样序列化的:
/* out_list_empty$_ = OutList 0; out_list$_ {n:#} prev:^(OutList n) action:OutAction = OutList (n + 1); */ export function storeOutList(actions: OutAction[]) { const cell = actions.reduce((cell, action) => beginCell() .storeRef(cell) // Cell 23 保存在 Cell 22 的 refs 中。Cell 22 的第 1 个 ref .store(storeOutAction(action)) .endCell(), beginCell().endCell() // Begin with empty cell,就是我们这个例子中的 Cell 23(空 Cell) ); return (builder: Builder) => { builder.storeSlice(cell.beginParse()); } } /* action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; */ const outActionSendMsgTag = 0x0ec3c86d; function storeOutActionSendMsg(action: OutActionSendMsg) { return (builder: Builder) => { builder.storeUint(outActionSendMsgTag, 32) // Cell 22 的数据部分 1 .storeUint(action.mode, 8) // Cell 22 的数据部分 2 .storeRef(beginCell().store(storeMessageRelaxed(action.outMsg)).endCell()); // Cell 22 的第 2 个 ref } }
Cell 22 数据可分解为下面更易读的形式:
out action tag | 0ec3c86d | Tag 0ec3c86d/ad4de08e 分别表示 send msg/set code。参考 https://ton.org/tblkch.pdf 4.4.11. Serialization of output actions |
send mode | 02 | 可简单理解为目标帐户合约没部署也不出错。参考 https://docs.ton.org/develop/smart-contracts/messages#message-modes |
Cell 22 的两个 refs 分别为:
- Cell 23,这是一个空 Cell,是序列化 Out List 时的首个 Cell。
- Cell 24,Send Message 主要内容在这个 Cell 中。
注:Cell 22 中指定的 send mode 为 2,并没有包含 +1 flag。这会导致:尽量转账数量指定的是 1.234 TON,但目标地址真正收到的 TON 的数量会在 1.234 的基础上扣除手续费(会小于 1.234)。我们查询目标地址余额也会发现确实比 1.234 要少:
$ curl 'https://testnet.toncenter.com/api/v2/getAddressBalance?address=0QASPuJu9dYvKyQuV-vQL4YKOEa9uXNkslh0qRbU5quI7TTU' {"ok":true,"result":"1233600000"}
Cell 24 数据可分解为下面更易读的形式:
internal message header (414 bits) |
magic bits | 0b0 | 0 表示 int_msg_info,https://ton.org/tblkch.pdf | |
ihrDisabled | 0b1 | 目前总是 1。因为 Instant Hypercube Routing 还没有被完全实现 | ||
isBounceable | 0b0 | 0,因为接收地址 0QASPuJu9dYvKyQuV-vQL4YKOEa9uXNkslh0qRbU5quI7TTU 为 Non-bounceable | ||
bounced | 0b0 | 0 表示没有被弹回 | ||
src addr | 0b00 | 00 表示 none addr | ||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |
anycast | 0b0 | |||
workchain | 0b00000000 | workchain | ||
addr hash | 0x123ee26ef5d62f2b242e57ebd02f860a3846bdb97364b25874a916d4e6ab88ed | 接收者的 raw 地址 | ||
currency collect- tion |
size | 0b0100 | 转帐数量占 4 字节 | |
value | 0x498d5880 | 转帐数量 1234000000(即 1.234 TON) | ||
other | 0b0 | 0 means no other (i.e. ExtraCurrencyCollection) in CurrencyCollection | ||
ihr fee (4 bits) | 0b0000 | |||
forward fee (4 bits) | 0b0000 | |||
created lt (64 bits) | 0x0000000000000000 | |||
created at (32 bits) | 0x00000000 | |||
message init present flag (1 bit) | 0b0 | 0 表示 message 没有 init 数据 | ||
message body ref flag (1 bit) | 0b0 | 0 表示 Cell 剩下空间可放下整个 body;1 表示 Cell 空间不够,body 需要放在 ref 中 | ||
message body (120 bits) |
type of payload | 0x00000000 | 32 bits 0 表示 string | |
payload | 0x48656c6c6f20776f726c64 | 交易备注 Hello world |
1.3. Tx 解析实例 3(V5R1 钱包 Jetton 转账)
下面以 TON 主网上的 Tx edc724b8b3733afc5133d62c2b65bafa3fae31df876d87b576050ce3369e15b4 为例介绍一下这个 Tx 的细节。
这个交易的功能是主网上 V5R1 钱包进行 USDT 的转帐:
UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_ (V5R1 wallet) --- 0.11 USDT --> UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk
提交 Tx 时的具体参数如下:
$ curl https://toncenter.com/api/v2/sendBocReturnHash -X POST -H "Content-Type: application/json" -d '{ "boc": "te6cckECBgEAASIAAUWIAUPGsCB+oHBGks4E/WQD8VoYBDFK43SgpC9TyWeqauJoDAEBoXNpZ25///8RZro7egAAAAGYUu8+ZHZK/wWb9ojB++h5tz2ie7e4GktWZfr475zJcnpRLZADd9wbyuuV7GXr8QzKeqvQbSDVJlgv4bcdnjwDYAICCg7DyG0DAwQAAAFoYgBwcARvtYoAq2KC3PPTyFSjlwwsLVuA3hCZtiQP1p6T4yAvrwgAAAAAAAAAAAAAAAAAAQUAyA+KfqUAAAAAAAAAADAa2wgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpsAKHjWBA/UDgjSWcCfrIB+K0MAhilcbpQUhep5LPVNXE0CAgAAAAB0ZXN0IGNvbW1lbnQIl4du" }'
上面命令中提到的 Base64 编码的 Tx 数据可以分解为下面这种更清晰的表达方式:
magic prefix | b5ee9c72 | 魔法数字,参考 https://docs.ton.org/tblkch.pdf 5.3.9 节 | ||
flags and size | 41 | 后面会介绍 | ||
off bytes | 02 | number of bytes to store the size of the serialized cells | ||
number of cells | 06 | 这个例子是 6 个 cell | ||
number of root cells | 01 | 只有 1 个 root cell | ||
absent | 00 | always 0 in current implementations | ||
size of serialized cells | 0122 | size of the serialized cells,这里是 290 字节 | ||
root cell list | 00 | root cell should have index 0 in case of 1 root cell | ||
serialized cells |
cell 0 |
refs descriptor | 01 | 当前 cell 有一个引用,当前 cell 是 ordinary cell |
bits descriptor | 45 | bits descriptor | ||
cell data | 880143c6b0207ea0704692ce04fd6403f15a1804314ae374a0a42f53c967aa6ae2680c | cell data | ||
cell refs | 01 | 所引用的 cell index 为 1 | ||
cell 1 |
refs descriptor | 01 | 当前 cell 有一个引用,当前 cell 是 ordinary cell | |
bits descriptor | a1 | bits descriptor | ||
cell data |
7369676e7fffff1166ba3b7a000000019852ef3e64764aff059bf688c1fbe879b73da27bb7b81a4b5665faf8ef9cc9727a512d900377dc 1bcaeb95ec65ebf10cca7aabd06d20d526582fe1b71d9e3c0360 |
cell data |
||
cell refs | 02 | 所引用的 cell index 为 2 | ||
cell 2 |
refs descriptor | 02 | 当前 cell 有两个引用,当前 cell 是 ordinary cell | |
bits descriptor | 0a | bits descriptor | ||
cell data | 0ec3c86d03 | cell data | ||
cell refs | 0304 | 所引用的 cell index 为 3,4 | ||
cell 3 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | 00 | bits descriptor | ||
cell 4 |
refs descriptor | 01 | 当前 cell 有一个引用,当前 cell 是 ordinary cell | |
bits descriptor | 68 | bits descriptor | ||
cell data | 62007070046fb58a00ab6282dcf3d3c854a3970c2c2d5b80de1099b6240fd69e93e3202faf080000000000000000000000000001 | cell data | ||
cell refs | 05 | 所引用的 cell index 为 5 | ||
cell 5 |
refs descriptor | 00 | 当前 cell 没有引用,当前 cell 是 ordinary cell | |
bits descriptor | c8 | bits descriptor | ||
cell data |
0f8a7ea50000000000000000301adb080029bd1cfc953a98c234e1ba9db7fe13008e3511c7a5e310b08942a1634a85469b002878d6040f d40e08d259c09fac807e2b430086295c6e941485ea792cf54d5c4d0202000000007465737420636f6d6d656e74 |
cell data |
||
crc32c | 0897876e | crc32c 校验码 |
1.3.1. Cell 0
前面介绍中提到了 Cell 0 由下面数据组成:
880143c6b0207ea0704692ce04fd6403f15a1804314ae374a0a42f53c967aa6ae2680c
下面把 Cell 0 数据分解为更易读的形式:
external message header (275 bits) |
magic bits | 0b10 | 10 表示 ext_in_msg_info,https://ton.org/tblkch.pdf | |
src addr | 0b00 | 00 表示 none addr | ||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |
anycast | 0b0 | |||
workchain | 0b00000000 | workchain | ||
addr hash | 0xa1e358103f5038234967027eb201f8ad0c0218a571ba505217a9e4b3d5357134 | 发送者的 raw 地址 | ||
import fee | 0b0000 | |||
stateInit present flag (1 bit) | 0b0 | 0 没有 stateInit;1 有 stateInit。首次提交 Tx 创建钱包合约时,它才是 1 | ||
body ref flag (1 bit) | 0b1 | 0 表示 Cell 剩下空间可放下整个 body;1 表示 Cell 空间不够,body 需要放在 ref 中 | ||
cell padding | 0b100 | 按字节补齐。如果 Cell 的 bits descriptor 为奇数,表示有 padding |
1.3.2. Cell 1
上面例子中 Cell 0 的 body ref flag 为 1,表示 Body 保存在 Cell 0 的 ref 中,而 Cell 0 只有一个 ref(Cell 1),所以 Cell 1 保存的就是 Body。
下面把 Cell 1 数据分解为更易读的形式:
body |
signing message |
V5R1 opcode | 0x7369676e | 0x7369676e/0x73696e74/0x6578746e 分别为 auth_signed_external/auth_signed_internal/auth_extension |
wallet id | 0x7fffff11 | 0x7ffffffd(2147483645) 测试网,0x7fffff11(2147483409) 主网 | ||
valid until | 0x66ba3b7a | 交易过期时间,Unix Timestamp 1723480954 | ||
seqno | 0x00000001 | 类似于 Ethereum 的 nonce 值 | ||
basic actions | 0b1 | 1 表示 basic actions(send message) 保存在当前 Cell 的 ref 中。Cell 1 的 ref 是 Cell 2 | ||
extended actions | 0b0 | 0 表示没有 extended actions(add extension/remove extension/set signature allowed) | ||
signature (512 bits) |
0x614bbcf991d92bfc166fda2307efa1e6dcf689eedee0692d5997ebe3be7325c9 0xe944b6400ddf706f2bae57b197afc43329eaaf41b483549960bf86dc7678f00d |
Ed25519 签名数据,后文会介绍它是如何计算的 |
||
cell padding | 0b100000 | 按字节补齐。如果 Cell 的 bits descriptor 为奇数,表示有 padding |
1.3.3. Cell 2/3/4/5
通过前面的介绍可知 Cell 2 保存着 Wallet V5R1 钱包的 Basic Actions,这节将分析它。Basic Actions 是 Out List 结构,细节可参考节 1.2.2。
Cell 2 数据可分解为下面更易读的形式:
out action tag | 0ec3c86d | Tag 0ec3c86d/ad4de08e 分别表示 send msg/set code。参考 https://ton.org/tblkch.pdf 4.4.11. Serialization of output actions |
send mode | 03 | 这是 +1 flag 和 +2 flag 的组合。参考 https://docs.ton.org/develop/smart-contracts/messages#message-modes |
Cell 2 的两个 refs 分别为:
- Cell 3,这是一个空 Cell,是序列化 Out List 时的首个 Cell。
- Cell 4,Send Message 主要内容在这个 Cell 中。
Cell 4 数据(62007070046fb58a00ab6282dcf3d3c854a3970c2c2d5b80de1099b6240fd69e93e3202faf080000000000000000000000000001)可分解为下面更易读的形式:
internal message header (414 bits) |
magic bits | 0b0 | 0 表示 int_msg_info,https://ton.org/tblkch.pdf | |
ihrDisabled | 0b1 | 目前总是 1。因为 Instant Hypercube Routing 还没有被完全实现 | ||
isBounceable | 0b1 | 1,因为接收地址 EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC(USDT wallet)为 bounceable | ||
bounced | 0b0 | 0 表示没有被弹回 | ||
src addr | 0b00 | 00 表示 none addr | ||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |
anycast | 0b0 | |||
workchain | 0b00000000 | workchain | ||
addr hash | 0xe0e008df6b140156c505b9e7a790a9472e18585ab701bc21336c481fad3d27c6 | 消息接收者地址(EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC),即 USDT 发送者的 jetton wallet | ||
currency collect- tion |
size | 0b0100 | 转帐数量占 4 字节 | |
value | 0x05f5e100 | 转帐数量 100000000(即 0.1 TON)。这个值不是用户指定的,钱包 App 指定的 | ||
other | 0b0 | 0 means no other (i.e. ExtraCurrencyCollection) in CurrencyCollection | ||
ihr fee (4 bits) | 0b0000 | |||
forward fee (4 bits) | 0b0000 | |||
created lt (64 bits) | 0x0000000000000000 | |||
created at (32 bits) | 0x00000000 | |||
message init present flag (1 bit) | 0b0 | 0 表示 message 没有 init 数据 | ||
message body ref flag (1 bit) | 0b1 | 0 表示 Cell 剩下空间可放下整个 body;1 表示 Cell 空间不够,body 需要放在 ref 中 |
Message Body 保存在 Cell 4 的 ref(即 Cell 5)中,下面我们介绍 Cell 5 的内容。
1.3.4. Cell 5(Jetton 转帐时的 Message body)
Cell 5 数据可分解为下面更易读的形式:
message body |
jetton op | 0x0f8a7ea5 | jetton transfer op. https://github.com/ton-blockchain/minter-contract/blob/main/contracts/imports/op-codes.fc | |
query id | 0x0000000000000000 | 可以用它把 Transfer/Transfer notification/Excesses 三个消息关联起来 | ||
amount |
size | 0b0011 | 转帐数量占 3 字节 | |
value | 0x01adb0 | 转帐数量 110000,由于 USDT 精度是 6,这里就是 0.11 USDT | ||
dest addr |
addr flag | 0b10 | 10 表示 std addr | |
anycast | 0b0 | |||
workchain | 0b00000000 | workchain | ||
addr hash | 0x14de8e7e4a9d4c611a70dd4edbff0980471a88e3d2f1885844a150b1a542a34d | jetton 的 new owner 地址(UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk) | ||
resp dest addr |
addr flag | 0b10 | 10 表示 std addr | |
anycast | 0b0 | |||
workchain | 0b00000000 | workchain | ||
addr hash | 0xa1e358103f5038234967027eb201f8ad0c0218a571ba505217a9e4b3d5357134 | 指定多余的 TON 会通过 Excesses 消息退给哪个地址(转移 jetton 时,会往 jetton wallet 转帐 0.1 TON 作手续费) | ||
custom payload flag | 0b0 | 0 表示没有 custom payload,1 表示 custom payload 在 ref 中 | ||
forward ton amount |
size | 0b0001 | 转帐数量占 1 字节 | |
value | 0x01 | 大于 0 时会向 new owner 地址发送 Transfer notification 消息,这里是 0.000000001 TON | ||
forward payload flag | 0b0 | 0 表示没有 forward payload,1 表示 forward payload 在 ref 中 | ||
comment |
type | 0x00000000 | 32 bits 0 表示 string | |
payload | 0x7465737420636f6d6d656e74 | 交易备注。这里是 test comment 的编码 |
2. 附录
2.1. Message Hash 计算
通过 RPC sendBocReturnHash 提交 Tx 时会返回 Message Hash。
Message Hash 就是 Root Cell 的“标准 Cell 表示哈希”,其具体规则可参考:Standard Cell representation hash。
下面是节 1.1 中 Message Hash 为 V8Y4wRJOdKphKnSjkBcWYTbIMTTcPrOq/1VA0zH1o4c=
,下面演示一下它的计算过程:
import hashlib import base64 # Standard Cell representation hash calculation # https://docs.ton.org/develop/data-formats/cell-boc#standard-cell-representation-hash-calculation cell_1_refs_descriptor = '00' cell_1_bits_descriptor = '7a' cell_1_data = '42004e752d9f7cc330f8b52957d4da83ec8bf02586bf309be218c4fbbe38b48ba762203ade5000000000000000000000000000000000000048656c6c6f' # cell 1 has no refs, so skip the refs descriptor and refs hashes cell_1_cell_hash = hashlib.sha256( bytes.fromhex(cell_1_refs_descriptor + cell_1_bits_descriptor + cell_1_data )).hexdigest() cell_0_refs_descriptor = '01' cell_0_bits_descriptor = 'e1' cell_0_data = '880119e26ed9ec68605b78dbf4e024f717f67a381d1578b99e026ac4f16a9ba6306802ee8e2b3f71ddcf12c08f47a2a014393b7864e300a4f3ce093e710dd940567482f1ca7943e9bcc3ec472d54e97c3aa4a7f686b7b6ab519692694bb8ada44e78094d4d18bb328e7e2000000018001c' cell_0_refs_depth = '0000' cell_0_refs_hashes = cell_1_cell_hash cell_0_cell_hash = hashlib.sha256( bytes.fromhex(cell_0_refs_descriptor + cell_0_bits_descriptor + cell_0_data + cell_0_refs_depth + cell_0_refs_hashes )).digest() print(cell_0_cell_hash.hex()) # 57c638c1124e74aa612a74a39017166136c83134dc3eb3aaff5540d331f5a387 print(base64.b64encode(cell_0_cell_hash)) # V8Y4wRJOdKphKnSjkBcWYTbIMTTcPrOq/1VA0zH1o4c=
2.2. Tx 解析实例 1 中的签名计算(Python)
节 1.1 中的交易的 Ed25519 签名数据是:
0x5dd1c567ee3bb9e25811e8f4540287276f0c9c60149e79c127ce21bb280ace905e394f287d37987d88e5aa9d2f875494fed0d6f6d56a32d24d297715b489cf01
下面 Python 代码演示了它是如何计算出来的:
import hashlib import nacl.encoding import nacl.signing # 生成要签名的数据,它是 signing message 的 Standard Cell representation hash # See https://docs.ton.org/develop/data-formats/cell-boc#standard-cell-representation-hash-calculation def get_data_to_be_signed(): cell_1_refs_descriptor = '00' cell_1_bits_descriptor = '7a' cell_1_data = '42004e752d9f7cc330f8b52957d4da83ec8bf02586bf309be218c4fbbe38b48ba762203ade5000000000000000000000000000000000000048656c6c6f' cell_1_hash = hashlib.sha256( bytes.fromhex(cell_1_refs_descriptor + cell_1_bits_descriptor + cell_1_data)).hexdigest() signing_message_refs_descriptor = '01' # signing message has one reference to cell 1 signing_message_bits_descriptor = '1c' signing_message_data = ('29a9a317' # wallet id, you can get it from the cell 0 + '6651cfc4' # expire time, you can get it from the cell 0 + '00000003' # seqno, you can get it from the cell 0 + '00' # op, you can get it from the cell 0 + '03' # send mode, you can get it from the cell 0 + '0000' # max depth as array + cell_1_hash) return hashlib.sha256(bytes.fromhex(signing_message_refs_descriptor + signing_message_bits_descriptor + signing_message_data)).digest() # Ed25519 私钥 private_key = bytes.fromhex('e36740a50f44ae14fdc3682f14361f7a686738dd624c0e9b9bc9183b7ef6b7cb') # 使用指定的私钥生成签名密钥 signing_key = nacl.signing.SigningKey(private_key, encoder=nacl.encoding.RawEncoder) # 生成验证密钥 verify_key = signing_key.verify_key print("public key:", verify_key.encode().hex()) # 6091a31d2acd5b5eb42a29888d106043796c1a438d1e000406e688f3d831056e # 要签名的消息 data = get_data_to_be_signed() print("data to be signed:", data.hex()) # 468e22fbaf2cf2578ecd879334eaf4bb80feb8e2f07d2c1e6f80201a50616ed9 # 对 data 进行签名 signature = signing_key.sign(data).signature print("signature:", signature.hex()) # 5dd1c567ee3bb9e25811e8f4540287276f0c9c60149e79c127ce21bb280ace905e394f287d37987d88e5aa9d2f875494fed0d6f6d56a32d24d297715b489cf01 # 验证签名 try: verify_key.verify(data, signature) print("Signature verified") except nacl.exceptions.BadSignatureError: print("Signature verification failed")
2.3. 帐户合约部署 VS Jetton 合约部署
往目标地址转账 Jetton 时,如果目标地址还没有部署 Jetton 合约,则会先为目标地址部署 Jetton 合约。这是因为用户的 Jetton 余额保存在用户的 Jetton 合约中, 不部署 Jetton 合约的话,就无法为目标地址用户记录 Jetton 余额。
这一点和原生币不一样!我们给某目标地址转账原生币时,如果目标帐户合约还没有部署也无需帮他部署,因为在目标合约部署之前 TON 链上有能力为这个地址记录原生币 TON 的余额(即 TON 链可以为处于 uninitialized 状态的合约记录原生币余额,也就是说原生币 TON 余额并不是保存在合约中)。其实就算你在转账 TON 时想帮目标帐户合约部署也很难实现,因为你没有目标帐户合约的 Init Code 和 Init Data,你最多只是猜测它可能是 V4R2/V5R1 等合约,如果目标合约完全是用户自已定制的,这种情况是没有办法帮忙部署的,除非他在链下发送给你。
关于钱包帐户合约和 Jetton 合约的部署时机的对比可以参考表 1。
一般部署时机 | 一般谁部署 | |
---|---|---|
钱包帐户(如 V4R2/V5R1)合约 | 首次往外转账原生币 TON 时 | 自己部署 |
Jetton 合约 | 首次收到别人转来的 Jetton 时 | 别人部署 |
考虑场景:Address 1 往 Address 2 转账 Jetton USDT,这会涉及到几个合约的部署呢?
- Address 1 曾经往外发过交易(帐户合约已经部署),Address 2 以前收过 Jetton USDT。这时不会有合约被部署。
- Address 1 曾经往外发过交易(帐户合约已经部署),但 Address 2 以前没有收过 Jetton USDT。在这种情况下这个 Tx 会部署一个合约,即别人(即 Address 2)的 Jetton USDT 合约。
- Address 1 以前没有往外发过交易(Address 1 帐户合约未部署过),Address 2 以前收过 Jetton USDT。在这种情况下这个 Tx 会部署一个合约,即自己(即 Address 1)的帐户合约。
- Address 1 以前没有往外发过交易(Address 1 帐户合约未部署过),而且恰好 Address 2 是首次收到 Jetton USDT。在这种情况下这个 Tx 会部署两个合约:一是自己(即 Address 1)的帐户合约;二是部署别人(即 Address 2)的 Jetton USDT 合约。链上就有这样的例子,比如 Tx 00defd4857b646c31b46049c1c8f61cc17d6cf3df1d0a22f40e325126dd2296c 。
注意:上面讨论的是最简单的场景,没有考虑 Mintless Jetton 的情况,也没有考虑由于 Transfer Notification 触发其它合约部署的情况。