详解 TON 区块链交易的底层细节

  • cig01
  • 发布于 2024-05-28 18:41
  • 阅读 9

本文深入探讨了 TON 区块链交易的底层细节,通过三个实际案例,详细解析了 V4R2 和 V5R1 钱包进行原生币转账以及 Jetton 转账的交易结构。文章还包括了消息哈希的计算方法,以及 Ed25519 签名的生成过程,对比了账户合约和 Jetton 合约的部署时机。

Created: <2024-05-27 Mon>

Last updated: <2024-11-12 Tue>

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 <br>  cells cell 0 refs descriptor 01 当前 cell 有一个引用,当前 cell 是 ordinary cell
bits descriptor e1 floor(901 / 8) + ceil (901 / 8) = 225 = 0xe1
cell data 880119e26ed9ec68605b78dbf4e024f717f67a381d1578b99e026ac4f16a9ba6306802ee8e2b3f71ddcf12c08f47a2a01439                       <br>  3b7864e300a4f3ce093e710dd940567482f1ca7943e9bcc3ec472d54e97c3aa4a7f686b7b6ab519692694bb8ada44e78094d                       <br>  4d18bb328e7e2000000018001c cell data。cell 中有 901 bits 数据                        <br>  序列化时按 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 的计算公式为: r+8s+32l ,其中 r 表示当前 Cell 的引用的个数,所以 0≤r≤4 ; s 的可选值为 0/1,分别表示 Ordinary Cell/Exotic Cell; l 是 level of Cell。

注 2:bits descriptor 的计算公式为: floor(b/8)+ceil(b/8) ,其中 b 是 Cell data 的二进制位数,由于一个 Cell 最多有 1023 位数据,所以 0≤b≤1023 。

1.1.1. Cell 0

前面介绍中提到了 Cell 0 由下面数据组成(下面十六进制数据共有 904 bits,需要根据 Cell 的 bits descriptor 信息才知道 padding 的比特位数是 3,才能知道 Cell 0 真正比特位数是 901:

0x880119e26ed9ec68605b78dbf4e024f717f67a381d1578b99e026ac4f16a9ba6306802ee8e2b3f71ddcf12c08f47a2a014393b7864e300a4f3ce093e710dd940567482f1ca7943e9bcc3ec472d54e97c3aa4a7f686b7b6ab519692694bb8ada44e78094d4d18bb328e7e2000000018001c

Cell 0 数据可分解为下面更易读的形式:

external   <br>  message    <br>  header     <br>  (275 bits) magic bits 0b10 10 表示 ext_in_msg_info,https://ton.org/tblkch.pdf
src addr 0b00 00 表示 none addr
dest <br>  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  <br>  (624  <br>  bits) signature              <br>  (512 bits) 0x5dd1c567ee3bb9e25811e8f4540287276f0c9c60149e79c127ce21bb280ace90 <br>  0x5e394f287d37987d88e5aa9d2f875494fed0d6f6d56a32d24d297715b489cf01 Ed25519 签名数据,后文会介绍它是如何计算的
signing <br>  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#L73https://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   <br>  message    <br>  header     <br>  (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     <br>  addr addr flag 0b10 10 表示 std addr
anycast 0b0
workchain 0b00000000 workchain
addr hash 0x9cea5b3ef98661f16a52afa9b507d917e04b0d7e6137c43189f77c7169174ec4 接收者的 raw 地址
currency <br>  collect- <br>  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       <br>  (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 <br>  cells cell 0 refs descriptor 03 当前 cell 有 3 个引用,当前 cell 是 ordinary cell
bits descriptor e7 序列化后的 cell data 占用 ceil(231/2) = 116 字节
cell data 8800ddd3b93ce0a482ae71da988a2699ee334ba4c2107390621ba74707b29681d6ac118e6d2cedcfffffffbfffffffe00000 <br>  0016f26f5094ed2143c228d7bae9ab6368a222621799c40b800315c8a4754d90bc9ff1e8bc2cbab29d0b192025d851b106e7 <br>  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 <br>  74d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d7 <br>  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   <br>  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   <br>  11 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 17 序列化后的 cell data 占用 ceil(23/2) = 12 字节
cell data b325fb51341c75c875c2c7e0 cell data。由于 0x17 为奇数,有补齐
cell   <br>  12 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 11 序列化后的 cell data 占用 ceil(17/2) = 9 字节
cell data b262fb513435c28020 cell data。由于 0x11 为奇数,有补齐
cell   <br>  13 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 19 序列化后的 cell data 占用 ceil(25/2) = 13 字节
cell data be5f0f6a2684080a0eb90fa02c cell data。由于 0x19 为奇数,有补齐
cell   <br>  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   <br>  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   <br>  16 refs descriptor 01 当前 cell 有 1 个引用,当前 cell 是 ordinary cell
bits descriptor e6 序列化后的 cell data 占用 ceil(230/2) = 115 字节
cell data 8ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a <br>  28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f <br>  01cf16c9ed542092f80fde70db3cd8 cell data。由于 0xe6 为偶数,没有补齐
cell refs 11 所引用的 cell index 为 0x11
cell   <br>  17 refs descriptor 03 当前 cell 有 3 个引用,当前 cell 是 ordinary cell
bits descriptor f6 序列化后的 cell data 占用 ceil(246/2) = 123 字节
cell data eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e0 <br>  9320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0eb <br>  d72c08142091709601d72c081c12e25210b1e30f20d74a cell data。由于 0xf6 为偶数,没有补齐
cell refs 121314 所引用的 cell index 为 0x12/0x13/0x14
cell   <br>  18 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 96 序列化后的 cell data 占用 ceil(150/2) = 75 字节
cell data 01fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e0 <br>  8c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54 cell data。由于 0x96 为偶数,没有补齐
cell   <br>  19 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 72 序列化后的 cell data 占用 ceil(114/2) = 57 字节
cell data 30d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9 <br>  ed5493f2c08de2 cell data。由于 0x72 为偶数,没有补齐
cell   <br>  20 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 10 序列化后的 cell data 占用 ceil(16/2) = 8 字节
cell data 935bdb31e1d74cd0 cell data。由于 0x10 为偶数,没有补齐
cell   <br>  21 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 51 序列化后的 cell data 占用 ceil(81/2) = 41 字节
cell data 800000003ffffffed941b029f3d7258ffec8cee42140edb93113f0926990bb60942e322c298989d0a0 cell data。由于 0x51 为奇数,有补齐
cell   <br>  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   <br>  23 refs descriptor 00 当前 cell 没个引用,当前 cell 是 ordinary cell
bits descriptor 00 序列化后的 cell data 占用 ceil(0/2) = 0 字节
cell   <br>  24 refs descriptor 00 当前 cell 没有引用,当前 cell 是 ordinary cell
bits descriptor 86 序列化后的 cell data 占用 ceil(134/2) = 67 字节
cell data 4200091f71377aeb179592172bf5e817c3051c235edcb9b2592c3a548b6a7355c476a24c6ac4000000000000000000000000 <br>  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   <br>  message    <br>  header     <br>  (275 bits) magic bits 0b10 10 表示 ext_in_msg_info,https://ton.org/tblkch.pdf
src addr 0b00 00 表示 none addr
dest <br>  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 <br>  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                  <br>  (512 bits) 0xde4dea129da42878451af75d356c6d14444c42f33881700062b9148ea9b21793 <br>  0xfe3d1785975653a1632404bb0a3620dceda0c762227d060a95269633cb976507 Ed25519 签名数据
cell padding 0b100 按字节补齐。如果 Cell 的 bits descriptor 为奇数,表示有 padding

从上面的分析中可知 Cell 0 共有 3 个 refs(Cell 0 的 cell refs 数据为 0x011516):

  1. Init Code,对应的 Cell Index 是 0x01,即 Cell 1

  2. Init Data,对应的 Cell Index 是 0x15,即 Cell 21

  3. 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 分别为:

  1. Cell 23,这是一个空 Cell,是序列化 Out List 时的首个 Cell。

  2. 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   <br>  message    <br>  header     <br>  (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     <br>  addr addr flag 0b10 10 表示 std addr
anycast 0b0
workchain 0b00000000 workchain
addr hash 0x123ee26ef5d62f2b242e57ebd02f860a3846bdb97364b25874a916d4e6ab88ed 接收者的 raw 地址
currency <br>  collect- <br>  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    <br>  body       <br>  (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
                 ^                                                                                                           ^
                 |                                                                                                           |
                 v                                                                                                           v
EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC (USDT jetton wallet)                                 EQCQZAeLOM3kwjdn1CTVmRThDro138uxqvGeLfB1sFTNhA5J (USDT jetton wallet)

提交 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 <br>  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 <br>  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 <br>  d40e08d259c09fac807e2b430086295c6e941485ea792cf54d5c4d0202000000007465737420636f6d6d656e74 cell data
crc32c 0897876e crc32c 校验码
1.3.1. Cell 0

前面介绍中提到了 Cell 0 由下面数据组成:

880143c6b0207ea0704692ce04fd6403f15a1804314ae374a0a42f53c967aa6ae2680c

下面把 Cell 0 数据分解为更易读的形式:

external   <br>  message    <br>  header     <br>  (275 bits) magic bits 0b10 10 表示 ext_in_msg_info,https://ton.org/tblkch.pdf
src addr 0b00 00 表示 none addr
dest <br>  addr addr flag 0b10 10 表示 std addr
anycast 0b0
workchain 0b00000000 workchain
addr hash 0xa1e358103f5038234967027eb201f8ad0c0218a571ba505217a9e4b3d5357134 发送者的 raw 地址,UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_
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 <br>  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                  <br>  (512 bits) 0x614bbcf991d92bfc166fda2307efa1e6dcf689eedee0692d5997ebe3be7325c9 <br>  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 分别为:

  1. Cell 3,这是一个空 Cell,是序列化 Out List 时的首个 Cell。

  2. Cell 4,Send Message 主要内容在这个 Cell 中。

Cell 4 数据(62007070046fb58a00ab6282dcf3d3c854a3970c2c2d5b80de1099b6240fd69e93e3202faf080000000000000000000000000001)可分解为下面更易读的形式:

internal   <br>  message    <br>  header     <br>  (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     <br>  addr addr flag 0b10 10 表示 std addr
anycast 0b0
workchain 0b00000000 workchain
addr hash 0xe0e008df6b140156c505b9e7a790a9472e18585ab701bc21336c481fad3d27c6 消息接收者地址(EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC),即 USDT 发送者的 jetton wallet
currency <br>  collect- <br>  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    <br>  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    <br>  addr addr flag 0b10 10 表示 std addr
anycast 0b0
workchain 0b00000000 workchain
addr hash 0x14de8e7e4a9d4c611a70dd4edbff0980471a88e3d2f1885844a150b1a542a34d jetton 的 new owner 地址(UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk)
resp    <br>  dest    <br>  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 <br>  ton     <br>  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          # pip install pynacl
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 时 别人部署

Table 1: 钱包帐户合约部署时机 VS Jetton 合约部署时机

考虑场景:Address 1 往 Address 2 转账 Jetton USDT,这会涉及到几个合约的部署呢?

  1. Address 1 曾经往外发过交易(帐户合约已经部署),Address 2 以前收过 Jetton USDT。这时不会有合约被部署。

  2. Address 1 曾经往外发过交易(帐户合约已经部署),但 Address 2 以前没有收过 Jetton USDT。在这种情况下这个 Tx 会部署一个合约,即别人(即 Address 2)的 Jetton USDT 合约。

  3. Address 1 以前没有往外发过交易(Address 1 帐户合约未部署过),Address 2 以前收过 Jetton USDT。在这种情况下这个 Tx 会部署一个合约,即自己(即 Address 1)的帐户合约。

  4. Address 1 以前没有往外发过交易(Address 1 帐户合约未部署过),而且恰好 Address 2 是首次收到 Jetton USDT。在这种情况下这个 Tx 会部署两个合约:一是自己(即 Address 1)的帐户合约;二是部署别人(即 Address 2)的 Jetton USDT 合约。链上就有这样的例子,比如 Tx 00defd4857b646c31b46049c1c8f61cc17d6cf3df1d0a22f40e325126dd2296c

注意:上面讨论的是最简单的场景,没有考虑 Mintless Jetton 的情况,也没有考虑由于 Transfer Notification 触发其它合约部署的情况。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
cig01
cig01
https://aandds.com/