導入ドキュメントSpec v1

Provably Fair を
あなたのプラットフォームに。

オリパ、一番くじ、ガチャ、ブラインドボックス、カード合成など13種類の抽選形式に対応。プラットフォームが各記録に Server Seed、Client Seed、Nonce、確率を表示し、プレイヤーはそれを検証ページに貼り付けて、1回ごとにご自身で確認できます。

概要

JACKZ.AI 公正性検証は独立した検証ページです。プラットフォームが各抽選記録に検証用データを公開し、プレイヤーがそれを検証ページに貼り付けると、ページがブラウザ内でその1回を再計算し、結果を表示します。プレイヤー自身が入力して「検証する」を押すので、結果を自分の手で確認できます。

プラットフォームは SDK も API も不要です。計算はすべてプレイヤーのブラウザ内で行われ、データはサーバーに送られません。入力の手間を減らしたい場合はワンクリックリンク(「検証リンク v1」参照)を追加できますが、必須ではありません。

Commit抽選前に Server Seed Hash を公開
Roll抽選ごとに Client Seed、Nonce、結果を記録
Revealシード交換時に旧 Server Seed を公開
検証するプレイヤーがデータを検証ページに貼り付け

用語

ドキュメントと検証ページでは、以下の英語の用語を使います。

Server Seed
プラットフォームが生成するランダムな文字列。32 バイトの暗号論的乱数を16進数で表すことを推奨します。シード交換まで秘密にします。
Server Seed Hash
SHA-256(Server Seed) の16進小文字の文字列。抽選前に公開します。
Client Seed
プレイヤーのシード。プレイヤーが設定するか、プラットフォームがランダムに生成します。大文字・小文字を区別します。
Nonce
同じシードの組における抽選の通し番号。10進整数で、1回抽選するごとに1増えます。
Draw
同じ Nonce 内で何回目の抽選か。0 から始まります。10連ガチャは1つの Nonce を共有し、Draw 0〜9 で区別できます。
Roll
上記のデータから算出される 32 ビット符号なし整数。範囲は 0〜4,294,967,295 です。
確率(weights)
Pick 方式における各賞の整数の重み。一番くじはその1枚を引く時点での各賞の残り枚数を使います。
Threshold
Threshold 方式の成功ゾーン上限。Roll がこれより小さければ成功です。
Seed Rotation
シード交換:旧 Server Seed を公開すると同時に新しいシードの Hash を公開し、以降の抽選は新しいシードで行います。
プラットフォーム手数料
プラットフォームの手数料率。商品ごとに設定し、いつでも変更できます。小数で表し、8% は 0.08、手数料なしは 0 と書きます。カード合成は成功率の計算に使います。任意の検証リンクでのみ検証ページに渡し、プレイヤーが入力することはありません。

導入手順

以下の7ステップをすべて実施して初めて、プレイヤーが各抽選を独立して確認できます。

  1. 抽選前に Server Seed Hash を公開Server Seed を生成し、プレイヤーが抽選する前にその Hash を表示します。可能であればオンチェーンへの記録や証跡保存も行い、以後変更できないようにします。
  2. プレイヤーが Client Seed を設定できるようにする初期値はランダム生成で構いませんが、プレイヤーが自分で変更できる必要があります。プレイヤーが Client Seed を変更した際は、新しい Server Seed への交換もあわせて行うことを推奨します。
  3. 抽選ごとに次の Nonce を使う1回抽選するごとに Nonce を1増やします。再利用、番号飛ばし、結果を理由にした引き直しは禁止です。すべての Nonce に記録が必要です。
  4. 抽選前に確率を公開Pick 方式は各賞の重みを、一番くじは各賞の残り枚数を公開します。Threshold 方式は成功率または計算方法を公開します。
  5. すべての抽選記録を保存client_seed、nonce、draw、weights、ranges または threshold、roll、result を、記録番号と一緒に保存します。
  6. Seed Rotation 時に旧シードを公開プレイヤーが「シード交換」を押したとき、またはシードの期限が来たときに旧 Server Seed を公開し、新しいシードの使用を開始します。
  7. 記録に検証データを公開する各記録に Server Seed Hash、Server Seed(交換後)、Client Seed、Nonce、Draw、その抽選の確率または成功率を表示し、できればそれぞれにコピーボタンを付けます。横に検証ページへの通常のリンクを置きます。データを付ける必要はありません。

アルゴリズム

どの方式もまず Roll を算出し、方式に従って Roll を結果に変換します。すべて整数演算で行い、浮動小数点は使いません。

Roll

Roll derivationHMAC-SHA256
block = HMAC-SHA256(key = server_seed, message = "{client_seed}:{nonce}:{floor(draw / 8)}")
roll  = block[4 × (draw mod 8) .. +4] をビッグエンディアン(big-endian)の符号なし 32 ビット整数として読む
Live · server_seed = テストベクトル数値を変えるとすぐに再計算
message block roll
  • server_seed は UTF-8 の文字列のままキーとして使います。先に16進数からバイト列へ変換しないでください。
  • コロンは半角。nonce と floor(draw / 8) は10進数で、ゼロ埋めしません。
  • HMAC ブロック1つは 32 バイトで、8 回分の Draw に使えます。Draw 8〜15 は2つ目のブロックを使い、以降も同様です。

Threshold:成功/失敗(カード合成)

Threshold modeCard Upgrade
success   = roll < threshold
成功率     = threshold ÷ 4,294,967,296
threshold = floor(素材カード市場価格合計 × (1 − edge_rate) × 2^32 ÷ 目標カード市場価格)
  • 市場価格は10進数の文字列とし、分数で計算して最後に一度だけ切り捨てます。素材カードと目標カードは同じ通貨にしてください。
  • threshold が 4,294,967,296 以上の場合は必ず成功します。
  • プラットフォームが別の合成式を使う場合は threshold だけを公開します。市場価格パラメータは任意の検証リンクでのみ使います。
  • 成功率を直接公開する形式(カード強化など)は threshold = floor(成功率 × 2^32) とし、同じく分数で計算します。例えば成功率 0.35 なら threshold は 1,503,238,553 です。

Pick:賞品抽選(オリパ、一番くじ、ガチャ、ブラインドボックス)

Pick modeWeighted
W      = すべての重みの合計
ticket = floor(roll × W ÷ 4,294,967,296)
result = 0 番目から重みを累積し、累積値が初めて ticket を超えた賞の番号

Pick(確率区間)

賞品表の確率を小数の区間で表すプラットフォームは、この方式を使います。検証ページでは「賞品抽選(確率区間で指定)」を選びます。

Pick modeRanges
f      = round(roll ÷ 4,294,967,296, 12)、さらに round(f, 6)
result = begin ≤ f ≤ end を満たす最初の区間の番号(0 から数え、begin と end を含む)
  • 丸めはどちらも四捨五入です。13 桁目が 5 以上なら切り上げ、6 桁のときも同じです。
  • 区間は小数で 6 桁まで。例:0.000000-0.000999,0.001000-0.019999。
  • プラットフォームが浮動小数点で計算すると、12 桁目がちょうど 5 になるごく一部の Roll で 1 桁ずれることがあります。整数または10進数で計算すれば避けられます。

抽選形式の対応

抽選形式計算方式仕組み
オリパ OripaPick公開された確率に従ってカードを抽選します。
一番くじ Ichiban KujiPick各賞の残り枚数を重みとし、1枚引くごとに更新します。
ガチャ GachaPickレアリティ別の確率に従ってキャラクターやカードを抽選します。
ブラインドボックス Blind BoxPick種類別の確率で、どの種類が出るかを決めます。
ガシャポン GashaponPickカプセルごとに種類別の確率で抽選します。
福袋 Lucky BagPickランク別の確率で福袋の中身を決めます。
ミステリーボックス Mystery BoxPick公開された賞品リストから中身を抽選します。
パック開封 Pack OpeningPickカード枠ごとに Draw を1つ使い、レアリティの確率(重みまたは確率区間)で抽選します。階層のあるパックは階層ごとに weights、labels、Draw を1組公開し、同じ Nonce を使って Draw を「枠 → 階層」の順に連番にします(例:5 枠 × 各 2 階層なら Draw 0〜9)。最初の階層の weights は各レアリティの重みで、次の階層の weights と labels は、前の階層で抽選されたレアリティ内のカードです。
BOX開封シェア Box BreakPickチームや枠をランダムに割り当てます。次の抽選の weights と labels からは、当選済みの項目を外します。
抽選 RafflePick応募者全員が同じ重みで、購入権を抽選します。複数名を抽選するときも、当選済みの人を外します。
抽選キャンペーン Lucky DrawPickキャンペーン賞品を当選枠と重みに従って抽選します。
カード合成 Card UpgradeThresholdRoll がプラットフォームの記録にある Threshold より小さければ成功です。成功率はプラットフォームが決めて公開します。
カード強化 EnhancementThreshold公開された成功率で強化の成否を決めます。
  • roll × W は 2^53 を超えることがあるため、JavaScript では BigInt を使ってください。
  • 各 ticket に対応する Roll の数の差は最大 1 です。W が 1,000,000 以下なら、各賞の確率の相対誤差は 0.03% 未満です。
  • 一番くじは1枚引くごとに、重みが新しい残り枚数に変わります。記録には、その1枚を引いた時点の重みを公開してください。

コード例

3つのバージョンは同じアルゴリズムで、言語の組み込み関数だけを使い、下のテストベクトルで確認済みです。PHP 版のカード合成 threshold は bcmath 拡張を使います。

// JACKZ.AI Fair Verify v1:Node.js 18 以上、組み込みモジュールのみ使用
import { createHash, createHmac } from "node:crypto";

const SPACE = 2n ** 32n;

// 抽選前に公開:SHA-256(Server Seed)、16進小文字
export function seedHash(serverSeed) {
  return createHash("sha256").update(serverSeed, "utf8").digest("hex");
}

// draw 番目の Roll:0〜4,294,967,295
export function roll(serverSeed, clientSeed, nonce, draw = 0) {
  const block = createHmac("sha256", serverSeed)
    .update(`${clientSeed}:${nonce}:${Math.floor(draw / 8)}`)
    .digest();
  return BigInt(block.readUInt32BE((draw % 8) * 4));
}

// Threshold:Roll が threshold 未満なら成功
export function isSuccess(r, threshold) {
  return r < BigInt(threshold);
}

// カード合成の threshold。金額は10進数の文字列で渡し、浮動小数点誤差を避ける
export function upgradeThreshold(inputValue, targetPrice, edgeRate) {
  if (!/^0(\.\d+)?$/.test(edgeRate ?? "")) throw new Error("edge_rate は必須で、0 以上 1 未満の小数。手数料なしは 0");
  if (![inputValue, targetPrice].every((s) => /^\d+(\.\d+)?$/.test(s ?? ""))) throw new Error("市場価格は10進数の数字にしてください");
  const frac = (s) => {
    const [i, f = ""] = String(s).split(".");
    return [BigInt(i + f), 10n ** BigInt(f.length)];
  };
  const [a, ad] = frac(inputValue), [t, td] = frac(targetPrice), [e, ed] = frac(edgeRate);
  return (a * (ed - e) * SPACE * td) / (ad * ed * t);
}

// Pick:weights は各賞の整数の重み(一番くじは各賞の残り枚数)。賞の番号を返す
export function pick(r, weights) {
  const w = weights.map(BigInt);
  const ticket = (r * w.reduce((x, y) => x + y, 0n)) / SPACE;
  let cum = 0n;
  for (let i = 0; i < w.length; i++) {
    cum += w[i];
    if (ticket < cum) return i;
  }
  throw new Error("weights の合計は 0 より大きくなければなりません");
}

// ワンクリック検証リンク(Link Format v1)
export function verifyLink(base, params) {
  return `${base}#${new URLSearchParams({ v: "1", ...params })}`;
}

テストベクトル

導入前に、以下の数値で実装を確認してください。すべての数値は検証ページで再計算できます。

server_seedd790b1978d538dea96a16942291c4efa2d5e631ca48db984c1f2e9e7b738b434
server_seed_hashcae64244e12924e26d2eb5e9dd90820650d6cb377944a13eee82c2df31d56f93
client_seedMySeed2026

Roll

noncedrawroll
102079044362
50441912824
184185532748
192271708478

Threshold

input_valuetarget_market_priceedge_ratethreshold
401000.051632087572
30.00100.000.051224065679
50950.052147483648
401000.081580547964
41.80108.5001654650995
40100(なし)検証を拒否
401001検証を拒否

Pick

rollweightsresult
4419128241,4,20,752
20790443621,4,20,753

Pick(確率区間)

rollf(6 桁)rangesresult
11631909750.2708270.000000-0.270826, 0.270827-1.0000001
11631909750.2708270.000000-0.270827, 0.270828-1.0000000
21668110.0005050.000000-0.000504, 0.000505-1.0000001

検証の範囲

検証ページが照合するのは「結果がコミットされたシードから算出されたこと」です。以下に、証明できることと単独では証明できないことを示します。プラットフォームが公に説明する際は、このとおり正確に記載してください。

検証で証明できること

  • この1回は入力された Server Seed、プレイヤーの Client Seed、Nonce から計算されており、その Server Seed は入力された Server Seed Hash と一致します。
  • プラットフォームが記録した Roll と結果が、再計算と一致しています。
  • 市場価格が含まれる場合、カード合成の成功率が市場価格とプラットフォーム手数料から算出した値と一致しています。

別途確認が必要なこと

  • 確率が抽選前の公開内容と同じか:重みはプラットフォームが提供するため、プレイヤーは抽選前に見た確率と照らし合わせる必要があります。
  • 番号飛ばしや引き直しの有無:「まとめて検証」で連続した Nonce を再計算し、プラットフォームの抽選履歴と1件ずつ照合します。
  • 入力された Server Seed Hash が、プレイヤーが抽選前に見たものと同じかどうか。
  • 市場価格そのものが妥当かどうか:市場価格とプラットフォーム手数料はプラットフォームが提供し、検証ページはその数値から計算した成功率だけを照合します。
  • 一番くじの残り枚数が正しいか、賞品が実際に届けられたか。

バージョン

リンク内の v は形式のバージョンで、現行バージョンは 1 です。

2026-10-24 · v1Pick(重みと確率区間)と Threshold の2つの計算方式、プレイヤー自身の入力による検証、まとめて検証、そして任意のワンクリック検証リンク。