以太坊、比特币和比特币现金上的智能合约
智能合约是什么?用某种或某些条件决定资产的使用就相当于执行合约,而当合约可完全通过代码无需信任地自动执行,就行成了智能合约。本文作者Rosco Kalis系区块链资深技术人员,CashScript发明者。
尽管以太坊是第一个拥有图灵完备智能合约的平台,但已经有可能使用一种称为“比特币脚本”(Script)的语言在比特币上创建基本合约。而比特币现金最近一直在改善其智能合约功能。尽管不如以太坊先进,但它们都以自己独特的方式和优势支持链上智能合约的存在。
本文重点介绍这三个平台上智能合约之间的突出区别。由于关注的重点是智能合约和脚本功能,因此在此不会讨论平台或区块链的基础知识。本文还特此介绍了链上智能合约,以及存在几种第二层智能合约解决方案,例如RSK。这些解决方案绝对值得讨论,碍于篇幅不做非常深入的研究。
以太坊:自有状态且图灵完备
以太坊是迄今为止最大的智能合约平台,其智能合约使用以太坊虚拟机(EVM)实现,这是一个图灵完备的虚拟机。这意味着只要有足够的资源,EVM就可以计算任何东西。从概念上讲,这与许多其他通用平台(例如Java虚拟机JVM)相似,该平台用于执行Java程序。
解释EVM
不过那些通用平台与以太坊的EVM之间的最大区别在于,以太坊的智能合约代码由所有以太坊节点执行,以验证交易的有效性。为了奖励节点执行此代码,所有EVM操作码均具有相关的燃气成本,如下图所示。
图:EVM费用结构
每笔交易都会花费大量的Gas,具体取决于所使用的操作码。用以太坊的原生加密货币ETH支付Gas费用。为了限制这些节点每个区块必须执行的计算量,单个区块中可以使用的Gas量有一个限制,称为“区块Gas限制”。
在执行智能合约功能期间,合约可以存储和访问必要的数据。根据其用途,这些数据可以存储于不同的位置,即位于不同的数据位置。首先是“堆栈”(stack),其中包含计算中使用的值。只能轻松访问堆栈中的前16个项目,因此不适合长期存储。下面显示了使用堆栈进行计算。
图:基于堆栈的计算
其次,为了补充堆栈,“contract memory”可用于在当前合约执行过程中存储、检索和传递数据。可以从内存中检索这些值,以将其用于堆栈上进行计算,并将结果存储回内存中。这些值仅在当前执行期间持续存在。在合约执行结束时,将擦除contract memory内存和堆栈。
最终,数据所在的位置是“contract storage”,用于在合同执行期间持久保存数据。持久性变量(如token余额)存储在合约存储中。为了实现数据持久性,contract memory存储在每个以太坊节点上。因此,其内存类似于RAM,而存储类似于硬盘驱动器或持久数据库。
更多以太坊虚拟机如何工作的细节可参见MyCrypto的文章。
编写智能合约
尽管所有智能合约都使用EVM,但大多数智能合约不是使用EVM字节码手工编写的,就像大多数JVM字节码不是手工编写的一样。相反,有几种高级语言可用于在以太坊中编写智能合约,最受欢迎的语言是Solidity,除此之外还出现了Vyper,也是一种面向EVM的编程语言。下图包含一个非常简单的Solidity智能合约的示例。
与智能合约互动
以太坊中的智能合约作为字节码存在于以太坊网络上。这意味着将Solidity代码编译为字节码,然后通过发送部署交易(deployment transaction)将其部署到网络。这是一种特殊的交易,没有任何接收者,但以字节码作为交易数据。
这些部署的合约仅以EVM字节码的“碎片”形式存在于网络中,几乎无法单独使用。为了与它们进行“接头”,需要提供一个应用程序二进制接口(ABI),其中包括所有公共功能及其参数的列表。为了使用户对这些合约有更多了解,Etherscan之类的服务帮助可以验证合约源代码,以便用户可以在使用合同之前检查代码。
通过连接到以太坊节点并使用其JSON-RPC接口,可以直接访问所有智能合约。但是,许多智能合约却是通过连接到节点并管理ABI的前端应用程序访问的。这可以通过众多不同的以太坊SDK(例如web3.js或ethers.js)之一来完成,它们可以在后台调用JSON-RPC。这为合约用户提供了更好的体验,相当于最困难的部分被形象化了。
可以通过两种不同的方式与智能合约进行交互:调用(Calls)和交易
(Transactions)。调用是合约功能的本地调用,它不会向区块链广播任何内容。因此,调用过程是“只读”的,不能对合约状态进行任何更改,也不会产生任何费用。而交易是实打实的“写入操作”,确实会广播到网络,并包含在区块链中,并且会产生矿工费用。以ERC20令牌为例,通过调用检索token余额,同时通过发动交易转移令牌。
智能合约间互操作
以太坊的这些属性(具有图灵完备的EVM和持久性存储)允许创建在链上运行的任何类型的去中心化应用程序。一个很好的例子就是defi生态系统,其中包括Maker,Uniswap和Compound等应用程序,以及ERC20和ERC721令牌标准。这些应用程序允许具有无限数量的动态参与者的复杂功能。
图:rDai中的DeFi互操作性
最重要的是,智能合约本身可以成为其他智能合约的“参与者”,从而实现这些合约之间的强大集成和组合。比如,持有代币余额并将其借给Compound或与Uniswap交易所的智能合约,或者是使用rDai自动投资DAI并将应计利息贡献给慈善机构。
尽管智能合约可以彼此交互,但是链上的每个交易都必须来自外部帐户。因此,以太坊智能合约间的交互仍然必须由用户通过初始交易来触发。在此初始触发之后,合约间的交互类似于直接访问。
换句话说,某个智能合约可以调用其他智能合约的“只读功能”或触发“写入操作”(交易)。由于只有外部帐户才能触发交易,因此智能合约之间的交易称为内部交易或消息调用,以与用户启动的交易区分开。
比特币:无状态且简单
所有比特币交易(包括定期转账)均由基于堆栈的、被称为“比特币脚本”(Script)的编程语言支持。上文提到以太坊的EVM是为图灵完备且可互操作的智能合约而设计的,而比特币脚本却有意添加各种限制,并且以根本不同的方式工作。
解释比特币脚本
就想EVM,比特币脚本使用堆栈(Stack)来保存值(Value)并在这些值上执行计算。但与EVM不同的是,堆栈是比特币脚本中唯一可用的数据位置。这意味着,很难存储多个值以便在以后的合约执行中使用。更重要的是,这意味着不可能存储或修改那些在合约执行期间仍存在的值(但EVM可以)。
这是以太坊的智能合约和比特币的最大区别。以太坊的模型是有状态的,而比特币是无状态的。通俗理解就是,以太坊可以被认为类似于普通的命令式可变数据编程范例,而比特币则类似于功能不变的数据范例。
比特币的模型允许独立且高效得多地验证交易,这使得并行化和分片交易更加容易。但是,如果没有任何可变的数据存储,在比特币上创建像以太坊那样的复杂智能合约就更加困难,比如,ERC20合约必须跟踪代币余额并进行更改。
除了这些状态差异之外,还有其他一些因素限制了比特币智能合约的复杂性。值得注意的是,比特币脚本缺乏对某些算术函数以及任何形式的循环或递归的支持。其合约还具有520个字节的有效大小限制,并且最多只能包含201个操作码。
比特币上的大多数智能合约都属于几类简单合约。例如,可以由多个参与者使用的多重签名合约,或哈希时间锁定合约(HTLC)。
头等仓小知识:关于哈希锁定,可以举一个简单的例子,比如你从我这儿买一段视频,这个视频通过密码访问,那么在交易合约中,我把视频的访问密码也变成其中的一个key,如果要达成交易,这个key必须让你知道;而一旦你知道(你拿到视频),钱就支付给我了,并且整个交易过程一定要在设定好的时间内完成,超出时间则合约也将自动触发。
而且由于比特币上的这些合约非常简单,大多数价值是通过将不同的合约与其他脱链应用程序逻辑相结合而捕获的。这样,可以将简单的合同组合起来以创建复杂的解决方案,例如闪电网络或跨链拍卖等等。
解释比特币交易
比特币每一笔交易都是通过所谓的“交易输出”实现的。当这些交易输出在某个账户中可用,它们就被称为未用交易输出(UTXOs)。这些UTXOs被锁定脚本(或scriptPubKey)锁定,并被指定好了使用条件。尝试使用UTXOs时,会提供一个解锁脚本(或ScriptSig),然后这些脚本一起执行计算。仅当脚本执行无错误且结果值为正确(TRUE)时,交易才生效。
与EVM一样,所有比特币节点都执行这些脚本来验证交易,但是比特币区块链并没有Gas成本的概念,因此,按交易数据的每个字节向矿工支付费用。为了限制节点必须完成的工作量,在一些更密集的脚本操作上也加入了一些限制。
与智能合约互动
比特币中的智能合约的编写使用P2SH模式(头等仓注:有翻译为“向脚本哈希支付”)。P2SH模式中的锁定脚本包含脚本哈希,并且需要提供完整脚本(称为赎回脚本)以及该赎回脚本的解锁脚本的解锁脚本。下图显示了这种模式。
图:P2SH模式
头等仓小知识:P2SH模式是为了简化复杂脚本的运用而被引入的(传统机制是P2PKH),将复杂的锁定脚本编译成一个含有20个字节哈希的简单脚本,就想比特币地址是基于Base58编码的一个含有20个字节的公钥。P2SH跟传统P2PKH方式的最大区别是把设置转出条件的人从发送者变成接收者。通过在赎回脚本中添加各种转出条件可以来实现形式多样的交易,例如多重签名交易。
比特币节点分两个阶段验证这些智能合约交易。首先,对“赎回脚本”进行哈希处理,并对照“锁定脚本”中的哈希值进行检查。如果它们匹配,则用“锁定脚本”去解锁“赎回脚本”,就好像“赎回脚本”才是初始的锁定脚本一样。
因为只有字节码的哈希值存储在比特币链上,所以完整的字节码必须存储在链外,并包含在任何合同执行的解锁脚本中。因此,比特币智能合约的“部署”是免费的,但以后执行合约的成本更高。相反,以太坊的初始部署相对昂贵,而后来的合约执行则便宜。
这些部署上的差异鼓励综合使用这两种智能合约。一个很好的例子是LocalCryptos,它支持以太坊和比特币的非托管本地交易。对于以太坊,它使用一个大的智能合约来跟踪所有交易,而对于比特币,它为每笔交易创建单独的合约。
编写智能合约
虽然以太坊有多种高级语言可以编译为EVM字节码,但比特币对此的关注较少。尽管可以用比特币脚本构建的系统很复杂,但是合约本身通常很简单。因此,几乎没有必要抽象出底层系统。而且由于比特币合约有大小限制且每增加一个字节的高昂成本,因此保证合约“尽可能小”很重要。
尽管这些高级语言在比特币中不太重要,但它们确实存在。Ivy是比特币中“最精致”的高级语言,该语言由Dan Robinson于2017年创建。下图给出了一个用Ivy编写的智能合约示例。如果接收者未及时使用该合同,则该合约可用于发送可以由发送者收回的款项。
最近,Blockstream的几位研究人员发布了Miniscript,该语言专注于智能合约的分析和可组合性,而不是抽象底层系统。考虑到比特币合约往往缺乏抽象的复杂性,这似乎是正确的道路。
比特币现金:可组合功能
上文一方面是以太坊,它能够创建许多功能强大且有用的智能合约,这些合约完全存在于以太坊链上,同时,由于其有状态(Stateful)的性质,它还提出了扩展性问题。另一方面,比特币的智能合约无状态(Stateless)模型允许对智能合约交易进行独立,简单的验证,但是其脚本系统限制了合同的实用性。
在分叉之前,比特币现金(Bitcoin Cash)和比特币拥有相同的历史,因此它们的底层脚本系统在功能上是相同的,比特币现金也受益于这些相同的方面。从那时起,比特币现金社区的很大一部分就认识到了对“更有用的智能合约”的需求。比特币现金启用了新功能,使其智能合约更有用,同时保留了允许比特币进行无状态验证的基本属性。
BCH的功能升级
要了解比特币现金智能合约的可能性,我们需要查看其半年两次的网络升级。自2017年最初的硬分叉以来,每年5月和11月都将执行这些网络升级。我们特别讨论了对比特币脚本引擎的更改,尽管已进行了其他一些改进,例如Schnorr签名。
在比特币的早期,由于一些操作码不安全使用而被禁用。在比特币现金分叉之后的第一年内,比特币-ABC节点的开发人员解决了这些问题,并重新引入了经过稍微修改的功能的操作码,如下图所示。最重要的是,该升级使在比特币脚本中对结构化数据进行编码和解码成为可能。
图:2018年5月BCH启用了新的操作码
此后半年,在2018年11月的比特币现金网络升级中发布了另一个新的操作码。其中包括该更新OP_CHECKDATASIG,它使用户可以验证比特币脚本中任何消息的签名。如果我们结合2018年推出的脚本更新,这些更新可用于将新颖有用的智能合约功能引入比特币现金。
编写智能合约
相对于原始比特币脚本,比特币现金的新功能为其智能合约增加了很多额外的复杂性。这使得拥有一个通过高级语言、SDK和工具提供更高级别抽象层的生态系统变得更加重要。
目前正在处理此问题的两个大项目是Spedn(由笔名Tendo Pein创建)和CashScript(由本文作者Rosco Kalis创建,受以太坊的Solidity语言的启发)。尽管它们仍在积极开发中,但这些工具使处理比特币现金中的智能合约变得更加容易。下文将使用CashScript代码片段来阐述一些功能。
预言机(Oracles)
当过去的比特币脚本更新组合在一起时,它们使您可以通过受信任的预言机将外部数据带入比特币现金的智能合约中。可以将结构化数据编码为字节数组,并由oracle提供程序签名。然后,智能合约可以验证签名并解码结构化数据。
可以在下面的HODLVault示例合同中查看此示例。该合同强制执行HODLing,直到达到一定的BCH / USD价格为止。所需的BCH / USD价格供稿由oracle提供程序发布,并由用户传递到合同中。为了增加分散性,可以将智能合约设置为使用多个数据源,而不是信任单个集中式服务。
保证合同(Covenants)
第二个大用例是称为“保证合同”(Covenants)的技术,该技术的名称来自物权法中用来限制对象使用的术语。就比特币现金而言,它限制了智能合约中货币的使用。因此,尽管比特币中的智能合约只能授权一般的货币支出,但比特币现金合约能够对可支出的金额或收款人的身份施加限制。
转让比特币时,发送者必须提供签名以授权交易。为了生成此签名,发送方对交易的哈希表示进行签名。此哈希称为Sighash,而实际的交易数据包含在Sighash原像中。
通过使用OP_CHECKSIG和OP_CHECKDATASIG具有相同的签名,我们可以访问Sighash数据。数据格式可以在规范中进行检查,并包含在下面的图像中。一个重要字段是scriptCode,其中包含智能合约本身的字节码。另一个是hashOutputs允许用户强制执行交易的输出。
图:比特币现金Sighash数据格式
知道保证合同在技术层面上是如何工作的,这很不错,但是CashScript 已经抽象出了与契约有关的大多数复杂性。使用CashScript编写智能合约时,这些字段很容易获得,而无需执行手动验证和解码Sighash原图的步骤。
第一个使用保证合同技术的智能合约是Licho的Last Will(最后的遗嘱),这是一个智能合约,可让用户将死者的财产转移到自己的财产上。合同定义了三个不同的功能。第一种允许继承者在180天后索取资金。第二个允许所有者的冷密钥(ColdKey)以任何方式花钱。第三个允许所有者的热密钥(Hot Key)通过强制将全部合约余额发送回合约来刷新180天的期限。
下面包含CashScript版本的Last Will,但是原始版本是由Spedn编写的,可以在此处进行查看。
这是有用的保证合同的简单示例之一,但是保证合同可实现甚至更复杂的功能。在《Last Will》合约签订后,其创建者Karol Trzeszczkowski继续创建了Mecenas,该公司可强制执行Patreon等定期付款。
保证合同最初是在题为《 Bitcoin Covenants》的论文中提出的,该论文需要一个新的操作码OP_CHECKOUTPUTVERIFY。随后提出了其他建议,例如OP_CHECKTEMPLATEVERIFY或OP_CHECKSIGFROMSTACK。后者与比特币现金的盟约实施非常相似。可在Tendo Pein的文章中学习到保证合同的更多信息。
模拟状态(Simulated state)
查看“最后的遗嘱”合约,当合同到位时更改继承者可能很有价值。由于我们可以通过查看当前合约的字节码来强制发送到当前合约,因此我们可以通过稍微更改此字节码来强制发送到稍微不同的合约。可以创建了一个函数,该函数将整个合同的余额发送到具有完全相同的字节码但具有不同的继承者的合约。
我们只能使用已知大小(例如20字节)的构造函数参数来执行此操作。构造函数参数始终是合同字节码的第一个数据,这就是我们能够轻松地用新数据替换旧数据的方式。该inheritor是第一个构造函数的参数,它的大小为20个字节,因此我们能够应用该技术,如可以看到下面。
通过使用该技术,可以在保留合约相同规则的同时更改合约中的某些变量。我们将其称为“模拟状态”,因为它提供了合约状态的某些好处,并且不存在像以太坊那样有状态(Stateful)的系统的可拓展性问题。由于必须在某处进行权衡,因此此方法还有其他缺点。
主要问题是我们实际上并未更改原始合同中的任何变量,因为由于比特币现金的无状态性,这是不可能的。而是创建一个新合同(具有新地址),并将全部余额转移到该新合约中。由于新地址,这会导致UX问题,但是可以通过在这些智能合约上具有良好的应用程序层抽象来缓解这些问题。
在字节码内部替换这些变量可能会感到很麻烦,尤其是在尝试替换字节码内部更深的变量时。应该以高级语言抽象该功能,以便通过提供新参数自动生成新的字节码。这种抽象可能看起来像这样:
许多抽象性是以更大的合约为代价的,从而导致更高的费用。但比特币现金的交易费用通常很低,因此可谓集二者之大成。但是比特币现金具有与比特币相同的大小限制,因此这些抽象确实使保持在限制内变得更加困难。这导致许多开发人员不得不手动优化已编译的字节码。
结论
每种加密货币都在其智能合约系统中进行权衡。以太坊是功能最广泛的最常用的智能合约平台。比特币通过其无状态脚本系统提供了更基本的智能合约版本,此无状态系统验证效率更高,但功能较少。比特币现金与比特币具有相同的基础,但是增加了新功能,试图在有效验证和有用的智能合约之间折衷。最后,所有人都朝着相似的目标迈进。
展望未来
本文写于2019年第四季度,反映了平台的当前状态。加密货币是一个快节奏的领域,因此涉及这些平台的人们始终在努力进行改进,这可能会改变生态系统的前景。
以太坊正在开发主要的以太坊2.0版本,其第0阶段计划于2020年第一季度上线。以太坊2.0旨在通过转移到权益证明,实施分片和其他改进来解决以太坊1.x的性能问题。ETH2.0的完整路线图尚未确定,可能要花费数年时间才能推出。但是,如果此重大更新能够实现其目标,则可以为以太坊提供强有力的支持。
比特币中的智能合约研究致力于进一步提高智能合约的效率和隐私性,这包括Taproot和无脚本脚本之类的解决方案。而比特币现金则继续致力于启用其他智能合约功能,并致力于使这些更改更易于访问。
微信扫描关注公众号,及时掌握新动向
2.本文版权归属原作所有,仅代表作者本人观点,不代表比特范的观点或立场
2.本文版权归属原作所有,仅代表作者本人观点,不代表比特范的观点或立场