TVM 升级 2023.07
此升级于 2023 年 12 月在主网上启动,详细信息请参考 run。
c7
c7 是存储有关合约执行所需的本地 context 信息的寄存器 (如时间、lt、网络配置等)。
c7 元组从 10 扩展到 14 个元素:
- 10: 存储智能合约本身的
cell
。 - 11:
[integer, maybe_dict]
:传入消息的 TON 值,额外代币。 - 12:
integer
,存储阶段收取的费用。 - 13:
tuple
包含有关先前区块的信息。
10 当前智能合约的代码仅以可执行继续的形式在 TVM 级别呈现,无法转换为cell。这段代码通常用于授权相同类型的 neighbor 合约,例如 Jetton 钱包授权 Jetton 钱包。目前我们需要显式地代码cell存储在存储器中,这使得存储和 init_wrapper 变得更加麻烦。 使用 10 作为代码对于 tvm 的 Everscale 更新兼容。
11 当前,传入消息的值在 TVM 初始化后以堆栈形式呈现,因此如果在执行过程中需要,
则需要将其存储到全局变量或通过本地变量传递(在 funC 级别看起来像所有函数中的额外 msg_value
参数)。通过将其放在 11 元素中,我们将重复合约余额的行为:它既出现在堆栈中,也出现在 c7 中。
12 目前计算存储费用的唯一方法是在先前的 交易中存储余额,以某种方式计算 prev 交易中的 gas 用量,然后与当前余额减去消息值进行比较。与此同时,经常希望考虑存储费用。
13 目前没有办法检索先前区块的数据。TON 的一个关键特性是每个结构都是 Merkle 证明友好的cell(树),此外,TVM 也是cell和 Merkle 证明友好的。通过在 TVM context中包含区块信息,将能够实现许多不信任的情景:合约 A 可以检查合约 B 上的交易(无需 B 的合作),可以恢复中断的消息链(当恢复合约获取并检查某些事务发生但被还原的证明时),还需要了解主链区块哈希以在链上进行某些验证 fisherman 函数功能。
区块 id 的表示如下:
[ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer ] = BlockId;
[ last_mc_blocks:[BlockId0, BlockId1, ..., BlockId15]
prev_key_block:BlockId ] : PrevBlocksInfo
包括主链的最后 16 个区块的 id(如果主链 seqno 小于 16,则为少于 16 个),以及最后的关键区块。包含有关分片区块的数据可能会导致一些数据可用性问题(由于合并/拆分事件),这并非必需(因为可以使用主链区块来证明任何事件/数据),因此我们决定不包含。
新的操作码
在选择新操作码的 gas 成本时的经验法则是它不应少于正常成本(从操作码长度计算)且不应超过每个 gas 单位 20 ns。
用于处理新 c7 值的操作码
每个操作码消耗 26 gas,除了 PREVMCBLOCKS
和 PREVKEYBLOCK
(34 gas)。
xxxxxxxxxxxxxxxxxxxxxx Fift 语法 | xxxxxxxxx 堆栈 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 描述 |
---|---|---|
MYCODE | - c | 从 c7 检索智能合约的代码 |
INCOMINGVALUE | - t | 从 c7 检索传入消息的值 |
STORAGEFEES | - i | 从 c7 检索存储阶段费用的值 |
PREVBLOCKSINFOTUPLE | - t | 从 c7 中检索 PrevBlocksInfo: [last_mc_blocks, prev_key_block] |
PREVMCBLOCKS | - t | 仅检索 last_mc_blocks |
PREVKEYBLOCK | - t | 仅检索 prev_key_block |
GLOBALID | - i | 从网络配置的第 19 项检索 global_id |
Gas
xxxxxxxxxxxxxx Fift 语法 | xxxxxxxx 堆栈 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 描述 |
---|---|---|
GASCONSUMED | - g_c | 返回到目前为止 VM 消耗的 gas(包括此指令)。 26 gas |
算术
New variants of the division opcode (A9mscdf
) are added:
d=0
takes one additional integer from stack and adds it to the intermediate value before division/rshift. These operations return both the quotient and the remainder (just like d=3
).
还提供了静默变体(例如 QMULADDDIVMOD
或 QUIET MULADDDIVMOD
)。
如果返回值不适应 257 位整数或除数为零,非静默操作会引发整数溢出异常。静默操作返回 NaN
而不是不适应的值(如果除数为零则返回两个 NaN
)。
Gas 成本等于 10 加上操作码长度:大多数操作码为 26 gas,LSHIFT#
/RSHIFT#
额外加 8,静默额外加 8。
xxxxxxxxxxxxxxxxxxxxxx Fift 语法 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 堆栈 |
---|---|
MULADDDIVMOD | x y w z - q=floor((xy+w)/z) r=(xy+w)-zq |
MULADDDIVMODR | x y w z - q=round((xy+w)/z) r=(xy+w)-zq |
MULADDDIVMODC | x y w z - q=ceil((xy+w)/z) r=(xy+w)-zq |
ADDDIVMOD | x w z - q=floor((x+w)/z) r=(x+w)-zq |
ADDDIVMODR | x w z - q=round((x+w)/z) r=(x+w)-zq |
ADDDIVMODC | x w y - q=ceil((x+w)/z) r=(x+w)-zq |
ADDRSHIFTMOD | x w z - q=floor((x+w)/2^z) r=(x+w)-q*2^z |
ADDRSHIFTMODR | x w z - q=round((x+w)/2^z) r=(x+w)-q*2^z |
ADDRSHIFTMODC | x w z - q=ceil((x+w)/2^z) r=(x+w)-q*2^z |
z ADDRSHIFT#MOD | x w - q=floor((x+w)/2^z) r=(x+w)-q*2^z |
z ADDRSHIFTR#MOD | x w - q=round((x+w)/2^z) r=(x+w)-q*2^z |
z ADDRSHIFTC#MOD | x w - q=ceil((x+w)/2^z) r=(x+w)-q*2^z |
MULADDHIFTMOD | x y w z - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z |
MULADDRSHIFTRMOD | x y w z - q=round((xy+w)/2^z) r=(xy+w)-q*2^z |
MULADDHIFTCMOD | x y w z - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z |
z MULADDRSHIFT#MOD | x y w - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z |
z MULADDRSHIFTR#MOD | x y w - q=round((xy+w)/2^z) r=(xy+w)-q*2^z |
z MULADDRSHIFTC#MOD | x y w - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z |
LSHIFTADDDIVMOD | x w z y - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq |
LSHIFTADDDIVMODR | x w z y - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq |
LSHIFTADDDIVMODC | x w z y - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq |
y LSHIFT#ADDDIVMOD | x w z - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq |
y LSHIFT#ADDDIVMODR | x w z - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq |
y LSHIFT#ADDDIVMODC | x w z - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq |
堆栈操作
目前,所有堆栈操作的参数都以 256 为界。
这意味着如果堆栈深度超过 256,就很难管理深堆栈元素。
在大多数情况下,这种限制并没有安全方面的原因,也就是说,限制参数并不是为了防止过于昂贵的操作。
对于某些大规模堆栈操作,如 ROLLREV
(计算时间与参数值成线性关系),气体成本也与参数值成线性关系。
- 现在,
PICK
、ROLL
、ROLLREV
、BLKSWX
、REVX
、DROPX
、XCHGX
、CHKDEPTH
、ONLYTOPX
、ONLYX
的参数不受限制。 - 当参数较大时,
ROLL
,ROLLREV
,REVX
,ONLYTOPX
耗气量更大:额外耗气量为max(arg-255,0)
(参数小于 256 时,耗气量不变,与当前行为一致) - 对于
BLKSWX
,额外费用为max(arg1+arg2-255,0)
(这与当前行为不符,因为当前arg1
和arg2
都限制为 255)。
哈希值
目前 ,TVM 只提供两种散列操作:计算 cell /片的表示散列和数据的 sha256,但最多只能计算 127 字节(一个 cell 只能容纳这么多数据)。
HASHEXT[A][R]_(HASH)
系列操作被添加:
xxxxxxxxxxxxxxxxxxx Fift 语法 | xxxxxxxxxxxxxxxxxxxxxx 堆栈 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 说明 |
---|---|---|
HASHEXT_(HASH) | s_1 ... s_n n - h | 计算并返回片段(或构建器)s_1...s_n 的连接哈希值。 |
HASHEXTR_(HASH) | s_n ... s_1 n - h | 同理,但参数顺序相反。 |
HASHEXTA_(HASH) | b s_1 ... s_n n - b' | 将生成的哈希值追加到构造函数 b 中,而不是推送到堆栈中。 |
HASHEXTAR_(HASH) | b s_n ... s_1 n - b' | 参数以相反顺序给出,并将哈希值追加到生成器中。 |
仅使用 s_i
的根cell的位。
每个块 s_i
可能包含非整数数量的字节。但所有块的位的和应该是 8 的倍数。注意 TON 使用最高位优先顺序,因此当连接两个具有非整数字节的 slice 时,第一个 slice 的位变为最高位。
Gas 消耗取决于哈希字节数和所选算法。每个块额外消耗 1 gas 单位。
如果未启用 [A]
,则哈希的结果将作为无符号整数返回,如果适应 256 位,否则返回整数的元组。
可用以下算法:
SHA256
- openssl 实现,每字节 1/33 gas,哈希为 256 位。SHA512
- openssl 实现,每字节 1/16 gas,哈希为 512 位。BLAKE2B
- openssl 实现,每字节 1/19 gas,哈希为 512 位。KECCAK256
- 以太坊兼容实现,每字节 1/11 gas,哈希为 256 位。KECCAK512
- 以太坊兼容实现,每字节 1/6 gas,哈希为 512 位。
Gas 用量四舍五入。
加密货币
目前唯一可用的加密算法是 "CCHKSIGN":检查哈希 "h "与公钥 "k "的 Ed25519 签名。
- 为了与比特币和以太坊等上一代区块链兼容,我们还需要检查
secp256k1
签名。 - 对于现代密码算法,最低要求是曲线的加法和乘法。
- 为了与以太坊 2.0 PoS 和其他现代密码学的兼容性,我们需要在 bls12-381 曲线上进行 BLS 签名方案。
- 对于某些安全硬件,需要
secp256r1 == P256 == prime256v1
。
secp256k1
比特币/以太坊签名。使用 libsecp256k1 实现。
xxxxxxxxxxxxx Fift 语法 | xxxxxxxxxxxxxxxxx 堆栈 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 说明 |
---|---|---|
ECRECOVER | hash v r s - 0 or h x1 x2 -1 | 从签名恢复公钥,与比特币/以太坊操作相同。 以 32 字节哈希作为 uint256 hash ;以 65 字节签名作为 uint8 v 和 uint256 r 、s 。失败返回 0 ,成功返回公钥和 -1 。以 65 字节公钥返回为 uint8 h ,uint256 x1 、x2 。1526 gas |
secp256r1
使用 OpenSSL 实现。界面类似于 CHKSIGNS
/CHKSIGNU
。与 Apple Secure Enclave 兼容。
xxxxxxxxxxxxx Fift 语法 | xxxxxxxxxxxxxxxxx 堆栈 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 说明 |
---|---|---|
P256_CHKSIGNS | d sig k - ? | 检查片段 d 的数据部分和公钥 k 的 seck256r1-signature sig 。成功时返回-1,失败时返回 0。公钥是一个 33 字节的片段(根据 SECG SEC 1 第 2.3.4 节第 2 点编码)。 签名 sig 是一个 64 字节的片段(两个 256 位无符号整数 r 和 s )。3526 gas |
P256_CHKSIGNU | h sig k - ? | 相同,但签名的数据是 32 字节对 256 位无符号整数 h 的编码。3526 gas |
Ristretto
扩展文档 此处。简而言之,Curve25519 在开发时考虑到了性能,但由于组元素有多种表示形式,因此它具有对称性。较简单的协议(如 Schnorr 签名或 Diffie-Hellman 协议)在协议层面采用了一些技巧来缓解某些问题,但却破坏了密钥推导和密钥保密方案。这些技巧无法扩展到更复杂的协议,如防弹协议。Ristretto 是 Curve25519 的算术抽象,每个组元素对应一个唯一的点,这是大多数加密协议的要求。Ristretto 本质上是 Curve25519 的压缩/解压缩协议,提供了所需的算术抽象。因此,加密协议很容易正确编写,同时还能受益于 Curve25519 的高性能。
Ristretto 操作允许在 Curve25519 上计算曲线操作(反之则不行),因此我们可以认为我们在一个步骤中同时添加了 Ristretto 和 Curve25519 曲线操作。
libsodium 实现已被使用。
所有 ristretto-255 点都在TVM中表示为 256 位无符号整数。非静默操作在参数无效的情况下引发 range_chk
。零点表示为整数 0
。
xxxxxxxxxxxxx Fift 语法 | xxxxxxxxxxxxxxxxx 堆栈 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 说明 |
---|---|---|
RIST255_FROMHASH | h1 h2 - x | 从 512 位哈希(由两个 256 位整数给出)确定性生成有效点 x 。626 gas |
RIST255_VALIDATE | x - | 检查整数 x 是否是某个曲线点的有效表示。出错时会抛出 range_chk 。226 gas |
RIST255_ADD | x y - x+y | 在曲线上两个点的相加。 626 gas |
RIST255_SUB | x y - x-y | 在曲线上两个点的相减。 626 gas |
RIST255_MUL | x n - x*n | 将点 x 乘以标量 n 。任何 n 都有效,包括负数。2026 gas |
RIST255_MULBASE | n - g*n | 将生成器点 g 乘以标量 n 。任何 n 都有效,包括负数。776 gas |
RIST255_PUSHL | - l | 推送整数 l=2^252+27742317777372353535851937790883648493 ,这是群的阶。26 gas |
RIST255_QVALIDATE | x - 0 或 -1 | RIST255_VALIDATE 的静默版本。234 gas |
RIST255_QADD | x y - 0 或 x+y -1 | RIST255_ADD 的静默版本。634 gas |
RIST255_QSUB | x y - 0 或 x-y -1 | RIST255_SUB 的静默版本。634 gas |
RIST255_QMUL | x n - 0 或 x*n -1 | RIST255_MUL 的静默版本。2034 gas |
RIST255_QMULBASE | n - 0 或 g*n -1 | RIST255_MULBASE 的静默版本。784 gas |
BLS12-381
在配对友好的 BLS12-381 曲线上进行操作。使用 BLST 实现。此外,还对基于该曲线的 BLS 签名方案进行了操作。
BLS 值在 TVM 中的表示方法如下:
- G1点和公钥:48字节 slice 。
- G2点和签名:96字节 slice 。
- 字段FP的元素:48字节 slice 。
- 字段FP2的元素:96字节 slice 。
- 信息: slice 。位数应能被 8 整除。
当输入值是一个点或一个字段元素时,片段的长度可能超过 48/96 字节。在这种情况下,只取前 48/96 字节。如果片段的字节数少于 48/96(或信息大小不能被 8 整除),则会出现 cell 下溢异常。