这次给大家介绍一个在智能合约中经常用到的东西——随机数。随机数常用于智能合约的开发。比如彩票的属性,现在流行的NFT数字收藏品,都需要随机数。目前常见的获取随机数的方式有两种:使用块变量生成随机数,使用预言机生成随机数。我们来看看两者的特点:1)使用区块变量产生随机数先来了解一下常见的区块变量有哪些:block.basefee(uint):当前区块的基础费用block.chainid(uint):当前链idblock.coinbase():当前区块矿工地址payableblock.difficulty(uint):当前区块难度block.gaslimit(uint):当前区块gaslimitblock.number(uint):当前区块编号block.timestamp(uint):自Unix纪元blockhash(uintblockNumber)返回(bytes32)以来的当前区块时间戳(以秒为单位):给定区块的哈希值,仅适用于256个最新区块where区块。difficulty、blockhash、block.number和block.timestamp这四个用得比较多。区块数据产生的随机数可能会限制普通用户预测随机数的可能性,但无法限制矿工作恶。矿工可以决定是否广播一个区块。当他们挖出一个区块时,他们不必广播一个区块。可以直接扔掉。这被称为矿工的选择性包装。他们可以不断尝试生成随机数,直到在广播前得到想要的结果。当然,矿工会这样做的前提是有足够的利益诱惑,比如来自大型奖励池的奖励,所以使用块变量获取随机数的方法更适合一些不属于的随机数核心业务应用程序。2)使用预言机生成随机数预言机是专门为生成随机数种子而构建的链上或链下服务。除了使用第三方服务,DApp开发者还可以构建链下服务来提供随机数。这种在链上获取链下数据的场景,通常是通过链上预言机来实现的。当然,这种方式也存在一定的安全隐患。比如依赖第三方给的随机数种子,也会出现第三方作弊或者受贿的情况。即使是自己搭建的随机数服务,也有可能因为故障等原因无法使用。也有可能是项目方操纵随机数,对DApp的运行和用户造成重大损失。因此,使用链下服务获取随机数的方式取决于是否有可信稳定的第三方服务。如果有,那么这种方法比使用区块链变量生成随机数的方法更难预测。会更强。接下来,我们将通过合约代码来论证弱随机数可能带来的危害。漏洞示例漏洞分析首先我们看一下代码中的两个函数,abi.encodePacked和keccak256:labi.encodePacked对参数进行编码,solidity提供了encode和encodePacked两种编码方式,前者对每个参数进行编码,32个字段补全,后者不进行补全而是直接连接待编码的参数。lkeccak256哈希算法,可以将任意长度的输入压缩成64位的十六进制数,哈希碰撞概率几乎为0。接下来我们看合约代码。这个合约是一个猜数字赢取以太币的游戏。我们可以看到部署者使用上一个区块的区块哈希和区块时间作为随机数种子来生成随机数。我们只需要模拟他的随机数生成方式就可以得到奖励。再看攻击合约:Attackcontract先来分析一下攻击过程:攻击者调用Attack.attack()函数,该函数模拟了GuessTheRandomNumber合约中的随机数生成方式。生成随机数后,调用guessTheRandomNumber.guess()并将生成的随机数传入,因为随机数的生成从Attack.attack()到调用guessTheRandomNumber.guess()是在同一个block中完成的,同一个区块中的两个blocks.number和block.timestamp参数不变,所以Attack.attack()和guessTheRandomNumber.guess()这两个函数生成的随机数结果相同,所以攻击者可以成功通过if(_guess==answer)判断获得奖励。修复建议如果随机数是非核心业务,可以使用未来区块的哈希值生成随机数,即将猜中和领奖分开进行异步处理。为这个漏洞合约写一个优化版本,可以看看:添加deadline参数异步处理猜测和认领后,部署合约后72小时内可以调用guess()猜测随机数,72小时后guess()关闭,claim()打开,玩家可以使用claim()来验证自己是否猜对了。当然,这个修复契约并不是一个完美的解决方案。前置知识中提到,如果矿工来玩,在打包的时候就可以知道自己猜对了没有。打包(我相信没有矿工愿意为一个以太支付如此高的价格)。因此,最好的解决方案是访问知名的预言机来获取随机数。如果你想了解更多智能合约和区块链知识,欢迎来到区块链交流社区CHAINPIP社区一起交流学习~社区地址:https://www.chainpip.com/
