Skip to content

Solidity 游戏/随机数/开宝箱 安全问题

今年靠 Solidity 随机数问题赚了 6 位数,说一下 Solidity 游戏类合约的一个随机数相关的安全问题吧,

随机数

基本上 开箱子(抽卡)、战斗、彩票 等等都跟随机数有关,有些用 block.difficulty、block.number、block.timestamp 等等组个 hash 取模来取随机数的,有使用 ChainLink 等 Oracle 的 真随机合约的。

不管是真随机数还是伪随机数都逃不开数据持久化。

拿抽卡游戏来说,有 R、S、SR、SSR 四种等级吧,然后用户可以用 10U 来抽卡,抽到的概率是随机的,一般都是用户调用接口,扣过来 10U,然后按随机数生成卡片然后将卡片转给用户。那么这个流程中我们可以有什么地方可以操作?

同 transaction 内部

做合约的都知道,我们可以通过合约来抽卡,抽前给自己卡片做个快照,抽后给自己卡片做个快照。一是如果连抽到抽不到都是有概率的我们可以知道有没有抽到,二是如果抽到了,我们可以去读取卡片信息来验证开出来的卡片的稀有度。

如果稀有度没有达到我们的期望值,或者根本没抽到,可以在我们合约这里直接 revert,除了付点 gas 费,我们的 10U 还是老老实实回到我们钱包了。

这个方案需要权衡尝试成本和抽到高级成本,有利润就可以操作

estimateGas

我们广播交易前可以对 call 进行 gas 估算,这个方法比较适用于战斗,比如一个有战斗场景的游戏,战斗成功之后直接发 U 给你当奖励。如果判定你失败,就不会给你发 U,当然成功失败的操作差异不会只有这一个,这样大概率 战斗成功 的 gas 会比 战斗失败 要多一些。

这样在我们调用 fight 之前,就可以进行 gas 估算,如果 gas 比我们算出的中值要高,那么大概率是赢的。

这个方案只能适用于逻辑比较明显,差异明显的合约。对于逻辑复杂的合约,gas 是不好估算的,成功率不高

防护方案

以吾之盾,防吾之矛

数据被验证

像上面提到的同交易中利用外部合约验证,验证不通过就 revert 的场景,有几种解决方案。

检查 caller 的 extcodesize

普通用户地址的 extcodesize 一定是 0 的,使用以下代码可以检查是否来自普通用户地址,

contract OnlyForEOA {  
    uint public flag;

    // bad
    modifier isNotContract(address _a){
        uint len;
        assembly { len := extcodesize(_a) }
        require(len == 0);
        _;
    }

    function setFlag(uint i) public isNotContract(msg.sender){
        flag = i;
    }
}

但是,部署合约时在 constructor 中的操作 caller 的 extcodesize 也是 0,所以我们可以使用以下代码 bypass 这种防护方案,这也是为什么这个方案使用删除线的原因

contract FakeEOA {
    constructor(address _a) public {
        OnlyForEOA c = OnlyForEOA(_a);
        c.setFlag(1);
    }
}

tx.origin

检查 tx.origin == msg.sender 是一个较好的方案,它将确保调用者来自普通用户且未经过其他合约来调用,但是

  1. tx.origin 将来有可能 会从以太坊协议中删除
  2. 这种方式将会把合理的合约用户拒之门外,比如多签合约钱包、DAO 持有人

所以奶爸更推荐下面的防护方案,在业务逻辑中将 「剧透」 的科学家们拒之门外

延迟读取

比如我在 1000 区块开出卡,我可以记录下来出卡区块,读取稀有度那里可以限定只能 N+1 读取,同区块读取就返回 0,这样就完美避免了利用外部合约验证稀有度的问题。

分阶段执行

可以将一个操作拆分为两个阶段,

  1. 第一阶段就隐式的将第二阶段所用到的所有随机数都准备好,不暴露出来。gas 都是一模一样的,无法推测、无法验证。
  2. 第二阶段根据第一阶段产生的结果执行对应操作,给对应奖励,这一阶段不论干什么都是无效的,结果已在第一阶段确认。

⚠️ 这里需要限制这两个阶段不能在同一个区块中避免验证,或者直接限制个人地址调用。

估算 Gas

这个问题可以这么解决,战斗成功,奖励 10 U,战斗失败 奖励 0 U 呗,该执行的操作一视同仁,但是你听过一句话吗?「听君一席话,胜听一席话」,给他加 0 U 呗,gas 表现出来不能说完全一样(修改数值和不修改数值 Gas 是不同的),但是差异也是很小的,也减少了特征点。

也可以做些额外操作来补充没进行的操作,类似 花指令。

扩展阅读

Comments

Commenting as a guest. A nickname is required; email is optional. Only new comments posted after verifying your email and signing in will appear on your profile. Existing guest comments will not be linked automatically.

Share article

Solidity 游戏/随机数/开宝箱 安全问题

Email X

View original ↗