隨機數生成在諸如賭博、遊戲贏家選擇或隨機種子生成等應用中至關重要。然而,在以太坊上生成隨機數是一項挑戰,因為以太坊的區塊鏈是確定性的(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.timestamp
、block.difficulty
和msg.sender
結合keccak256
來生成隨機數。- 問題:
- 礦工可以操縱
block.timestamp
(在合理範圍內調整時間戳)或block.difficulty
,影響keccak256
的輸出。 - 攻擊者可能預測或通過試驗推導出隨機數(特別是在區塊數據公開後)。
- 這種隨機數生成方式並非真正的隨機,容易被操控,導致攻擊者能以高機率猜中
answer
。
- 礦工可以操縱
影響
不安全的隨機數生成可能導致以下嚴重後果:
- 不公平的遊戲或彩票結果:攻擊者可以預測或操縱隨機數,獲得不公平的優勢,從而在賭博、彩票或其他依賴隨機性的應用中獲勝。
- 資金損失:如範例所示,攻擊者可能猜中隨機數,提取合約中的資金,導致其他參與者或協議損失。
- 信任危機:不安全的隨機數破壞了合約的公平性和完整性,導致用戶對協議失去信任,影響其採用率和生態系統。
修復方法
為確保隨機數生成的公平性和安全性,開發者可採用以下方法:
使用外部預言機(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.timestamp
或 block.difficulty
)在以太坊的確定性環境中容易被操縱,導致不公平的遊戲結果、資金損失和信任危機。攻擊者可通過預測或影響隨機數獲取不當利益。修復方法包括使用 Chainlink VRF、承諾方案或可信預言機來確保隨機數的公平性和安全性。修復後的合約展示了如何整合 Chainlink VRF,提供可驗證的隨機數,防止操縱並保護合約的完整性。開發者應避免使用區塊鏈內部資料作為隨機數來源,並進行全面測試以確保隨機性安全。