【入門編】 ランダム性生成の脆弱性(ブロックハッシュの予測可能性) – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

こんにちは!Web3やIoTのセキュリティの世界へようこそ。セキュリティリサーチャーの私です。

今日は、スマートコントラクト(ブロックチェーン上のプログラム)開発で一番やりがちな「落とし穴」、そして攻撃者が一番ニヤリとしてしまう「ランダム性生成の脆弱性(ブロックハッシュの予測可能性)」についてお話しします。

「ブロックチェーンって全部の記録が公開されていて、すごく安全なんでしょ?」と思っていませんか?実は、その「すべてが見えている」という性質が原因で、ある大きな弱点を持っているのです。

今回は、セキュリティを学び始めたばかりのあなたに向けて、身近な例えを使いながら、攻撃の仕組みと対策を優しく紐解いていきましょう。一歩ずつ、一緒に学んでいきましょうね!

—

1. 家の鍵で例える「予測できるランダム」の怖さ

まずは、身の回りの防犯に例えて考えてみましょう。

想像してみてください。あなたは今、家の玄関に「最新式のデジタル金庫」を設置しました。その金庫を開けるための暗証番号は、誰にも破られないように、毎日お昼の12時00分00秒に、家の外の電光掲示板に表示される「その日の天気予報の数字(気温と湿度)」の組み合わせで自動的に決まる仕組みに設定したとします。

……これ、めちゃくちゃ危険だとは思いませんか?

なぜなら、その電光掲示板の数字は、「誰でも・いつでも・事前に」見ることができるからです。泥棒は、12時00分00秒の数秒前にそこへ行って数字を盗み見れば、あなたが帰ってくる前に簡単に金庫を開けて中身をごっそり持ち去ることができますよね。

ブロックチェーンの世界における「ブロックハッシュ(ブロックの指紋のようなもの)」を使った乱数生成も、これとまったく同じことをやっているのです。

—

2. なぜブロックチェーンの「ブロックハッシュ」を乱数にしてはいけないのか?

スマートコントラクトで宝くじアプリや、ランダムなアイテムが出るNFTゲームを作るとします。そのとき、プログラムに「次のブロックのハッシュ値(blockhashやblock.difficultyなど)を割った余りを、ランダムな当選番号にしよう!」と書きたくなる気持ち、すごくよく分かります。

だって、パッと見は複雑な英数字の羅列ですし、「誰も次にどんなハッシュが生成されるかなんて予測できないだろう」って思ってしまいますよね。

しかし、ここにサイバー攻撃者が狙う最大の盲点があります。

攻撃者が使う「裏技」のメカニズム

ブロックチェーン上でトランザクション(取引や操作)が実行されるとき、攻撃者は次のようなずる賢い手を使います。

1. 攻撃者は、自分で作った「悪意あるスマートコントラクト(攻撃用のプログラム)」を用意します。
2. そのプログラムから、あなたの宝くじコントラクトを呼び出します。
3. ブロックチェーンの仕組み上、「同じブロックの中」であれば、そのブロックのハッシュ値を事前に計算・予測(あるいは条件分岐で試行)できる場合があります。
4. 攻撃者のプログラムは、「もし今回の計算だと自分がハズレてしまう(大損する)」と分かった瞬間、そのトランザクションをわざと失敗(キャンセル)させます。
5. 「アタリ」のときだけトランザクションを成功させ、賞金を丸取りします。

つまり、カジノのイカサマで言えば、「ダイスを振った後に、自分の都合が悪い出目だったら、なかったことにできる(振り直せる)」というチート行為を許してしまうわけです。これが、ブロックハッシュの予測可能性が持つ恐ろしさです。

—

3. 【危険なコード例】やってはいけない実装

実際のSolidity(スマートコントラクトの言語)で、絶対に書いてはいけない「危険なコード」の例を見てみましょう。

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

// 【危険な例】ブロックハッシュをそのまま乱数に使うコントラクト
contract InsecureLottery {
    
    // 宝くじを引いて、ランダムに勝者を決める関数
    function playLottery() public payable {
        require(msg.value == 1 ether, "Participation fee is 1 ether");

        // 危険:ブロックハッシュやブロックのタイムスタンプは予測・操作可能です!
        uint256 randomValue = uint256(
            keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp, msg.sender))
        );

        // 乱数をもとに勝敗を決める
        if (randomValue % 2 == 0) {
            // 当たりの処理(全額持ち逃げされるリスクあり)
            payable(msg.sender).transfer(address(this).balance);
        }
    }
}

このコードのどこがいけないのでしょうか?コメントにもある通り、blockhash(block.number - 1) や block.timestamp は、マイナー(ブロックを作る人)や巧妙な攻撃者によってある程度コントロールされたり、結果を見てから取引を仕掛けられたりする隙を与えてしまいます。

—

4. 安全な乱数生成はどうやるの?(Chainlink VRFの登場)

「じゃあ、ブロックチェーンの世界では公正なランダムな数字なんて作れないの?」
いいえ、そんなことはありません。ここで登場するのが、現実世界の信頼できるデータをブロックチェーンに安全に持ってくる仕組みである「オラクル(Oracle)」、その中でもランダム生成に特化した「Chainlink VRF(Verifiable Random Function:検証可能なランダム関数)」です。

イメージとしては、自分たちの閉じた部屋の中でこっそり決めるのではなく、「絶対に信用できる第三者(第三者機関の公的な抽選マシーン)に、ブロックチェーンの外でランダムな数字を作ってもらい、それが本物であるという証明書(暗号学的証明)付きで持ってきてもらう」という方法です。

Chainlink VRFを使った安全なコード例

実務の開発現場でよく使われる、安全なパターンを見てみましょう(※概念を分かりやすく簡略化しています)。

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

// Chainlinkの公式インターフェースをインポートする想定
// import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract SecureLottery { // 実際は VRFConsumerBaseV2 を継承します

    // 安全な乱数を受け取るための関数(Chainlinkオラクルにリクエストを送る)
    function requestRandomNumber() public onlyOwner {
        // 外部の信頼できるオラクル(Chainlink)へ乱数生成を依頼する
        // 依頼すると、オラクル側で安全なランダム値が計算されます
        // 算出された数値は、暗号学的な「証明書」と一緒に私たちのコントラクトへ送り返されます
    }

    // オラクルから安全な乱数が届いたときに自動で呼ばれるコールバック関数
    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        // ここに届く randomWords[0] は、誰も予測できず、改ざんもできない真の乱数です!
        uint256 secureRandom = randomWords[0];
        
        // 安全な乱数を使った公正な処理
        if (secureRandom % 2 == 0) {
            // 当たりの処理
        }
    }
}

このように、外部の安全な乱数プロバイダを挟むことで、攻撃者が「結果を見てから取引をキャンセルする」というイカサマを防ぐことができるのです。

—

5. まとめと現場からのアドバイス

今回は、ブロックハッシュの予測可能性というスマートコントラクト特有の脆弱性と、その対策についてお話ししました。

  • ブロックのハッシュ値やタイムスタンプは「誰でも見通せる電光掲示板」のようなもの。これを乱数源にしてはいけない!
  • 攻撃者は結果を見てからトランザクションを操作(キャンセル)するので、宝くじやゲーム系は一瞬で破綻する。
  • 実務では Chainlink VRF などの外部オラクルを活用し、暗号学的に安全で検証可能なランダム性を取り入れよう。

スマートコントラクトの開発は、一度デプロイ(公開)してしまうと、後からコードを簡単に修正するのが難しいため、最初の設計段階からこうしたセキュリティの落とし穴に気づけるかどうかが勝負の分かれ道になります。

最初は難しい用語が多くて面食らうかもしれませんが、一歩ずつ仕組みを理解していけば大丈夫です。一緒に安全で楽しいWeb3の未来を作っていきましょう!それではまた次回のセキュリティ解説でお会いしましょう。

コメント

タイトルとURLをコピーしました