Random Number Generator

Summary

The ThunderCore blockchain supports generating cryptographically secure 256-bit random numbers through a Thunder trusted random number generating pre-compiled contract.

Motivation

When developing smart contracts for Ethereum, developers do not have built-in support for generating cryptographically secure random numbers. While there are some possible solutions (e.g. using Ethereum Alarm r Oraclize), these solutions often rely on external services and are not scalable. To address this need, ThunderCore has implemented built-in support to generate cryptographically secure random numbers through a pre-compiled contract.

Specification ThunderCore has implemented a pre-compiled contract which resides at address 0x8cC9C2e145d3AA946502964B1B69CE3cD066A9C7. This address is the first 20 bytes of sha256("Thunder_Random"). Each invocation of fallback function of trusted random generator precompiled contract will return a 256-bit random number. The gas cost for each invocation is 1265040. Below is an example which can be embedded into a smart contract. ThunderCore also provides a library which can be sued in Remix with import "github.com/thundercore/RandomLibrary/RandomInterface.sol from Github with URL in Remix. The random number generator will always return a bytes32 value, so you will need to cast or convert this value as it suits your needs.

interface ThunderRandomLibraryInterface {
    function rand() external returns (uint256);
}

Example In the basic example shown below, a random number is used to determine whether the contract will pay the user. If the number is greater than the bet from the user, the contract takes the user's wager. If not, the contract pays the user their own bet plus 1.

Note: the require(msg.sender == tx.origin) check in bet() is necessary for security and explained below.

pragma solidity ^0.8.9;

interface ThunderRandomLibraryInterface {
    function rand() external returns (uint256);
}

contract RandomExampleInterfaceVersion {
    event didWin(bool);
    uint256 public contractBalance;
    // Random precompiled contract address is 0x8cC9C2e145d3AA946502964B1B69CE3cD066A9C7
    address constant public randomNumberContractAddress = 0x8cC9C2e145d3AA946502964B1B69CE3cD066A9C7; 
    ThunderRandomLibraryInterface internal RNGLibrary;

    constructor() payable {
        contractBalance = uint256(msg.value);
        RNGLibrary = ThunderRandomLibraryInterface(randomNumberContractAddress);
    }

    function betNumber(uint256 bet) payable external returns (bool) {
        // block calls from other contracts to prevent "revert transaction unless I won" attacks
        require(msg.sender == tx.origin);
        
        if (msg.value < 5) {
            contractBalance = contractBalance + msg.value;

            emit didWin(false);
            return false;
        }

        uint256 randomNumber = RNGLibrary.rand();
        address payable sender = payable(msg.sender);
        if (bet < randomNumber) {
            sender.transfer(msg.value+1);
            emit didWin(true);

            contractBalance = contractBalance - 1;
            return true;
        }

        contractBalance = contractBalance + msg.value;
        emit didWin(false);
        return false;
    }
}

How to prevent Revert the Transaction Unless I Won attacks There's a conceptually simple approach to attack any game of chance on EVM compatible blockchains. The attacker would deploy a contract and do something like:

  1. Play a game of chance

  2. Check if the balance in the attacker's contract decreased after the game

  3. Revert the transaction if the contract balance decreased

In Solidity, the attack would look like this:

    function attack(uint256 v) public  {
        uint256 pool = this.balance;
        /* play game of chance ... */
        require(this.balance >= pool);
    }

Add a require(msg.sender == tx.origin) check at the beginning of your bet function prevents other contracts from calling your own and thus blocks the attack. See the Block and Transaction Properties section in Solidity in Depth for details:

  • msg.sender (address): sender of the message (current call)

  • tx.origin (address): sender of the transaction (full call chain)

Last updated