Skip to main content

Overview

The FHE library provides on-chain encrypted random number generation. Random values are generated as encrypted ciphertexts, maintaining privacy throughout the computation.

Import

import { FHE, euint32, euint64, ebool } from "@fhevm/solidity/lib/FHE.sol";
Random values are generated by the FHE coprocessor and are cryptographically secure. They remain encrypted and can only be revealed through decryption.

Random Boolean

FHE.randEbool()

Generate an encrypted random boolean value.
returns
ebool
Encrypted random boolean (true or false with equal probability)
Signature:
function randEbool() returns (ebool)
Example:
contract RandomDemo is ZamaEthereumConfig {
    ebool private randomFlag;
    
    function generateRandomBool() external {
        randomFlag = FHE.randEbool();
        FHE.allowThis(randomFlag);
        FHE.allow(randomFlag, msg.sender);
    }
    
    function getRandomBool() external view returns (ebool) {
        return randomFlag;
    }
}
Gas Cost: ~100k gas

Random Unsigned Integers

FHE.randEuint8() through randEuint256()

Generate encrypted random unsigned integers of various sizes.
returns
euintXX
Encrypted random unsigned integer in full range [0, 2^bits - 1]
Signatures:
function randEuint8() returns (euint8)      // 0-255
function randEuint16() returns (euint16)    // 0-65535
function randEuint32() returns (euint32)    // 0-4294967295
function randEuint64() returns (euint64)    // 0-2^64-1
function randEuint128() returns (euint128)  // 0-2^128-1
function randEuint256() returns (euint256)  // 0-2^256-1
Example:
contract RandomNumberGenerator is ZamaEthereumConfig {
    euint8 private random8;
    euint32 private random32;
    euint64 private random64;
    
    function generateRandom8() external {
        random8 = FHE.randEuint8();
        FHE.allowThis(random8);
        FHE.allow(random8, msg.sender);
    }
    
    function generateRandom32() external {
        random32 = FHE.randEuint32();
        FHE.allowThis(random32);
        FHE.allow(random32, msg.sender);
    }
    
    function generateRandom64() external {
        random64 = FHE.randEuint64();
        FHE.allowThis(random64);
        FHE.allow(random64, msg.sender);
    }
}
Gas Cost (approximate):
  • randEuint8(): ~100k gas
  • randEuint32(): ~125k gas
  • randEuint64(): ~150k gas
  • randEuint256(): ~200k gas

Bounded Random Numbers

FHE.randEuintXX(upperBound)

Generate encrypted random unsigned integers in range [0, upperBound).
upperBound
uintXX
required
Upper bound (exclusive) - must be plaintext
returns
euintXX
Encrypted random unsigned integer in range [0, upperBound)
Signatures:
function randEuint8(uint8 upperBound) returns (euint8)
function randEuint16(uint16 upperBound) returns (euint16)
function randEuint32(uint32 upperBound) returns (euint32)
function randEuint64(uint64 upperBound) returns (euint64)
function randEuint128(uint128 upperBound) returns (euint128)
function randEuint256(uint256 upperBound) returns (euint256)
Example:
contract RandomInRange is ZamaEthereumConfig {
    /// @notice Generate random number from 0 to 99
    function generateDiceRoll() external returns (euint8) {
        euint8 roll = FHE.randEuint8(100);  // [0, 99]
        FHE.allowThis(roll);
        FHE.allow(roll, msg.sender);
        return roll;
    }
    
    /// @notice Generate random number from 1 to 6 (dice)
    function generateD6() external returns (euint8) {
        euint8 roll = FHE.randEuint8(6);    // [0, 5]
        euint8 dice = FHE.add(roll, 1);     // [1, 6]
        FHE.allowThis(dice);
        FHE.allow(dice, msg.sender);
        return dice;
    }
    
    /// @notice Generate random index for array of size n
    function generateIndex(uint32 arraySize) external returns (euint32) {
        euint32 index = FHE.randEuint32(arraySize);  // [0, arraySize-1]
        FHE.allowThis(index);
        FHE.allow(index, msg.sender);
        return index;
    }
}
Alternative - Using Modulo:
// Generate random 0-99 using modulo
function randomInRange(uint32 max) external returns (euint32) {
    require(max > 0, "Max must be > 0");
    euint32 random = FHE.rem(FHE.randEuint32(), max);
    FHE.allowThis(random);
    FHE.allow(random, msg.sender);
    return random;
}

Use Cases

Use Case 1: Encrypted Lottery

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

import { FHE, euint32, ebool } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";

contract EncryptedLottery is ZamaEthereumConfig {
    uint32 public constant MAX_TICKETS = 1000;
    
    mapping(uint256 => euint32) private tickets;
    mapping(uint256 => address) public ticketOwners;
    uint256 public ticketCount;
    
    euint32 private winningTicket;
    bool public hasDrawn;
    
    event TicketPurchased(address indexed buyer, uint256 ticketId);
    event WinnerDrawn();
    
    /// @notice Buy ticket with encrypted number
    function buyTicket(uint32 number) external payable {
        require(msg.value >= 0.01 ether, "Insufficient payment");
        require(ticketCount < MAX_TICKETS, "Sold out");
        
        uint256 ticketId = ticketCount++;
        tickets[ticketId] = FHE.asEuint32(number);
        ticketOwners[ticketId] = msg.sender;
        
        FHE.allowThis(tickets[ticketId]);
        FHE.allow(tickets[ticketId], msg.sender);
        
        emit TicketPurchased(msg.sender, ticketId);
    }
    
    /// @notice Draw winner (random)
    function drawWinner() external {
        require(ticketCount > 0, "No tickets");
        require(!hasDrawn, "Already drawn");
        
        // Generate random winning ticket
        winningTicket = FHE.randEuint32(MAX_TICKETS);
        FHE.allowThis(winningTicket);
        
        hasDrawn = true;
        emit WinnerDrawn();
    }
    
    /// @notice Check if ticket won (encrypted comparison)
    function isWinner(uint256 ticketId) external view returns (ebool) {
        require(hasDrawn, "Not drawn yet");
        ebool won = FHE.eq(tickets[ticketId], winningTicket);
        return won;
    }
    
    /// @notice Get winning ticket number (for decryption)
    function getWinningTicket() external view returns (euint32) {
        require(hasDrawn, "Not drawn yet");
        return winningTicket;
    }
}

Use Case 2: Random Selection from Pool

contract RandomSelector is ZamaEthereumConfig {
    address[] public candidates;
    
    function addCandidate(address candidate) external {
        candidates.push(candidate);
    }
    
    /// @notice Select random candidate
    function selectRandom() external returns (address) {
        require(candidates.length > 0, "No candidates");
        
        // Generate random index
        euint32 randomIndex = FHE.randEuint32(uint32(candidates.length));
        
        // Note: Cannot use encrypted index directly to access array
        // Must decrypt off-chain or use alternative approach
        FHE.allowThis(randomIndex);
        FHE.makePubliclyDecryptable(randomIndex);
        
        // Client decrypts index, then contract can select winner
        return address(0);  // Placeholder - requires decryption flow
    }
}

Use Case 3: Encrypted Dice Game

contract DiceGame is ZamaEthereumConfig {
    mapping(address => euint8) private playerRolls;
    mapping(address => euint8) private houseRolls;
    
    event GamePlayed(address indexed player);
    
    /// @notice Play dice game (1-6)
    function play() external payable returns (euint8 playerRoll, euint8 houseRoll) {
        require(msg.value >= 0.01 ether, "Minimum bet 0.01 ETH");
        
        // Roll for player: 1-6
        euint8 player = FHE.add(FHE.randEuint8(6), 1);
        
        // Roll for house: 1-6
        euint8 house = FHE.add(FHE.randEuint8(6), 1);
        
        playerRolls[msg.sender] = player;
        houseRolls[msg.sender] = house;
        
        FHE.allowThis(player);
        FHE.allow(player, msg.sender);
        FHE.allowThis(house);
        FHE.allow(house, msg.sender);
        
        emit GamePlayed(msg.sender);
        
        return (player, house);
    }
    
    /// @notice Get game result (encrypted comparison)
    function getResult() external view returns (ebool) {
        euint8 player = playerRolls[msg.sender];
        euint8 house = houseRolls[msg.sender];
        
        // Player wins if roll > house roll
        return FHE.gt(player, house);
    }
}

Use Case 4: Shuffled Deck Index

contract CardGame is ZamaEthereumConfig {
    euint8[] private deck;
    uint8 public constant DECK_SIZE = 52;
    
    /// @notice Generate shuffled deck
    function shuffleDeck() external {
        delete deck;
        
        // Generate random value for each card
        for (uint8 i = 0; i < DECK_SIZE; i++) {
            euint8 randomValue = FHE.randEuint8();
            deck.push(randomValue);
            FHE.allowThis(randomValue);
        }
    }
    
    /// @notice Draw encrypted card
    function drawCard(uint8 index) external view returns (euint8) {
        require(index < deck.length, "Invalid index");
        return deck[index];
    }
}

Best Practices

1. Always Set ACL After Generation

// ❌ WRONG: Missing ACL
euint32 random = FHE.randEuint32();

// ✅ CORRECT: Set ACL
euint32 random = FHE.randEuint32();
FHE.allowThis(random);
FHE.allow(random, msg.sender);

2. Use Bounded Random When Possible

// ❌ Less efficient
euint32 bounded = FHE.rem(FHE.randEuint32(), 100);

// ✅ More efficient (if available)
euint32 bounded = FHE.randEuint32(100);

3. Generate Once, Store for Multiple Uses

// ❌ Wasteful: Generate twice
euint32 r1 = FHE.randEuint32();
euint32 r2 = FHE.randEuint32();

// ✅ Generate once if same random value needed
euint32 random = FHE.randEuint32();
euint32 doubled = FHE.mul(random, 2);

4. Use Appropriate Type Size

// ❌ Wasteful for dice roll (1-6)
euint256 dice = FHE.randEuint256();

// ✅ Efficient: euint8 sufficient
euint8 dice = FHE.add(FHE.randEuint8(6), 1);

Security Considerations

Randomness is Cryptographically Secure

Random values are generated by the FHE coprocessor using cryptographically secure methods. They cannot be predicted or manipulated by miners or users.

Random Values Remain Encrypted

Random values are encrypted. To use them in plaintext logic, you must decrypt them using FHE.makePubliclyDecryptable() or client-side decryption.

Fairness in Games

// ✅ Fair: Both players roll simultaneously
function playGame() external {
    euint8 player1Roll = FHE.randEuint8(6);
    euint8 player2Roll = FHE.randEuint8(6);
    // Compare encrypted rolls
}

// ❌ Unfair: Second player can see first roll before deciding
function roll() external {
    playerRolls[msg.sender] = FHE.randEuint8(6);
    FHE.makePubliclyDecryptable(playerRolls[msg.sender]);  // Leaks!
}

Gas Optimization

1. Use Smallest Type for Range

// ❌ Expensive: euint256 for 0-99
euint256 random = FHE.rem(FHE.randEuint256(), 100);

// ✅ Cheaper: euint8 sufficient
euint8 random = FHE.randEuint8(100);

2. Generate Off-Chain When Possible

// ❌ Generate on-chain
function generateMany() external {
    for (uint i = 0; i < 10; i++) {
        numbers[i] = FHE.randEuint32();  // 10 * ~125k gas
    }
}

// ✅ Generate one seed, derive others
function generateSeed() external {
    seed = FHE.randEuint256();  // 1 * ~200k gas
    // Derive other values using seed + index
}

Important Notes

Encrypted Output: All random functions return encrypted values. You cannot use them directly in plaintext if statements.
Gas Cost: Random generation costs ~100-200k gas depending on type. Plan accordingly for gas budgets.
Bounded Generation: Use FHE.randEuintXX(upperBound) for bounded ranges - it’s more efficient than using modulo.

Build docs developers (and LLMs) love