如何在Solidity中生成随机数?

28

我有几个keccak,如果我能找到一种廉价的方法来获取已创建的uint的部分,则可以将它们减少为一个。

pragma solidity ^0.4.19;

contract test {
  function test() {

  }

function sup() returns (uint test) {
    uint _test = uint(keccak256("wow"));
    return _test;
  }
}

这会给我返回一个美好的随机数:13483274892375982735325

现在的计划是,我可以将这个数字拆开,得到像1348、3274、8923等数字,然后将其用于生成随机数,例如:1348 % 10。

但是 Solidity 不能直接这样做。有什么简单的方法可以解决吗?


请生成以太坊中的多个随机数。 - Alberto Perez
11个回答

22
Solidity合约是确定性的。任何人只要找出你的合约如何产生随机数,就可以预测其结果并利用这些信息来攻击你的应用程序。
一种选择是在链下生成随机数(无法被预测),然后在智能合约中使用它。Chainlink VRF是一个易于实现的解决方案,可用于在智能合约中使用随机数据。以下是一个请求和接收随机数据的示例片段:
requestRandomness(keyHash, fee, seed);

您的合约请求已在回调函数中完成:

function fulfillRandomness(bytes32 requestId, uint256 randomness) external override {
  // Do something with randomness
}

一个实现随机数的完整合约示例如下:

pragma solidity 0.6.2;

import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/VRFConsumerBase.sol";

contract Verifiable6SidedDiceRoll is VRFConsumerBase {
    using SafeMath for uint;

    bytes32 internal keyHash;
    uint256 internal fee;

    event RequestRandomness(
        bytes32 indexed requestId,
        bytes32 keyHash,
        uint256 seed
    );

    event RequestRandomnessFulfilled(
        bytes32 indexed requestId,
        uint256 randomness
    );

    /**
     * @notice Constructor inherits VRFConsumerBase
     * @dev Ropsten deployment params:
     * @dev   _vrfCoordinator: 0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb
     * @dev   _link:           0x20fE562d797A42Dcb3399062AE9546cd06f63280
     */
    constructor(address _vrfCoordinator, address _link)
        VRFConsumerBase(_vrfCoordinator, _link) public
    {
        vrfCoordinator = _vrfCoordinator;
        LINK = LinkTokenInterface(_link);
        keyHash = 0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205; // hard-coded for Ropsten
        fee = 10 ** 18; // 1 LINK hard-coded for Ropsten
    }

    /** 
     * @notice Requests randomness from a user-provided seed
     * @dev The user-provided seed is hashed with the current blockhash as an additional precaution.
     * @dev   1. In case of block re-orgs, the revealed answers will not be re-used again.
     * @dev   2. In case of predictable user-provided seeds, the seed is mixed with the less predictable blockhash.
     * @dev This is only an example implementation and not necessarily suitable for mainnet.
     * @dev You must review your implementation details with extreme care.
     */
    function rollDice(uint256 userProvidedSeed) public returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) > fee, "Not enough LINK - fill contract with faucet");
        uint256 seed = uint256(keccak256(abi.encode(userProvidedSeed, blockhash(block.number)))); // Hash user seed and blockhash
        bytes32 _requestId = requestRandomness(keyHash, fee, seed);
        emit RequestRandomness(_requestId, keyHash, seed);
        return _requestId;
    }

    function fulfillRandomness(bytes32 requestId, uint256 randomness) external override {
        uint256 d6Result = randomness.mod(6).add(1);
        emit RequestRandomnessFulfilled(requestId, randomness);
    }

}

4
请记住,用这种方式生成随机数需要支付费用(以 LINK 支付)。 - Tamás Sengel
3
在以太坊上,每次请求将耗费10个LINK。今天1个LINK的价值为22.5美元。如果您的项目是一个包含1000个NFT的NFT项目,并且您想确保在每次铸造时都使用“无法预测的随机数”,那么您可以进行计算。 - John Joker
尽管Solidity合约是确定性的,但仍有一种方法可以生成伪随机数。答案应该首先涵盖这一点,然后展示像Chainlink这样的选项。 - Juanu

19
您不能创建真正的随机数,但能够生成伪随机数。区块链是一种确定性系统,因此我们必须确保每个节点必须提供相同的随机数。
确定性非常重要,因为无论智能合约代码在何处执行,都会每次和任何地方产生相同的结果。
在确定性系统中获取真正的随机数是不可能的,因为使用全局变量是可预测或可以被某种方式操纵。
例如,时间戳漏洞很常见。通常,块的时间戳通过block.timestamp访问,但这个时间戳可以被矿工操纵,导致影响依赖于时间戳的某些函数的结果。时间戳用作彩票游戏中选择下一个获胜者的随机源。因此,矿工可能会以某种方式修改时间戳,从而增加成为下一个获胜者的机会。
为了获得真正的随机数,我们必须从区块链外部寻找。我们需要使用oracle服务来获取真正的随机数。如果您的智能合约中没有真正的随机数,则您的智能合约可能会被黑客攻击。您可以阅读2个案例的详细信息:

https://www.reddit.com/r/ethereum/comments/74d3dc/smartbillions_lottery_contract_just_got_hacked/

https://hrishiolickel.medium.com/why-smart-contracts-fail-undiscovered-bugs-and-what-we-can-do-about-them-119aa2843007

由于Solidity发展非常快,其他答案已经过时。虽然这个答案有一天也会过时,但目前您可以像这样实现一个伪随机数生成器:

  // I realized if you call the random() in for loop, you get same result. So I had to add another changing variable
  // https://dev59.com/k3kPtIcB2Jgan1znkmKL#73557284
  uint counter =1;
  function random() private view returns (uint) {
        counter++;
        // sha3 and now have been deprecated
        return uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players,counter)));
        // convert hash to integer
        // players is an array of entrants
        
    }

这将返回非常大的数字。但我们使用模运算符。

random() % players.length

这将返回一个介于0和players.length之间的数字。我们编写一个函数来实现:

function pickWinner() public {
        uint index=random()%players.length;
    }

使用Chainlink获取随机数

在部署此合约后,您需要向该合约发送Link代币,然后点击getRandomNumber。等待大约一分钟,然后点击result

 import "https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/VRFConsumerBase.sol";

contract Test is VRFConsumerBase {
    bytes32 public keyHash;
    uint256 public fee;
    uint256 public ticketPrice;
    uint256 public result;

    // ---------------- GOERLI ADDRESSESS----------------
    // link address 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
    // key hash 0x0476f9a745b61ea5c0ab224d3a6e4c99f0b02fce4da01143a4f70aa80ae76e8a
    //ChainlinkVRFCoordinator 0x2bce784e69d2Ff36c71edcB9F88358dB0DfB55b4
    constructor(
        address _ChainlinkVRFCoordinator,
        address _ChainlinkLINKToken,
        bytes32 _ChainlinkKeyHash,
        uint256 _ticketPrice
    ) VRFConsumerBase(_ChainlinkVRFCoordinator, _ChainlinkLINKToken) {
        keyHash = _ChainlinkKeyHash;
        fee = 0.1 * 10 ** 18;
        ticketPrice = _ticketPrice;
    }

    function getRandomNumber() public payable returns (bytes32 requestId) {
        require(
            LINK.balanceOf(address(this)) >= fee,
            "YOU HAVE TO SEND LINK TOKEN TO THIS CONTRACT"
        );
        return requestRandomness(keyHash, fee);
    }

    // this is callback, it will be called by the vrf coordinator
    function fulfillRandomness(
        bytes32 requestId,
        uint256 randomness
    ) internal override {
        result = randomness;
    }

    receive() external payable {}
}

我回答了这个问题:使用Chainlink VRF获取随机数

6
如果这个函数被连续多次调用,由于区块的难度和时间戳在调用之间没有改变,那么返回相同的数字的可能性有多大? - GGizmos
1
@GGizmos,正如我所提到的,“你无法创建真正的随机数,但你可以伪随机数”。你可以更改参数。 - Yilmaz
2
你可以添加一个状态变量的引用,它将在每次调用之间更改。最简单的示例是 uint randomCallCount - Kevin Audleman

4
为了生成一个伪随机数,你可以像这样做:
function random() private view returns (uint) {
    return uint(keccak256(block.difficulty, now));
} 

如果您需要在特定范围内生成随机数,可以使用模运算。例如,要获取介于0到999之间(包括边界)的随机数,可以按照以下方式操作:

function random() private view returns (uint) {
    uint randomHash = uint(keccak256(block.difficulty, now));
    return randomHash % 1000;
} 

如果您有另一个类型为数组的字段可用,您可以将其长度作为附加参数传递给keccak256函数。

(所有代码都是使用v0.4.17编译的)。


有人能告诉我这个安全吗? - Renan Coelho
1
@RenanCoelho 这种方法使用诸如block.difficultynow之类的值生成伪随机数,这些值可能会受到矿工的影响。因此,在需要真正的随机性的生产环境中,它并不安全,因为恶意行为者可能会预测或操纵它。 - Marteng
1
谢谢提供的信息!也许最安全的方法是使用Chainlink VRF。https://chain.link/vrf - Renan Coelho

2
为了防止操纵,您需要一个不仅是伪随机数的数字。请看randao智能合约。它提供实际上无法轻易被攻击者操纵的随机数。

2
// Generate a random number between 1 and 100:
uint randNonce = 0;
uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;
randNonce++;
uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;

1

在Solidity中生成随机数相当复杂,因为区块链是一个确定性系统。

要在Solidity中生成随机数,您必须从区块之外生成此数字。

为此,您需要了解VRFConsumerBase。这是一个包,可以帮助我们生成完全随机的数字,而不干扰我们的区块链块。有关更多帮助,请查看此链接: https://docs.chain.link/docs/get-a-random-number/

以下是在Solidity中生成随机数的简单示例。

pragma solidity ^0.6.6;
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";

contract Lottery is VRFConsumerBase, Ownable {
    address payable[] public players;
    uint256 public usdEntryFee;
    address payable recentWinner;
    AggregatorV3Interface internal ethUSDPriceFeed;
    enum LOTTERY_STATE {
        OPEN,
        CLOSED,
        CALCULATING_WINNER
    }
    LOTTERY_STATE public lottery_state;
    uint256 public fee;
    bytes32 public keyHash;
    uint256 public randomness;

    constructor(
        address _priceFeedAddress,
        address _vrfCoordinator,
        address _link,
        uint256 _fee,
        bytes32 _keyHash
    ) public VRFConsumerBase(_vrfCoordinator, _link) {
        usdEntryFee = 50 * (10**18);
        ethUSDPriceFeed = AggregatorV3Interface(_priceFeedAddress);
        lottery_state = LOTTERY_STATE.CLOSED;
        fee = _fee;
        keyHash = _keyHash;
    }

    function enter() public payable {
        //$50 minimum
        require(lottery_state == LOTTERY_STATE.OPEN, "LOTTERY IS CLOSED!");
        require(msg.value >= getEntranceFee(), "Not Enough ETH!");
        players.push(msg.sender);
    }

    function getEntranceFee() public view returns (uint256) {
        (, int256 price, , , ) = ethUSDPriceFeed.latestRoundData();
        uint256 adjustedPrice = uint256(price) * 10**10;
        uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;
        return costToEnter;
    }

    function startLottery() public onlyOwner {
        require(
            lottery_state == LOTTERY_STATE.CLOSED,
            "Can't start a new lottery yet!"
        );
        lottery_state = LOTTERY_STATE.OPEN;
    }

    function endLottery() public onlyOwner {
        
        lottery_state = LOTTERY_STATE.CALCULATING_WINNER;
        bytes32 requestId = requestRandomness(keyHash, fee);
    }

    function fulfillRandomness(bytes32 _requestId, uint256 _randomness)
        internal
        override
    {
        require(
            lottery_state == LOTTERY_STATE.CALCULATING_WINNER,
            "You aren't there yet!"
        );
        require(_randomness > 0, "random-not-found");
        uint256 indexOfWinner = _randomness % players.length;
        recentWinner = players[indexOfWinner];
        recentWinner.transfer(address(this).balance);
        players = new address payable[](0);
        lottery_state = LOTTERY_STATE.CLOSED;
        randomness = _randomness;
    }
}

如果您有任何其他问题,请告诉我。


1
首先,让我们看看计算机如何生成随机数。它需要一个随机源,比如- CPU温度按下字母“B”的次数风扇的速度在某个时间点 t 等等。
但是在区块链中,几乎没有随机源。智能合约所见即公众所见。有人可以通过查看其随机源来操纵系统。
鉴于上述情况,您可以使用这个简单的随机生成器solidity程序生成0到100之间的随机数。请注意,仍然有人可以操纵系统。我们只是让他们难以操作。
contract Random {
uint256 private seed;

constructor() {
    seed = (block.timestamp + block.difficulty) % 100;
}

function getRandomNumber() public returns (uint256) {
    seed = (seed + block.timestamp + block.difficulty) % 100;
    return seed;
}

block.difficultyblock.timestamp是相当随机的,我们正在利用它们。

block.difficulty表示矿工挖掘块的难度。因此,我们通常可以说一个块有越多的交易就越难挖掘。

block.timestamp是块被挖掘时的Unix时间。

因此,在智能合约创建时进行种子生成,即使在调用getRandomNumber时,我们也会通过添加另一种随机源(即上一个seed)来改变seed,以使其更加困难。希望这可以帮助您。


0

0

这是我最好的尝试。基于一个我找不到的不同问题。如果我找到了,我会链接它。

pragma solidity ^0.4.19;

contract test {

event randomNumbers(uint[8] numbers, uint[8] randomNumbers);

function testa() public returns (uint[8] bla) {

    //uint something = 12345678123456781234567812345678;
    uint something = uint(keccak256("rockandroll"));

    uint[8] memory array;
    uint[8] memory random;

    for(uint i=0; i<8; i++) {
        uint digit = something % 10000;
        // do something with digit
        array[i] = digit;
        something /= 10000;
        random[i] =digit % 10;
    }

    randomNumbers(array,random);

    return array;
}

0

你不能仅使用Solidity来完成这个任务,否则会不安全。你需要一个oracle。

如果没有oracle,随机数就会变得更加可预测。

例如,你可以使用区块时间戳和难度来创建一个随机数,但它是伪随机的:

function createRandomNumber(uint256 max) external view returns (uint256)
{
    return
        uint256(
            keccak256(
                abi.encodePacked(
                    block.timestamp,
                    block.difficulty,
                    msg.sender
                )
            )
        ) % max;
}

所以你需要使用ChainLink的VRF V2合约(可验证随机数):

contract SomeContract is VRFV2WrapperConsumerBase {

    // fee given to the oracle for fulfilling the request (LINK tokens)
    uint256 internal fee;

    uint32 callbackGasLimit = 100000;

    uint16 confirmations = 3;

    // Goerli testnet addresses
    address linkToken = 0x326C977E6efc84E512bB9C30f76E30c160eD06FB;
    address vrfWrapper = 0x708701a1DfF4f478de54383E49a627eD4852C816;

    constructor() VRFV2WrapperConsumerBase(linkToken, vrfWrapper) {
        fee = 0.25 * 10**18;
    }

    // Requests a random number from ChainLink
    function generateRandomNumber() public returns (uint256) {
        require(
            LINK.balanceOf(address(this)) > fee,
            "Not enough LINK"
        );

        // Request a random number from chainlink
        uint256 requestId = requestRandomness(
            callbackGasLimit,
            confirmations,
            1
        );

        return requestId;
    }

    // will be executed when chainlink is done generating the number
    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        uint256 number = _randomWords[0];

        // Do something with the random number
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接