2. 链上随机数的挑战:区块链的确定性环境,区块哈希的局限性,矿工/验证者操纵风险
好,我们进入第二讲。
上一讲我们聊了随机数在区块链里的重要性。从 DeFi 抽奖到 NFT 铸造,到处都需要它。但真正动手做的时候,你会发现——嗯,事情没那么简单。
区块链本质上是一个确定性状态机。什么意思?就是同样的输入,在任何节点上重放,结果必须完全一样。否则全网就达不成共识了。但随机数偏偏需要不确定性。这就产生了根本矛盾。
我个人习惯把这个问题拆成三个层面来看:
- 环境本身的限制(确定性)
- 常用方案的漏洞(区块哈希)
- 人的因素(矿工/验证者操纵)
咱们一个一个说。
2.1 确定性环境:随机数的天敌
你想想看,以太坊的 EVM 是一个封闭的沙盒。它没有网络访问,没有系统时间(block.timestamp 其实是被矿工控制的),更没有硬件随机数生成器。所有节点必须对同一笔交易计算出完全相同的状态。
这意味着什么?
意味着你不能在合约里写 random() 然后指望它每次返回不同值。Solidity 没有原生的随机函数。所有看起来「随机」的东西,都必须从链上已有的、确定性的数据中推导出来。
我在项目中遇到过一位刚入行的开发者,他直接用 block.number 做随机种子。结果呢?同一个区块里的所有交易,拿到的随机数一模一样。抽奖直接变成「人人中奖」或者「人人不中奖」。这显然不行。
核心矛盾:
区块链需要确定性来达成共识,而随机数需要不确定性来保证安全。这两者天然冲突。
2.2 区块哈希:看似美好,实则脆弱
很多人第一个想到的方案是:用区块哈希作为随机源。
比如这样:
// 不安全的示例 - 仅用于说明
function unsafeRandom() public view returns (uint256) {
return uint256(blockhash(block.number - 1));
}
这个方案看起来挺合理。区块哈希是公开的,不可预测的(至少在挖矿完成前),而且每个区块都不一样。
但问题来了。
第一个问题:未来区块哈希不可用。
Solidity 里 blockhash(block.number) 永远返回 0。你只能拿到过去 256 个区块的哈希。这意味着你不能用「当前区块」的哈希来做随机数,因为你调用的时候它还没生成。
第二个问题:也是更致命的——矿工可以操纵。
我记得有一次审计一个抽奖合约,项目方用了 blockhash(block.number - 1) 作为随机种子。我直接告诉他们:这个合约里的所有奖金,理论上都可以被矿工拿走。
为什么会这样?
因为矿工在打包交易时,可以决定是否包含某笔交易,以及交易的顺序。如果矿工自己也参与抽奖,他可以先看看自己用当前区块哈希算出来的结果是否中奖。如果不中,他就不打包自己的交易,等下一个区块再试。一直试到中奖为止。
说白了,矿工有无限次重试机会。而你只有一次。
避坑指南:
我曾经见过一个项目,把区块哈希和交易发送者的地址拼接起来做随机数。他们以为这样就能防矿工。但矿工可以创建多个新地址来试,成本极低。最终这个项目在测试网上就被我找到了漏洞。
2.3 矿工/验证者操纵:最现实的威胁
区块哈希的问题只是冰山一角。更深层的风险在于:谁有权决定「随机数」是什么?
在 PoW 链上,矿工有打包权。在 PoS 链上,验证者有提议权。他们都可以在某种程度上影响随机数的生成。
常见的操纵手段包括:
| 攻击方式 | 原理 | 危害程度 |
|---|---|---|
| 交易排序攻击 | 矿工调整交易顺序,使对自己有利的随机数生效 | 高 |
| 区块扣留攻击 | 矿工挖到区块但不广播,等自己交易被包含后再发布 | 中 |
| 分叉选择攻击 | 在多个分叉中选择对自己最有利的那条 | 高(需要算力) |
| 拒绝服务攻击 | 阻止不利于自己的交易被打包 | 中 |
你可能会问:这些攻击真的会发生吗?
答案是:只要利益足够大,就一定会发生。
我记得 2018 年有一个叫「Fomo3D」的游戏,最后的赢家拿走了上千万美元。那个游戏的随机数机制就被矿工操纵了。获胜者本身就是一名矿工,他利用自己对区块时间的控制,在最后一刻提交了交易,确保自己中奖。
这不是理论攻击。这是真实发生过的案例。
2.4 小结:我们到底需要什么样的随机数?
讲到这里,你应该明白了:
- 链上环境天生不适合生成随机数
- 区块哈希方案有根本性缺陷
- 矿工/验证者有能力也有动机去操纵
那是不是说链上随机数就做不了?
当然不是。只是我们需要更精巧的方案。
我个人认为,一个好的链上随机数方案应该满足三个条件:
- 不可预测性:在生成之前,任何人都无法知道结果
- 不可操纵性:即使矿工也无法单方面影响结果
- 可验证性:生成后,任何人都能验证随机数是否被篡改
下一讲,我们会介绍一种满足这些条件的密码学工具——可验证随机函数(VRF)。它会从根本上解决我们今天讨论的这些问题。
个人经验:
如果你现在正在设计一个需要随机数的 DApp,我的建议是:先别急着写代码。先想清楚你的安全模型。你的对手是谁?是普通用户,还是专业的矿工?不同的威胁模型,需要不同的解决方案。我们后面会详细讲。
好,这一讲就到这里。下一讲见。