🎲

Aptos Onchain Randomness実践入門:セキュアな抽選システムの実装とExpo Token配布への応用

に公開

はじめに

ブロックチェーン上で「公平なランダム性」を確保することは、多くのdApps、特にゲームや抽選システムにとって長年の課題でした。従来の方法は、セキュリティ上の脆弱性を抱えていたり、外部のオラクルに依存することで実装が複雑化したりする問題を抱えていました。

本記事では、この課題を解決するAptosブロックチェーンの革新的な機能「Onchain Randomness」に焦点を当てます。実際にこの機能を用いて、セキュアで透明性の高いトークンエアドロップ抽選スマートコントラクト(AptosJapan/airdrop_lottery)を実装した経験を基に、その技術的詳細、実装上の工夫、そして実用性を徹底解説します。Aptosでの開発に興味を持つすべてのエンジニアにとって、実践的な知見を提供できれば幸いです。

なぜAptos Onchain Randomnessなのか?

スマートコントラクトで信頼できる乱数を生成するのは、なぜ難しいのでしょうか。その背景と、Aptosが提供するエレガントな解決策を見ていきましょう。

従来の乱数生成とその課題

多くのブロックチェーンでは、オンチェーンで利用可能な情報(タイムスタンプやブロックハッシュなど)を乱数ソースとして利用する試みがありましたが、これらは予測可能性や操作可能性といった深刻な脆弱性を内包していました。

例えば、タイムスタンプを乱数生成に利用した場合、悪意のあるマイナー(またはバリデーター)がトランザクションを含めるブロックを操作することで、特定の結果を意図的に引き出すことが可能になります。

この問題を回避するために、drandのような外部の乱数ビーコンを利用するアプローチもありますが、以下のような新たな複雑さを生み出します。

  1. 非同期な処理: スマートコントラクトは、将来生成される乱数にコミットし、その結果を待つ必要があります。
  2. 複雑な実装: 外部ソースから取得した乱数をオンチェーンで検証するためのロジックが必要になります。
  3. ユーザビリティの低下: 複数のステップを踏む必要があり、ユーザー体験を損ないます。

Aptosが提供する革新的な解決策「Aptos Roll」

Aptosは、これらの課題を根本から解決するために、Aptos Rollと呼ばれるセキュアで即時利用可能なオンチェーン乱数APIをプロトコルレベルで提供しています [1]。

// Aptos Onchain Randomnessを利用したシンプルな抽選ロジック
#[randomness] // このアトリビュートが鍵
entry fun decide_winner() {
    let lottery_state = load_lottery_state_mut();
    let n = vector::length(&lottery_state.players);
    // APIを呼び出すだけで、セキュアな乱数を即座に取得
    let winner_idx = aptos_framework::randomness::u64_range(0, n);
    lottery_state.winner_idx = std::option::some(winner_idx);
}

このアプローチの核となるのは、weighted Verifiable Random Function (wVRF) と呼ばれる暗号技術です。これにより、Aptosネットワークのバリデーター群が分散的に乱数を生成し、その結果は数学的に検証可能でありながら、50%以上のステークを持つ攻撃者でなければ予測・操作が不可能です [2]。

特徴 説明
予測不可能性 50%以上のステークがなければ、乱数の結果を事前に知ることはできません。
操作不可能性 50%未満のステークを保有する悪意のあるバリデーターは、結果に影響を与えることができません。
即時性 スマートコントラクトはAPIを呼び出すと、その場で即座に乱数を取得できます。
シンプルさ 複雑な外部連携は不要。#[randomness]アトリビュートとAPI呼び出しだけで利用可能です。

実装:Aptosエアドロップ抽選コントラクト

理論的な素晴らしさを確認したところで、次はこのAptos Onchain Randomnessを実際にどう活用したか、私たちが開発したairdrop_lotteryコントラクトを例に解説します。

アーキテクチャの概要

コントラクトは、責務の分離を意識して2つのモジュールで構成されています。

  • airdrop_lottery.move: 抽選の作成、参加者管理、状態管理など、メインのインターフェースを定義します。
  • lottery_logic_details.move: shuffle_and_selectなど、乱数を利用した抽選ロジックの核心部分を実装します。

技術的ハイライトと実装の工夫

1. Undergasing攻撃への対策:動的アルゴリズム選択

抽選ロジックを実装する上で最も考慮すべき脅威の一つが「Undergasing攻撃」です。これは、大量の参加者がいる場合に処理のガス代が高騰し、トランザクションが失敗することを利用した攻撃です。

この対策として、参加者の数に応じて抽選アルゴリズムを動的に切り替える戦略を採用しました。

// lottery_logic_details.move より
fun shuffle_and_select(participants: &vector<address>, count: u64): vector<address> {
    let threshold = 100; // 閾値

    if (count <= threshold) {
        // 参加者が少ない場合:直接選択方式
        // 参加者リストから都度ランダムに当選者を選び、リストから削除する。
        // ガス効率が良い。
    } else {
        // 参加者が多い場合:Fisher-Yatesシャッフル方式
        // 全参加者リストをシャッフルし、先頭からN名を当選者とする。
        // 計算量は増えるが、より公平性が高い。
        let shuffled = shuffle_all(&participants_copy);
        // ...
    }
}

この設計により、小規模な抽選ではガス効率を、大規模な抽選では公平性と堅牢性を両立させています。

2. Fisher-Yatesシャッフルによる公平な抽選

大規模抽選で採用しているのが、Fisher-Yatesアルゴリズムです。これは、配列をランダムにシャッフルするための古典的で信頼性の高いアルゴリズムです。

// lottery_logic_details.move の shuffle_all 関数
fun shuffle_all(list: &vector<address>): vector<address> {
    let length = vector::length(list);
    let result = *list;
    let i = length;
    while (i > 1) {
        i = i - 1;
        // randomness::u64_rangeでセキュアなインデックスを生成
        let j = randomness::u64_range(0, i + 1);
        if (j != i) {
            // 要素をスワップ
            vector::swap(&mut result, i, j);
        };
    };
    result
}

randomness::u64_rangeをループ内で使用することで、各ステップで予測不可能なスワップを実現し、結果として完全にランダムな順列を生成します。

3. モダンなAptos Frameworkの活用

本コントラクトでは、ガス効率とパフォーマンスを最大化するために、Aptos Frameworkの最新機能を積極的に採用しています。

  • SmartVector / SmartTable: 従来のvectortableに比べ、大規模なデータセットを扱う際のガス消費を大幅に削減します。
  • Event v2: より構造化され、効率的なイベント通知を可能にします。

これらのモダンなデータ構造を利用することで、将来的に数万、数十万規模の参加者にも対応可能なスケーラビリティを確保しています。

セキュリティと堅牢性

security_verification.mdに詳述されている通り、本コントラクトは多角的なセキュリティ検証を実施しています。

  • アクセス制御: signer::address_ofを用いて、抽選の作成者のみが管理機能を実行できるよう厳格に制限。
  • タイミング制約: timestamp::now_seconds()を利用し、締切時間前の抽選実行や締切後の参加を確実に防止。
  • データ整合性: 参加者の重複登録や、当選者数の不正な設定をassert!でチェック。
  • 包括的なエラーコード: 想定されるすべての異常系をエラーコードとして定義し、問題発生時の原因特定を容易にしています。

実際の使用方法

Aptos CLIを使えば、この抽選コントラクトを簡単に利用できます。

# 1. 抽選を作成する
aptos move run --function-id <YOUR_ADDR>::airdrop_lottery::create_lottery \
  --args string:"Expo Token Airdrop" string:"Celebrating our new feature!" u64:100 u64:1735689600

# 2. 参加者を追加する
aptos move run --function-id <YOUR_ADDR>::airdrop_lottery::add_participant \
  --args u64:1 'address:["0xabc...", "0xdef...", "0xghi..."]'

# 3. 締切後に抽選を実行する
aptos move run --function-id <YOUR_ADDR>::airdrop_lottery::draw_winners --args u64:1

# 4. 当選者を確認する
aptos move view --function-id <YOUR_ADDR>::airdrop_lottery::get_winners --args u64:1

まとめ

Aptos Onchain Randomnessは、これまでブロックチェーン開発者を悩ませてきた「信頼できる乱数」の問題に対する、非常に強力かつエレガントなソリューションです。今回のairdrop_lotteryコントラクトの実装を通じて、そのシンプルさ、セキュリティ、そして実用性の高さを改めて実感しました。

特に、Undergasing攻撃のような実践的な脅威を考慮し、参加者規模に応じてアルゴリズムを動的に切り替える設計は、Aptosの柔軟なプログラミングモデルとガス効率の良い実行環境があってこそ実現できたものです。

ブロックチェーン上で公平性が求められるアプリケーションを構築する際、Aptos Onchain Randomnessは間違いなく最も有力な選択肢の一つとなるでしょう。本記事が、皆さんの次のプロジェクトのヒントとなれば幸いです。


参考文献

[1] Aptos Foundation. (2025). Randomness API. Aptos Documentation. https://aptos.dev/build/smart-contracts/randomness

[2] Tomescu, A., & Xiang, Z. (2024). Roll with Move: Secure, instant randomness on Aptos. Aptos Labs Medium. https://aptoslabs.medium.com/roll-with-move-secure-instant-randomness-on-aptos-c0e219df3fb1

Discussion