The DAO 事件的教训

 

专注于智能合约技术在现实世界中的应用,这是RSK(rootstock)的目标之一。尽管对于DAOs这种用激进...





专注于智能合约技术在现实世界中的应用,这是RSK(rootstock)的目标之一。尽管对于DAOs这种用激进的新方式来解决古老的信任问题,我们很兴奋,但我们认为比特币及其区块链、智能钱包和加密资产已经具备颠覆性的潜力,用这些技术我们就可以实现真正的金融包容性。使用简单明了的智能模块,分析、推论、审查它们非常容易,这是我们成功的必须条件。



上个月,我们见证了众筹2亿美元资金的The DAO的诞生过程。我对The DAO创建者是否做好保护这么大规模资金的准备表示怀疑。毕竟the DAO还只是没有经过考验的实验性项目。这篇文章我将总结引起the DAO这次事件的主要原因,之后我们会分析RSK的设计,说明我们会怎样最小化这样的风险。我们RSK的目标是提供一种分层安全保障:这样以来,一个保护层被破坏并不会导致整个系统的崩溃。以太坊有它自己的安全模型,所以在这次the DAO事故中,它幸免于难,在这次事故之后它甚至会变得更健壮,我希望它能接受这个深刻的教训,毕竟它也是深受其拖累。

以飞机飞行做类比

我们中的大部分人开始使用比特币的时候(买比特币或者收到别人支付的比特币),比特币还处于试验中:它所有的功能都是在运行中的。比特币有很多次被拿来和必须在空中修复的飞机做类比。然而,比特币是一架已经持续飞了7年而没有着地一次的飞机,这是人们不厌其烦地称其可靠的原因。相反,当很多人投资the DAO的时候,它的代码还没有运行过。你会把你的生命托付给一个自动飞机驾驶员软件吗?而且这个软件从没试飞过。不幸的是,the DAO的投资者干的就是这种事情 。他们忘记了认真调查,所以被冲动狂热惩罚了。有人会说大部分the DAO投资者只投了点小钱,基本上他们算是小赌怡情了一把(我知道高端人士经常盲目的投资),然而有一些机构投资者在the DAO中投入了上百万的资金。所以应该让the DAO的投资者分担一些这次事故的责任。

以太坊虚拟机的设计

简化共识层的规范,这是以太坊的设计目标之一。这是一个伟大的目标,它提升了这个平台在不同语言和限制之间的重用性。尽管最小化图灵完备指令集可以限制在10条指令以内,然而以太坊没有对自己实施这样的限制,原因是:(a)这样降低了效率 (b)这让编译代码难以审计。所以,以太坊有100种不同的操作码。然而,为了达到简化的目的,调用操作码被两个函数拖累:调用其它合约的函数、发送以太的函数。但是,这两个函数的语义形态有很大区别,它们分别适用的语境区别也很大。这也是导致the DAO被攻击的一个因素。有意思的是,通过创建一个临时合约,使用自毁操作码,以太坊虚拟机可以不调用任何函数间接地实现发送以太币的功能,尽管消耗的燃料多一点。从这我们可以得到一个结论:虚拟机应该在不调用任何代码的情况下,提供发送操作码,降低上层的复杂度。有人可能会这样说,把一个调用的燃料供给限定为2300,这样就没有其它的调用可以执行了,所以它就安全了。如果我们考虑到虚拟机在将来会实施一次硬分叉:这会减少调用操作的费用,或者智能合约被允许自我支付,这种方式就失效了。所以,基本来说,这种方案是目光短浅的,把真正的问题隐藏了起来,而且这阻碍了未来的改进。在RSA中,我们使用简单的发送操作码,这样不会在目的合约中调用任何代码。

一些人会提出异议:大部分的智能合约将会处理加密资产,而不仅仅是以太币,所以发送一种资产,实际上会调用资产发行合约。这些情况会被添加不同的符号,而且可以用到限制方法。发送操作码所起的作用不仅仅是修复需求问题。

Solidity语言

众所周知,动态语言比静态语言更加难以验证(甚至推算)。以太坊智能合约会让动态语言代码消耗比静态语言更多的计算机资源。Solidity是基于javaScript的静态语言。选择静态语言给人的第一印象不错,但是问题是,为什么要从零开始重新创造一种语言?一种不是特定领域的语言,没有专为智能合约设计的功能。现存的语言没有足够好的嘛?之前智能合约的项目,如QixCoin,基于RISC处理器的模拟器,或者运行x86指令的沙盘,这让程序员可以使用主流的标准编程语言和编译器。Solidity是不成熟的:比如,我有一个合约让Solidity产生了错误,原因不明。我也遇到过Solidity生成糟糕代码的例子。当人们开始阅读以太坊合约源代码,而不是合约字节码,以便审计它们时,编译器的质量(被篡改的编译器的风险)问题非常重要。

回到the DAO事件,我们来看看the DAO的源代码。我们不打算再去探讨这个漏洞了,它已经被分析的够多了,我们仅仅看一下这有可能被黑客利用的两行代码:

a) if (_recipient.call.value(_amount)()) {
b) if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false)

第一行未定义的合约_recipient是发送了_amount这个数量的以太币,没有定义方法。第二行,一个指定的方法称为(createTokenProxy) 正发送fundsToBeMoved 的以太币。Solity 文档0.2.0有这样的警告:添加并设置.value(x)或.gas(y)的参数,调用函数来发送燃料,而且只有最后括弧中的内容才执行调用动作。如果.gas()后缀被忽略了,那么这些没有被使用的燃料就都便宜给外部调用了,所以合约递归漏洞就存在了(然而这在它的文档中没有明确说明)。我认为调用符号也是有瑕疵的,因为这很反常,首先,其它语言都不允许被这样一个修改者修改一个方法(method)的引用,这让这个方法的调用不清晰,貌似方法调用把“value()”代替了“createTokenProxy”。

创建一种强大且易于程序员理解的语言,有关这方面的研究已经很多,像 golang、c# 和Java就是很好的例子, 它们在人类理解、语言表达和符号紧凑性之间找到一种合理的平衡。Solidity貌似不属于这样的语言。我也不推荐使用Serpent,因为有关它的信息和实例太少。

运行环境的帮助

貌似智能合约的安全完全依赖于合约代码本身。虚拟机没有限制递归的专门服务,Solidity运行环境也没有阻止它的信号。the DAO中的这种类型的递归漏洞早在2014年的时候就被人们熟知了,所以这次事件之前是有充足的时间做准备。可是,很显然,这些工具都不成熟,高质量的工具,要花几年的时间开发。以太坊的去中心化开发可能有很多的益处,但这也带来一些坏处: 缺乏清晰的指令,安全相关的升级被无限期的延迟等。

在RSK中,我们计划提供一个固化的运行环境,避免合约默认重入。虽然这无法全面的解决问题,但这可以避免大部分的人为错误。安全审计

做过安全审计的人士应该知道,没有一个单独的安全审计可以覆盖所有的潜在漏洞。每个研究员或组织都会漏掉一些问题,这要看他们以前的经验。如果他们面对的是全新技术的代码(智能合约)、新语言(Solidity)和新的攻击类别(比如game-theoretic),那么情况会更是这样。安全审计的数量、深度和资金、审计代码相关。一段新代码将会消耗上百万美元资金,多次的安全审查,可能需要多个团队的合作才能完成。事实上,以太坊就是这么做的,他们雇佣了LeastAuthority、Dejavu、Coinspect公司为其审计。然而the DAO的创建者没有这么做,The DAO监管者,其中还包括以太坊的创始人,他们应该建议the DAO这么做。

规范化

用规范化模型设计智能合约,使用静态/动态校验器验证代码的正确性,这是安全审计无法替代的。规范化的方法一直是强有力的保证,尽管这种模型实施起来很困难。特定领域专用语言(DSL)避免更广泛的类bug。问题是,标准化意味着成本昂贵,所以它经常被忽略。所以,这又是一个教训:安全问题关乎成本。我们期待新的专用于RSK和以太坊的标准化工具的出现。不管怎样,很多为传统编程语言设计的工具如java( JML,KeY)已经存在,这也是RSA创建类似工具链的原因,好让智能合约可以在java中完成,我们期待用这工具链开发高风险的合约。

渐进的去中心化

创建一个像DAO这样的组织的困难不仅仅在于要保证代码正确性,还有投票系统的动态性可能会造成潜在的缺陷,这很难被预测。组织投票是一个复杂的人类活动过程,在它正式化之前需要反复试验。一种合理方法是,使用一个被一组公证人通过一个(n,m)多重签名控制着的合约作为开始,这个合约可以简单直接的进行升级改善,n简单的表示大多数人,n逐渐增多直到完成公证人共识,这是必须的。最后,当这个合约在真实环境中测试过之后,多重签名的功能在过一段时间后被自动移除。The DAO在开始阶段可能很少进行这样的疏导,然而这么做大大增加了成功的机会。

这样的准则被RSK放进了它的混合共识系统:它一开始需要公证人的确认,但是随着融合挖矿合约的出现公证人确认数会降低。我们把这种方式叫做渐进的去中心化,它可以被用于智能合约和共识系统。

对风险的忽视

在过去,有很多次,我在处理安全审计报告的时候,收到这样的反映说:没有这样的漏洞,因为这样的攻击条件永远不会被触发,或攻击实施起来太困难,还有的说修复这样的问题会影响到实用性。现实的一切都说明了这些说法站不住脚。因为软件的改变会激活潜在的漏洞,所以当软件后来被升级后,原来沉寂的代码会被运行,会突然成为一个漏洞。对攻击复杂度的评估更是不正确的:黑客会正确评估收益和工作量,比受害者不知高到哪里去了,这些被攻击者对黑客的能力根本就没有概念。实用性论据更站不住脚,即使这个软件的实用性再强,一旦被黑客攻破一次,谁还再用第二次?递归调用问题就是个很好的例子,这个漏洞是已知的而且记录在案,但很少人严肃对待它。

文档的缺乏

以太坊开发者急迫的需要一个专门的网站,来存与编写智能合约相关的设计模型、共同错误、错误概念和最佳安全实践等方面的文档。在RSK中,我们会设置一个资源空间,专为智能合约的安全服务,我们还会邀请研究员参与制定标准。我们有一个中期计划,就是创建一个平台进行智能合约黑客挑战,举行黑客竞赛(类似于黑客夺旗赛CTF).

RSK安全担保伙伴计划

RSK和安全公司合作来为智能合约的安全底线做担保。这可以让创业公司测试他们对抗智能合约安全漏洞的代码,作为优先于完整代码审计的第一道安全防护层。我们邀请所有对此计划感兴趣的计算机安全公司参与。

总结

智能合约领域处于初始阶段,失误是在所难的。在更丰富的工具和文档完善之前,我们应该对智能合约编程采用“深度防卫范式”,尽可能多的添加安全保护层,以达到减少漏洞影响的目的。人为错误被含糊的编程语义和文档的缺失放大。我们RSK及其生态全面吸取the DAO事件的教训,同时我们确保RSK使用成熟的工具集,让合约的开发步入正轨。我们鼓励智能合约公司和平台履行安全审计,并鼓励投资者和用户做好谨慎严格评估工作。

END
欢迎关注区块链应用研究院


    关注 区块链应用研究院


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册