Provably fair(検証可能な公平性)は、オンラインカジノが誕生して以来、「ゲーム結果が後から運営側によって改ざんされていないことを、プレイヤーはどうやって信頼できるのか?」、という常につきまとってきた課題を解決する仕組みです。 その答えは、暗号技術の世界で、互いを信頼していない当事者同士が安全に合意を形成する際に用いられるコミット・リビール方式にあります。
この記事では、クラウドベットのMinesゲームにおけるProvably Fairの仕組みを、シード生成から地雷配置まで順を追って詳しく解説します。また、基本的なプログラミングの知識があれば、これまでプレイしたすべてのゲーム結果を自分自身で検証できる方法もご紹介します。
本記事は、Cloudbet Originalsで採用されているProvably Fairの仕組みを技術的な視点から解説するシリーズの第1弾です。 Minesを含む各ゲームの結果を実際に検証したい方は、CloudbetのProvably Fair計算ツールをご利用ください。
Contents
プレイ開始前にゲーム結果を確定する仕組み
各ラウンドの開始前に、サーバーはランダムな32バイトのサーバーシードを生成し、そのSHA3-256ハッシュを即座に公開しますーーこれがコミットメントです。 このハッシュは一方向性を持つ指紋のようなもので、シードの内容を公開することなく、その時点ですでに存在していたことを証明できます。
ゲーム終了後、クラウドベットは実際のサーバーシードを公開します。 プレイヤーはハッシュを計算し、ゲーム開始前に公開されていたコミットメントと一致することを確認することができます。これにより、サーバーシード、ひいては地雷の配置が最初のタイルを選ぶ前にすでに確定していたことが証明されます。 つまり、カジノ側がより都合のよい結果へシードを差し替える余地はないということです。
プレイヤーのエントロピー(不確実性)を組み込む
サーバーシードだけでは、プレイヤーはゲーム結果のランダム性をすべてカジノ側に依存することになります。 それを防ぐために、2つの追加値が組み込まれています:それは、クライアントシード(プレイヤーが設定するもの)とノンス(ベットごとに1ずつ増加する整数)です。
これら3つの値を連結し、SHA3-256でハッシュ化することで、そのラウンド固有のラウンドシグネチャーを生成します:
roundSignature = SHA3-256(`${serverSeed}:${clientSeed}:${nonce}`)
クライアントシードが加わることで、サーバーシードが生成される時点ではプレイヤーの入力が不明であるため、カジノはコミットメントの時点で結果を予測することはできなくなります。 また、ノンスによって、ベット間で両方のシードが同じ変わらないとしても、各ラウンドで異なるラウンドシグネチャーが生成されるよう保証されています。
SHAKE256を用いた乱数生成
ラウンドシグネチャーは、SHA-3ファミリーに属する拡張出力関数(XOF)であるSHAKE256に入力されます。 通常のハッシュ関数とは異なり、XOFは固定された入力から任意の長さの擬似ランダムなバイトストリームを生成できます。これは、複数の地雷を配置する際に、複数の独立したランダム値が必要となるMinesの仕組みに適しています。
実装では、このランダムストリームから4バイトずつ値を取得します。 最初に生成された出力を使い切った場合は、状態に「next」を追加して再度値を生成します。ただし実際には、25マスのグリッドで現実的な数の地雷を配置するケースでは、最初の256ビット以内で必要なすべてのランダム値を十分に取得できます。
バイアスのない地雷配置:リジェクションサンプリング
ランダムな32ビット整数を単純なモジュロ演算でグリッド上の位置に変換すると、わずかながら実際の偏りが発生する可能性があります。その理由は、2^32が残りの位置数、Pで均等に割り切れない限り、一部の位置が他の位置よりわずかに選ばれやすくなるためです。
クラウドベットはこの問題をリジェクションサンプリングによって解消しています:
constmaxAcceptable= Math.floor(0x100000000 / P) * P;
letrand;
do {rand=rng.次へ().value; } while (rand >=maxAcceptable);
constindex=rand% P;
Pの最大倍数で32ビットに収まる範囲外の値はすべて破棄され、代わりにSHAKE256ストリームから次の値が抽出されます。 これのより、残るのはP個の候補位置に対して完全に均一分布になります。
地雷が1つ配置されるたびに、その位置はプールから削除され、Pが1つ減算されます。これは、25マスのフラットなグリッド配列に対する部分的なフィッシャー・イェーツ法によるシャッフルと同等の処理です。
ラウンド結果の検証方法
ゲーム終了後の検証には、4つの値が必要であり、これらはすべてラウンド終了後にクラウドベットによって公開されます:
serverSeed(サーバーシード)clientSeed(クライアントシード)nonce(ノンス)- 公開された
minePositions(地雷配置)
検証手順は以下の通りです:
サーバーシードをSHA3-256でハッシュ化し、それがゲーム開始前のコミットメントと一致することを確認します。roundSignature = SHA3-256(serverSeed:clientSeed:nonce)を生成します。roundSignatureを使用してSHAKE256ストリームを初期化し、リジェクションサンプリングの処理を実行して、minePositionsを個別に再生成します。
試合の結果、ラウンド開始後にグリッドが変更されていなかったことが確認できます。
サンプル実装 (Node.js)
npm installjs-sha3
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');
function generateServerSeed() {
const seed = crypto.randomBytes(32).toString('hex');
return { serverSeed: seed, commitment: sha3_256(seed) };
}
function createRoundSignature(serverSeed, clientSeed, nonce) {
return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}
function* createShake256Stream(signature) {
const byteStream = shake256.create(32);
byteStream.update(signature);
while (true) {
const buf = Buffer.from(byteStream.digest({ buffer: true }));
for (let i = 0; i < buf.length; i += 4) {
if (i + 4 <= buf.length) yield buf.readUInt32BE(i);
}
byteStream.update('next');
}
}
function pickUniquePositions(count, totalTiles, rng) {
const available = Array.from({ length: totalTiles }, (_, i) => i);
const result = [];
for (let i = 0; i < count; i++) {
const P = available.length;
const maxAcceptable = Math.floor(0x100000000 / P) * P;
let rand;
do { rand = rng.next().value; } while (rand >= maxAcceptable);
const index = rand % P;
result.push(available[index]);
available.splice(index, 1);
}
return result;
}
const { serverSeed, commitment } = generateServerSeed();
const clientSeed = 'player-provided-seed';
const nonce = 0;
const sig = createRoundSignature(serverSeed, clientSeed, nonce);
const rng = createShake256Stream(sig);
const mines = pickUniquePositions(5, 25, rng);
console.log('Commitment:', commitment);
console.log('Server seed (post-game):', serverSeed);
console.log('Mine positions:', mines);
知っておくべきポイント
なぜSHA2ではなくSHA3を使用するのか? 単純なコミットメント用途であれば、どちらでも十分に機能します。 SHA3のスポンジ構造は、長さ拡張攻撃を確実に排除しており、コミットメントの段階でも、SHAKE256の基盤としても、パイプライン全体で同じ暗号技術体系を採用することで、暗号処理の一貫性を維持しています。
なぜシード付きPRNGではなくSHAKE256を使用するのか? 理由は決定性と検証可能性です。 SHAKE256は、標準化された暗号プリミティブであり、仕様が明確に定義されています。そのため、どのプログラミング言語で実装しても、同じ入力からは同じ出力が生成されます。 独自設計のPRNGでは、実装ごとに挙動が異なる可能性があり、第三者による検証が難しくなる場合があります。
プレイヤーは有利なクライアントシードを選んでシステムを攻略できるのか? 理論上は可能ですーープレイヤーは複数のクライアントシードを試し、有利なグリッド結果を生成するシードを探すことができます。 これはこの仕組みにおいて想定された行為であり、脆弱性ではなく、プレイヤーが自分のシードを選択できること自体が、Provably Fair設計の重要な機能といえます。 カジノ側が受ける影響も同じ仕組みの中で対称的に存在します。そして最も重要なのは、プレイヤーが有利なシードを探せる仕組みとカジノが自分たちのシードを操作していないことを証明できる仕組みは同じだという点です。
Minesゲームは、Provably Fairの仕組みを理解するための入り口として適しています。その理由は、乱数生成パイプラインが比較的シンプルで自己完結しているためです。具体的には、単一のコミット・リビールサイクル、明確なバイトストリーム、そして十分に理解されたサンプリング手法によって構成されているためです。 このシリーズが進むにつれて、他のCloudbet Originalsがどのように同じ基盤を応用・発展させているかを見ていきます。場合によっては、追加のエントロピーソース、異なる出力マッピング、複数段階の検証プロセスなどが導入されます。それでも維持される基本的な保証は変わりませんーーゲーム結果が事前に暗号学的に確定され、プレイヤー自身が独立して検証できるということです。


