許多 DApp(抽獎、NFT mint、遊戲)會需要隨機數。但在區塊鏈上「看起來隨機」的值(像 block.timestamp、blockhash)其實並非真隨機,可能被礦工或驗證者操控一點點,足以讓攻擊者獲利。今天我們解釋為什麼鏈上隨機性不可信、攻擊向量,以及實務上更安全的替代方案(例如 Chainlink VRF 與 commit–reveal)。
🔍 為何 block.timestamp / blockhash 不可信?
礦工/驗證者可以操控時間(timestamp)
• 區塊時間由打包區塊的礦工/驗證者設定,雖然節點會有合理範圍限制(不能隨意天馬行空的時間),但礦工可以在允許範圍內微調數秒到數十秒,足以改變 % 運算等簡單亂數公式的結果。
• 在高利益場景(例如大量獎金)這些秒數就是攻擊者的利潤來源。
blockhash 與 recent block 的可預測性
• blockhash 通常只能取得最近 N 個區塊的雜湊(例如 Ethereum 限制),且當前區塊在被打包前也可能被前一個礦工預測或影響。
• 如果隨機數依賴可被觀察到或提前影響的值,攻擊者可在 mempool 觀察/操控交易來布局套利策略。
重排(Reorg)或自挖(Self-mining)
• 若攻擊者有部分算力或做得成 validator collusion,可能透過重排或自己打出特定區塊來改變隨機來源結果(雖然成本較高,但對高價值獎勵可能還是划算)。
⚠️ 攻擊向量示例(簡化)
• 抽獎合約 winner = uint(keccak256(block.timestamp, msg.sender)) % N:礦工能調整 timestamp 使自己或合作夥伴成為中獎者。
• NFT mint 階段:觀察 mempool,先發高優先費交易或延遲交易以影響 blockhash 計算結果,達到搶到稀有屬性。
✅ 較安全的替代方案(與示意)
commitPhase: submit keccak256(secret)
revealPhase: submit secret -> require(hash(secret) == commit)
finalRandom = keccak256(seedFromAllReveals)
requestRandomness() -> VRF node generates random + proof -> callback fulfillRandomness(random, proof) -> contract verifies & uses random
🛡️ 實務設計建議(checklist)
• 不要用 block.timestamp、block.difficulty、blockhash(近期)做為唯一隨機來源。
• 若是低價值或娛樂用途,blockhash + user-provided entropy 可能「足夠」,但明確告知風險。
• 若需高安全性:使用 Chainlink VRF 或其它已驗證的外部 VRF。
• 若希望去中心化且不用第三方:採 commit–reveal 並設計懲罰/保證機制,或採用 threshold randomness。
• 在白皮書與合約 README 清楚說明隨機性來源與風險,讓使用者知情同意。
在鏈上「看似隨機」的東西往往是最容易被利用的。以前我也常用 block.timestamp % n 當作快速做法,但做過幾次模擬後就發現:當獎金夠大時,總有人願意花點成本去操控結果。
實務上,選擇哪種隨機性解決方案是一個折衷(安全 vs 成本 vs 便利)。如果你的遊戲或抽獎涉及實際金錢,請務必把隨機來源當成頭等大事來設計,而不是臨時寫幾行算式就交差了。