伯克利女程序员硬核详解 Libra 编程语言 Move 的主要特征及基本语法
原文标题:《Whitepaper Deep Dive — Move: Facebook Libra Blockchain’s New Programming Language》
作者:Lee Ting Ting,加州大学伯克利分校区块链实验室研究学者,Turing Chain 联合创始人兼首席技术官
编译:Guoxi
责编:Aholiab
Lee Ting Ting,加州大学伯克利分校区块链实验室研究学者,Turing Chain 联合创始人兼首席技术官
自去年矿难以来,业界充满了对区块链唱衰的声音,链圈有很多人都开始对区块链的价值产生怀疑。而 Facebook 不断爆出的区块链项目的消息可谓是给链圈打了一剂强心针,盼望着,盼望着,今年 6 月,Facebook 发布了加密货币 Libra 的白皮书并上线了官网。可以预见, Libra 离落地不远了。
Libra 以非营利组织的形式管理,其创始成员包括大名鼎鼎的银行卡巨头 Visa,万事达,以及传统在线支付巨头 PayPal,颇有挑战当下全球金融货币体系的趋势。
Libra 最近越来越火。不少开发人员已经开始跟进 Libra 项目,希望在这个全球性的区块链项目中抢占先机。而 Libra 的编程语言 Move 则是开发人员眼中的重中之重。
为了帮助开发人员做好入门工作,加州大学伯克利分校区块链实验室研究学者,Turing Chain 联合创始人兼首席技术官 Lee Ting Ting 从开发人员的角度分析了 Move 语言的典型特征以及它与以太坊 Solidity 的异同,为我们带来了这篇 Move 语言的入门指南。
Move 语言的白皮书长达 26 页,本篇文章是对该白皮书的精华介绍,文中也会直接放出一些白皮书上的原文。Move 是 Facebook 公司为其加密货币产品 Libra 开发的全新编程语言。
作为开发人员和区块链社区爱好者,希望通过这篇文章帮助你快速入门 Move 语言。
关于 Move
Move 是一种用于实现 Libra 自定义交易和智能合约的可执行的字节码语言。
它与以太坊 Solidity 语言有以下两个区别:
Move 是一种字节码语言,它可以直接在 Move 虚拟机中运行,而以太坊的 Solidity 语言是一种更高级别的语言,它需要先编译成字节码再加入到以太坊虚拟机中运行。
Move 语言不仅可以用来实现智能合约,还可以用来实现自定义交易(别着急,接下来我们会详细介绍自定义交易),而 Solidity 语言只能用来实现以太坊中的智能合约。
Move 语言的一个关键特性是它能够定义受线性逻辑启发的带语义的自定义资源类型。在这种情况下资源永远不会被复制或被隐式丢弃,它只能在程序的存储位置之间转移。
这是一个与 Rust 语言类似的特性。Rust 语言中的数值一次只能分配给一个命名。如果你将某个数值分配给其他命名则将无法再使用之前的命名访问到该数值。
就比如说,下面这段代码将会输出一个错误:使用转移了的值「x」(Use of moved value ‘x’)。
这是因为 Rust 语言没有垃圾回收机制。当变量超出范围时,变量引用的内存也会被释放。
这样解释有点麻烦,为了简单起见,我们可以这样来理解,每个数据在同一时间内只能有一个「所有者」。在这个例子中,x 是数据初始的所有者,后来 y 也成了数据的所有者,所以程序会报错,如下面代码所示。
在开放系统中编码数字资产
将现实世界中的物理资产编码成区块链上的数字资产,主要存在两大难题:
稀缺性:区块链系统中资产的供应应该受到严格管控。应该禁止复制现有的资产,同时也应该禁止普通用户随意创建新资产。
访问控制:区块链系统中的参与者应该能够使用访问控制策略保护自己的资产。
这也是所有数字资产都需要实现的两个关键特性,这些特性被认为是现实世界中物理资产的自然表征。就比如说,现实世界中稀有金属就是很稀缺的,在现实世界中你只能花属于自己的钱,换句话说就是自己有访问权限的钱。
为了更好地阐述这两个关键特性是如何实现的,让我们先从以下三个示例说起:
示例 1:不考虑稀缺性和访问控制的最简单的规则
左边为交易脚本的格式,右边为区块链状态的评估规则
G [K]:= n 表示使用加密货币数额 n 来更新账户 K 在区块链全局状态中存储的加密货币余额。
transaction⟨Alice,100⟩ 表示将 Alice 的账户余额设置为 100 。
上述的实现方式存在两个很严重的问题:
Alice 可以通过不断发起交易 transaction⟨Alice,100⟩ 让自己拥有无限多的加密货币。
Alice 与 Bob 之间的加密货币转账也变得毫无意义,因为 Bob 也可以使用相同的手段向自己发送无限多的加密货币。
示例 2:在数字资产中加入稀缺性
左边为交易脚本的格式,右边为区块链状态的评估规则
现在我们强制要求在交易发起时发起方 Ka 的账户余额至少为交易的金额 n 。
虽然这种实现方式可以解决稀缺性的问题,但是现在还存在一个问题,就是你可以将任何人的加密货币余额转给自己,这是因为我们还没有加入对谁能发起交易的检查,也就是对加密货币所有权的检查。
示例 3:在数字资产中同时加入稀缺性和访问控制
左边为交易脚本的格式,右边为区块链状态的评估规则
为了实现加密货币的访问控制,我们可以在稀缺性检查之前使用数字签名机制 verify_sig 来检查所交易加密货币的所有者,这意味着 Alice 可以使用她的私钥来签署交易并证明她是所交易加密货币的所有者。
现有的区块链编程语言
现有的区块链编程语言往往都会被以下问题所困扰,令人欣慰的是,Move 语言完美地解决了所有这些问题。主要体现在以下两方面。
间接地表示资产。有些区块链编程语言使用整数对数字资产进行编码。这种编码方式十分牵强,因为这些整数值与数字资产根本就不是一回事。事实上,这些区块链中并没有任何类型或数值来表示比特币 / 以太币 / 山寨币!这使得编写与数字资产交互的智能合约变得十分笨拙且容易出错。在这些区块链中实现诸如资产转入 / 转出以及将资产存储在数据结构中这样的操作都需要特殊的语言支持。
稀缺性是不可扩展的。这些语言往往只能表示一种稀缺资产。除此之外,稀缺性保护直接在语言语义中进行硬编码。开发人员如果想要创建一个自定义的资产,遗憾的是他得不到该语言的一丝帮助,他将不得不重复造轮子,重新实现资产的稀缺性。
相信你可能已经看出来了,这些正是以太坊智能合约中存在的问题。ERC-20 通证等自定义资产使用整数来表示资产和总供应量。每当生成新的通证时,智能合约代码必须手动检查交易是否满足稀缺性(在这种情况下为是否超过总供应量)。
此外,资产的间接表示会给区块链带来很多的问题,就比如说资产复制、资产重复使用、资产意外丢失等漏洞。
当然,除了上面两点以外,还包括:访问控制不够灵活。
这些区块链强制执行的访问控制策略只有基于公钥的数字签名方案。与稀缺性保护一样,访问控制策略也被深深嵌入到语言语义中。
如果开发人员想要设置自定义的访问控制策略,那么他还是会陷入重复造轮子的困境。
以太坊也存在这样的问题。以太坊智能合约缺乏对使用公钥私钥密码学实现访问控制的本地语言支持。面对这种需求。开发人员不得不手动编写访问控制,就比如说使用 OnlyOwner 函数。
尽管我是以太坊的忠实粉丝,但我坚持认为以太坊在这些资产属性方面存在欠缺。从安全方面考虑,这些资产属性本应得到原生的语言支持。
特别是,将以太坊转移到智能合约中需要用到动态分派 (dynamic dispatch,处理编程语言的语言方法调用的一种计算机制),这又会带来一类新的漏洞:可重入性漏洞( re-entrancy vulnerabilities )。
这里的动态分派意味着代码的执行逻辑将在代码运行时(动态)确定,而不是在代码编译时(静态)确定。
因此,在 Solidity 语言中,当智能合约 A 调用智能合约 B 的函数时,智能合约 B 可能会运行智能合约 A 的设计者从未预料到的代码,这可能会导致可重入性的漏洞(智能合约 A 意外执行智能合约 B 的函数,从而在实际更新账户余额之前提取到资金)。
Move 语言的设计目标
一流的资源
从较高的层次上来说,Move 语言中模块 / 资源 / 程序之间的关系类似于面向对象语言中的类 / 对象 / 方法之间的关系。
Move 语言中的模块类似于其他区块链语言中的智能合约。模块声明资源类型和程序,而这些资源类型和程序编码用于创建,销毁和更新所声明资源的规则。
模块 / 资源 / 程序只是 Move 语言中的一些术语。下文中我们将会用一个例子来介绍它们。
灵活性
Move 通过交易脚本为 Libra 增加了很多灵活性。每笔 Libra 交易都包含一个交易脚本,该脚本实际上是交易的核心。
交易脚本可以用来执行一次性的行为(例如给一组特定的收款人付款),也可以用来执行可重用的行为(通过调用一个封装了可重用逻辑的程序)。
从上面我们可以看出,Move 的交易脚本通过同时支持一次性的行为和可重用的行为为 Libra 引入了更多的灵活性,而以太坊只能执行可重用的行为(即调用单个智能合约方法)。
以太坊被称为「可重用」的原因是智能合约中的函数可以被多次执行。
安全性
Move 的可执行格式是一种类型化的字节码,它比汇编语言更高级但比源语言更低级。在区块链上字节码验证器会检查字节码的资源,类型以及内存安全性,然后字节码解释器会直接执行字节码。这种设定使得 Move 在提供与源语言相关联的安全保证的同时,省去了将源编译器添加到可信计算基础 ( Trusted Computing Base,TCB) 以及编译到交易执行的关键路径的成本。
将 Move 构建成一种字节码语言确实是一种非常简洁的设计。由于它不需要像 Solidity 一样从源代码编译成字节码,因此不必担心编译器中可能出现的故障或漏洞。
可验证性
我们的方法是尽可能多地在区块链上执行核心安全属性的轻量级验证,但同时我们也在 Move 语言中加入了对链下高级静态验证工具的支持。
从这里我们可以看出 Move 更倾向于执行静态验证而不是在区块链上执行验证工作。尽管如此,正如白皮书末尾所述,Libra 团队未来将会开发完善验证工具。
模块化
Move 模块强制执行数据抽象并本地化执行资源的关键性操作。模块启用的封装与 Move 类型系统强制执行的保护相结合,强强联手可以确保模块外部的代码不能违反模块类型规定的属性。
这是一个非常好的数据抽象设计!这意味着智能合约中的数据只能在智能合约范围内修改,而不能在外部修改。
Move 语言实操
这个交易脚本的示例说明了模块外部的恶意开发人员或粗心的开发人员不可能违反模块资源的关键安全不变性。
这一部分中我们将讨论在进行 Move 语言开发时,实际使用到的模块、资源和程序分别是什么东西。
点对点支付交易脚本
Move 语言的点对点支付交易脚本,如下面代码所示:
amount (金额)表示所交易加密货币的金额,这些加密货币将从交易的发起方转移给接收方 payee。
代码中有几个新的符号,其中红色的小字是我记的笔记:
0x0:存储模块的帐户地址
currency:模块的名称
coin:资源类型
程序返回的 coin 值是一个类型为0x0.Currency.Coin的资源值
move ():该值不能再次使用
copy ():该值可以再次使用
代码功能解读:
在第一步中,发送方从存储在 0x0.Currency 的模块中调用了名为 withdraw_from_sender 的程序。
在第二步中,发送方通过将加密货币的资源值转移到 0x0.Currency 模块的存款程序中从而将资金转移给收款人。
以下是三种会报错的代码示例:
1. 通过将转移加密货币 move(coin) 替换为复制加密货币 copy(coin) 来复制加密货币。
资源值只能被转移。尝试复制资源值(就比如说示例中使用的复制加密货币 copy(coin) )将在字节码验证时引起错误。
因为 coin 是一个资源值,所以它只能被转移。
2. 通过两次转移加密货币 move(coin) 来重复使用加密货币(双重支付)。
在上述的示例代码中加入一行:
0x0.Currency.deposit (copy (some_other_payee),move (coin))
就可以让发送方两次花费同一笔加密货币,第一次交易的收款人是 payee ,第二次交易的收款人是 some_other_payee。现实生活中的物理资产可以完全杜绝双重支付,幸运的是,Move 也可以做到。
3. 忘记执行转移加密货币 move(coin) 导致加密货币丢失。
忘记转移资源(就比如说删除上述代码示例中转移加密货币 move(coin) 所在的行)将触发字节码验证错误。这种机制可以保护 Move 开发人员不会有意或无意地丢失资源。
货币 Currency 模块
模块入门:Move 语言的执行模型
三个账户的区块链全局状态示例
每个帐户可以拥有零个或多个模块(上图中的矩形)和一个或多个资源值(上图中的圆柱体)。就比如说,地址 0x0 处的帐户拥有一个名为 0x0.Currency 的模块和一个 0x0.Currency.Coin 类型的资源值。地址 0x1 处的帐户拥有两个资源值和一个模块;地址 0x2 处的帐户拥有两个模块和一个资源值。
需要注意的是:
交易脚本的执行只有两种结果:成功或是失败,不会存在中间的状态。
模块是在区块链全局状态中发布的长期存在的代码。
区块链全局状态的结构为从帐户地址到帐户的映射。
帐户最多只能包含一个给定类型的资源值,并且最多只能包含一个具有给定名称的模块(就比如说,上图中地址 0x0 处的帐户不能再拥有一个额外的 0x0.Currency.Coin 资源或另一个名为 Currency 的模块)。
所声明模块的地址是类型的一部分(就比如说,0x0.Currency.Coin 和 0x1.Currency.Coin 是不能互换使用的不同类型)。
开发人员仍然可以通过自定义的包装器( wrapper )资源来实现一个帐户拥有多个给定资源类型的实例。(resource TwoCoins { c1: 0x0.Currency.Coin, c2: 0x0.Currency.Coin })
开发人员仍然可以通过名称引用资源而不会产生任何冲突,就比如说,你可以使用 TwoCoins.c1 和 TwoCoins.c2 这两个名称引用这两个资源。
声明加密货币资源:
在名为 Currency (货币)的模块中定义一个由模块管理的名为 Coin (加密货币)的资源类型。
需要注意的是:
Coin (加密货币)是一种结构类型,其字段容许的值类型为 u64 (64 位无符号整数)。
只有 Currency (货币)模块的程序能够创建或销毁 coin (加密货币)类型的值。
其他模块和交易脚本只能通过模块提供的公共可访问的程序来写入或引用值字段。
实现存款操作
这段程序将 Coin (加密货币)资源作为输入,并将其与存储在收款人 payee 帐户中的 Coin 资源组合,具体的步骤如下:
销毁输入的加密货币并记录其数值。
获取对存储在收款人帐户下的 Coin 资源的唯一引用。
将程序传递过来的加密货币的数值加到收款人账户余额中,并更新收款人账户余额。
需要注意的是:
Unpack,BorrowGlobal 是内置程序。
Unpack 是唯一一种删除类型为 T 的资源的方法。它将类型为 T 的资源作为输入,删除它,并返回绑定到资源字段的数值。
BorrowGlobal 将地址作为输入,并返回对该地址下唯一的 T 实例的引用。
&mut Coin 是对 Coin 资源的可变引用,而不是对 Coin 。
实现撤销存款 withdraw_from_sender:
这个程序分为三步:
获取对发送方帐户下唯一的 Coin 类型资源的引用。
用输入的数额减少引用的 Coin 的数值。
创建并返回值为更新后金额的新加密货币。
需要注意的是:
任何人都可以调用存款函数 deposit ,但撤销存款函数 withdraw_from_sender 具有访问控制策略,因而只能被加密货币的所有者调用。
获取交易发起人账户函数 GetTxnSenderAddress 类似于 Solidity 语言中的 msg.sender 。
除非满足什么条件否则就拒绝函数 RejectUnless 类似于 Solidity 语言中的 require 。如果此项检查失败,则当前交易脚本会停止执行,并且它执行的任何操作都不会更新区块链全局状态。
Pack 也是一个内置程序,它主要用来创建一个 T 类型的新资源。
与 Unpack 一样, Pack 只能在资源 T 的声明模块中调用。
写在最后
现在你已经了解了 Move 语言的主要特征,基本语法以及它与以太坊的差异。
最后,如果你想从事 Move 语言开发,我强烈建议你阅读 Move 语言原始的白皮书。白皮书中包含许多 Move 语言的设计原则以及许多很好的参考资料。