深层解读白皮书——Move:Facebook Libra区块链的新编程语言
附注:部份英文专有名词无对应中文翻译,原文作者以及Blockore团队为其保留翻译之准确空间
概述和动机
这篇文章带领大家初步了解Facebook Libra新编程语言 Move 的26页 技术白皮书 。作为以太坊开发人员和区块链社区爱好者,我希望为每个对这种新语言感到好奇的人,快速的介绍这篇文章,让大家了解他的亮点:)希望大家会喜欢!
1. 摘要
Move是一种可执行的位元组码的语言,用于执行自定义事项和智能合约。相较于Solidity,有两件事需要注意:
- Move是一种位元组码语言,可以在Move的VM中直接执行,然而Solidity(以太坊的智能合约语言)是一种更高级别的语言,需要在EVM(以太坊的虚拟机)执行之前编译成位元组码。
- Move不仅可用于执行智能合约,还可用于自定义事项(本文稍后将对此进行说明),而Solidity仅适用于以太坊上的智能合约。
这是一个类似于Rust的功能。Rust中的值一次只能分配给一个名称。如果将值分配给一个不同的名称,那将会导致无法再使用以前的名称访问该值。
例如,以下代码段将输出错误: Use of moved value 'x' 。这是因为Rust没有垃圾回收。当变量超出范围时,它们引用的内存也会被释放。简单来说,我们可以理解为数据一次只能有一个「所有者」。在以下的示例中,x是原始所有者,然后y成为所有者。
参考: http://squidarth.com/rc/rust/2018/05/31/rust-borrowing-and-ownership.html
2. 在开放系统中编码数字资产
物理资产有两个属性难以在数字资产中编码:
- 稀缺性:应控制系统中的资产供应。复制现有资产应当被禁止,且创建新资产应该为一种特权。
- 资产拥有权的权限管理:系统中的参与者资产应该能受到资产控管条例的保护。
为了说明我们如何提出这两个属性,让我们从以下提案开始说明:
提案#1:最简单的规则,没有稀缺性和权限管理
最简单的状态评估规则,没有稀缺性和权限管理。
- G[K]:=n表示将储存在全区域区块链,密钥K中的值更新为K
- transaction ⟨Alice, 100⟩表示将Alice的账户余额设置为100。
- Alice可以通过发送transaction ⟨Alice, 100⟩,让自己拥有无限的硬币。
- Alice发送给Bob的硬币毫无价值,因为Bob可以使用相同的技术向自己发送无限量的代币。
第二个提案只考虑稀缺性
现在我们强制将在转移之前存储在??下的硬币数量设定至少为?。
然而,虽然这解决了稀缺问题,但是对于可以发送给Alice的货币的人没有做所有权的检查。(任何人都可以根据此评估规则这样做)
提案#3:同时考虑稀缺性和权限管理
第三个提案同时考虑稀缺性和权限管理
我们透过在检查稀缺性之前使用数字签章机制verify_sig来处理这个问题。这意味着Alice使用她的私钥来签署交易,并证明她是代币的所有者。
2.1 现有的区块链语言
现有的区块链语言面临以下问题(所有这些问题都已在Move中解决):- 间接代表资产:资产使用整数进行编码,但整数值与资产本质上并不相同。事实上,目前没有任何类型或数值代表Bitcoin/Ether/StrawCoin!这使得编写使用资产的程序变得不便且容易出错。诸如将资产传入或传出的过程,或是将资产存储在数据结构中等的模式会需要编程语言的一些特殊支持。
- 稀缺性是不可扩展的:这种语言只代表一种稀缺资产。此外,稀缺性的保护是直接在编程语言的语义中进行硬编码而成的。程序员在希望创建自定义资产时,必须在没有语言支持的情况下,谨慎地实现稀缺性这一特性。
此外,由于资产问题的间接表示,更有可能引入重复、重用或资产损失等严重错误。
- 权限管理不灵活:现有的模型架构,强制执行权限管理的唯一策略,是基于公钥的签章的模式。与稀缺性保护一样,权限管理的策略也深深嵌入编程语言的语义中。关于如何能扩展语言,并允许程序员制定自定义的权限管理策略,这个答案并不明显。
尽管我是以太坊的忠实粉丝,但我同意这些资产属性应该由语言本身支持实现,以达到安全目的。
特别是,将以太转移到智能合约涉及动态调度,这导致了一类新的错误,称为重新入侵漏洞
动态调度 在这里指代码执行逻辑将在运行时(动态)而不是编译时(静态)确定。因此,在Solidity中,当合约A调用合约B的功能时,合约B可以运行合约A的设计者未预料到的代码,这可能导致 重新入侵的漏洞 (合约A意外执行合约B的功能,以便在实际扣除帐户余额之前提取资金)。
3. Move的设计理念
3.1 一流的资源
在较高的层次上,Move中modules(模组)/resources(资源)/procedure(程序)之间的关系类似于物件导向程式中classes/objects/methods之间的关系。Move的模组类似于其他区块链语言中的智能合约。module声明resource类型和procedure。这些资源类型和程序用于编码创建,销毁和更新其声明的资源时的规则。modules/resources/procedure只是Move中的一些术语。我们将在本文后面用一个例子来说明这些;)
3.2 灵活性
Move通过(交易脚本)为Libra增加了灵活性。每个Libra交易都包含一个transaction script,它实际上是事务的主要过程。scripts可以执行expressive one-off behaviors(例如支付特定的一组收件人)或可重用行为(通过调用单个程序,此程序封装了可重用逻辑)
从上面我们可以看出, Move的transcation script引入了更多的灵活性,因为它能够实现一次性行为以及可重用行为,而以太坊只能执行可重用行为(这是一种只调用单一智能合约的方法)。它被命名为「可重用」的原因是因为智能合约功能可以多次执行。
3.3 安全
Move的执行格式是一种类型化的(typed)位元组码,它比汇编语言(assembly language)更高级但比源语言(source language)更低级。位元组码通过位元码验证器在链上检查资源,类型和记忆体安全性,然后由位元组码解释器直接执行。此选项允许Move提供通常与源语言相关的安全保证,但不将源编译器添加到可信任的计算库或编译的成本到执行交易的关键路径。对于Move来说,成为位元组码语言是一种非常简洁的设计。由于它不需要像Solidity一样,从源代码编译为位元组码,因此不必担心在编译器中可能出现的故障或攻击。
3.4 可验证性
我们的方法是尽可能多地执行关键安全属性的轻量级的链上验证,但同时设计Move可支持进阶链下的静态验证工具。从这里我们可以看到Move更喜欢执行静态验证而不是进行链上验证。尽管如此,正如他们在论文末尾所述,验证工具留在未来进行开发。
3.5 模组化
Move模组强制执行data abstraction(数据抽象)并将在资源上的关键操作本地化。模组利用的封装,结合Move系统强制执行的保护,可确保模组类型的属性不会受到模组外部的代码的影响。这也是一个非常好的数据抽象设计!这意味着智能合约中的数据只能在合约范围内修改,而不能从外部修改。
来自: https : //libra.org/en-US/open-source-developers/#move_carousel
4. Move概述
此示例的交易脚本演示了一个恶意或粗心程序员,在模组外部不能违反模组资源的关键安全不变量。
本节将透过一个示例,向您介绍在编写编程语言时的modules(模组),resources(资源)和procedure(程序)到底是什么。
4.1 点对点付款交易脚本
货币金额将从交易发送方转移到收款方
这里有几个新符号(红色小文字是我自己的笔记XD):
- 0x0 :存储module的帐户地址
- Currency :module的名称
- Coin :resource类型
- coin是一个被procedure返回的值。它是一个类型为0x0.Currency.Coin的resource值
- move() :该值不能再次使用
- copy() :该值可以在以后使用
在第一步中,发送方从存储在0x0.Currency的module中,调用名为withdraw_from_sender的procedure。
在第二步中,发送者通过将货币的资源值移动到0x0.Currency module的存款这动作,将资金转移到收款人。
以下是3种将被拒绝的代码示例:
1. 将move(coin)改为copy(coin)来复制货币金额
资源值只能被移动。尝试复制资源值(例如,在上面的示例中使用copy(coin) )将导致在位元组码验证时出错。
因为coin是资源值,所以它只能被移动。
2. 透过两次move(coin) 来重新使用货币
将0x0.Currency.deposit(copy(some_other_payee),move(coin))添加到上面的示例中会让发件人“花”两次货币- 第一次与收款人,第二次与some_other_payee。这种不良行为在物理资产的情况下无法实现。幸运的是,Move不会允许此类计划的实施。
3. 透过删除move(coin) 丢失货币
移动资源失败时(例如,通过将上面示例中包含move(coin)的那行代码删除)将触发位元组码验证错误。这可以防止使用Move的程序员不会意外地或有意地丢失对资源的跟踪。
4.2 Currency(货币)模组
4.2.1 入门开始:Move execution model(执行模型)
每个帐户可以包含零个或多个模组(为上图矩形)和一个或多个资源值(为上图圆柱体)。例如,在地址0x0的帐户有模组0x0.Currency和类型为0x0.Currency.Coin的资源值。在地址0x1的帐户有两个资源值和一个模组; 在地址0x2的帐户有两个模组和一个资源值。
一些值得注意的点:
- 执行交易脚本是全有或全无的
- 模组是在全域状态下发布的长期代码
- 全域状态的结构为从帐户地址到帐户的map
- 帐户最多只能包含一个给定类型的资源值,和一个具有给定名称的模组(在地址0x0的帐户不允许包含其他的类型为0x0.Currency.Coin的资源或另一个名为Currency的模组)
- declaring module(声明模组)的地址算是类型的一部分( 0x0.Currency.Coin和0x1.Currency.Coin是不能互换使用的不同类型)
- 程序员仍然可以通过定义自定义一个包装好的资源,来保有帐户中给定资源类型的多个实例( resource TwoCoins { c1: 0x0.Currency.Coin, c2: 0x0.Currency.Coin } )
- 规则是可以的,只要您仍然可以通过其名称引用资源而不会发生冲突,例如您可以使用TwoCoins.c1和TwoCoins.c2引用这两个资源。
一个名为Currency的模块组和一个由模组管理的名为Coin的资源类型
一些值得注意的点:
- Coin是一种结构类型,其单个字段(field)值类型为u64(64位无符号整数)
- 只有Currency module(模组)的procedure(程序)才能创建或销毁Coin类型的值
- 其他module(模组)和transaction script(交易脚本)只能透过模组公开的procedure(程序)写入或引用值的字段
此过程将Coin(货币)资源作为input(输入),并透过以下步骤将其与存储在收款人帐户中的Coin资源组合:
- 销毁输入Coin并记录其值。
- 获取对存储在收款人帐户下的独一的Coin资源的引用。
- 通过将Coin的值传递给procedure(程序),来增加收款人Coin的值。
- Unpack, BorrowGlobal是内置procedure(程序)
- Unpack<T>是删除类型为T的资源的唯一方法。它将类型为T的资源作为输入,销毁它,并返回绑定到资源的字段的值
- BorrowGlobal<T>将地址作为输入,并返回对在此地址下发布的唯一类型T实例的引用&mut Coin是对Coin资源的可变引用,而不是对Coin
这个程序(procedure):
- 获取对在发件人帐户下发布的Coin类型的唯一资源的引用。
- 透过输入量来减少引用的Coin的值。
- 创建并返回值为金额的新Coin。
- 任何人都可以调用Deposit ,但withdraw_from_sender有限制,它只能被硬币所有者调用的权限管理
- GetTxnSenderAddress类似于Solidity里的msg.sender
- RejectUnless类似于Solidity里的require 。如果此检查失败,则当前交易脚本的执行将停止,并且它执行的任何操作都不会应用于全域状态
- Pack<T> ,也是一个内置procedure(过程) ,它用于创建一个T类型的新资源 \ 与Unpack<T>相似, Pack<T>只能在资源T的声明模组内调用
总结
现在您已经初步了解了Move的主要特征,它与以太坊的相较的区别,以及其基本语法。
最后,我强烈建议您阅读 白皮书 的原文。它包含了许多关于编程语言设计原则的细节以及许多很好的参考资料。
非常感谢您的阅读时间。欢迎任何建议!
作者:李婷婷
转译: Blockore