SC Insecure Randomness

隨機數生成在諸如賭博、遊戲贏家選擇或隨機種子生成等應用中至關重要。然而,在以太坊上生成隨機數是一項挑戰,因為以太坊的區塊鏈是確定性的(Deterministic),即所有節點必須對同一輸入產生相同結果,這使得生成真正的隨機數幾乎不可能。因此,Solidity 通常依賴偽隨機(Pseudorandom)因素來模擬隨機性。此外,Solidity 中的複雜計算會消耗大量 gas,增加了設計隨機數生成器的成本。

開發者常使用區塊鏈相關資訊來生成隨機數,但這些方法不安全,因為它們可能被礦工(Miners)或攻擊者操縱。常見的不安全隨機數生成來源包括:

  • block.timestamp:當前區塊的時間戳。
  • blockhash(uint blockNumber):指定區塊的哈希值(僅限最近 256 個區塊)。
  • block.difficulty:當前區塊的挖礦難度。
  • block.number:當前區塊編號。
  • block.coinbase:當前區塊的礦工地址。

這些來源容易被礦工操縱(例如,調整時間戳或選擇有利區塊),從而影響合約的隨機數生成結果。

範例(存在漏洞的合約)

以下是一個使用不安全隨機數生成的合約範例:

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

contract Solidity_InsecureRandomness {
    constructor() payable {}

    function guess(uint256 _guess) public {
        uint256 answer = uint256(
            keccak256(
                abi.encodePacked(block.timestamp, block.difficulty, msg.sender) // Using insecure mechanisms for random number generation
            )
        );

        if (_guess == answer) {
            (bool sent,) = msg.sender.call{value: 1 ether}("");
            require(sent, "Failed to send Ether");
        }
    }
}

漏洞分析

  • guess 函數:使用 block.timestampblock.difficultymsg.sender 結合 keccak256 來生成隨機數。
  • 問題
    • 礦工可以操縱 block.timestamp(在合理範圍內調整時間戳)或 block.difficulty,影響 keccak256 的輸出。
    • 攻擊者可能預測或通過試驗推導出隨機數(特別是在區塊數據公開後)。
    • 這種隨機數生成方式並非真正的隨機,容易被操控,導致攻擊者能以高機率猜中 answer

影響

不安全的隨機數生成可能導致以下嚴重後果:

  1. 不公平的遊戲或彩票結果:攻擊者可以預測或操縱隨機數,獲得不公平的優勢,從而在賭博、彩票或其他依賴隨機性的應用中獲勝。
  2. 資金損失:如範例所示,攻擊者可能猜中隨機數,提取合約中的資金,導致其他參與者或協議損失。
  3. 信任危機:不安全的隨機數破壞了合約的公平性和完整性,導致用戶對協議失去信任,影響其採用率和生態系統。

修復方法

為確保隨機數生成的公平性和安全性,開發者可採用以下方法:

使用外部預言機(Oracles)

  • 通過可信的外部隨機數來源(如 Oraclize)提供隨機數。但需謹慎選擇可信的預言機,或使用多個預言機來降低單點故障風險。

承諾方案(Commitment Schemes)

  • 採用「承諾-揭示」(Commit-Reveal)方法,參與者先提交加密承諾,後揭示結果,防止提前操縱。例如,RANDAO 是一種基於多方承諾的隨機數生成方案,適用於去中心化應用。

Chainlink VRF

  • 使用 Chainlink 的可驗證隨機函數(VRF),這是一種經過證明公平且可驗證的隨機數生成器,專為智能合約設計,確保隨機數不被操縱。

Signidice 演算法

  • 適用於涉及雙方的偽隨機數生成(PRNG),使用加密簽名來確保隨機性,適合特定場景。

比特幣區塊哈希

  • 通過如 BTCRelay 的預言機,從比特幣區塊鏈獲取區塊哈希作為隨機性來源。但需注意,礦工可能影響比特幣區塊內容,因此需謹慎實現。

安全隨機數的範例合約

以下是使用 Chainlink VRF 產生安全的隨機數生成合約:

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

import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";

contract Solidity_InsecureRandomness is VRFConsumerBase {
    bytes32 internal keyHash;
    uint256 internal fee;
    uint256 public randomResult;

    constructor(address _vrfCoordinator, address _linkToken, bytes32 _keyHash, uint256 _fee) 
        VRFConsumerBase(_vrfCoordinator, _linkToken) 
    {
        keyHash = _keyHash;
        fee = _fee;
    }

    function requestRandomNumber() public returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK");
        return requestRandomness(keyHash, fee);
    }

    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
        randomResult = randomness;
    }

    function guess(uint256 _guess) public {
        require(randomResult > 0, "Random number not generated yet");
        if (_guess == randomResult) {
            (bool sent,) = msg.sender.call{value: 1 ether}("");
            require(sent, "Failed to send Ether");
        }
    }
}

修復內容

使用 Chainlink VRF

  • 合約繼承 VRFConsumerBase,通過 requestRandomness 向 Chainlink VRF 請求隨機數。
  • fulfillRandomness 回調函數接收生成的隨機數,存儲在 randomResult 中。

檢查條件

  • guess 函數要求 randomResult 已生成(> 0),確保隨機數有效。

總結

不安全的隨機數生成(如依賴 block.timestampblock.difficulty)在以太坊的確定性環境中容易被操縱,導致不公平的遊戲結果、資金損失和信任危機。攻擊者可通過預測或影響隨機數獲取不當利益。修復方法包括使用 Chainlink VRF、承諾方案或可信預言機來確保隨機數的公平性和安全性。修復後的合約展示了如何整合 Chainlink VRF,提供可驗證的隨機數,防止操縱並保護合約的完整性。開發者應避免使用區塊鏈內部資料作為隨機數來源,並進行全面測試以確保隨機性安全。