罠
ハッシュは32バイトです。ゲームには0から1の間の数字が必要です。明らかな方法は、いくつかのバイトを整数として読み込み、最大可能な値で割ることです。Provably fairのあらゆる説明者(私たちを含む)はそのように説明しています。この明らかな方法には、2つの異なるプログラムがそれを行うときにのみ現れる問題があります。
サーバーとブラウザの両方が通常の数値をIEEE 754ダブルとして保存します:64ビット、このうち53が精度を持ちます。ハッシュの7バイトは56ビット、8バイトは64ですが、どちらも適合しません。したがって、変換はビットを失う必要があり、問題は丸めるかどうかではなく、何回丸めるかです。バイトを1つずつ累積するループで、実行中の合計に256を掛けて次のバイトを追加すると、合計が2⁵³を超えた後の各ステップで丸められます。Kotlinの64ビット整数からダブルへの変換は1回だけ丸めます。同じ値の2つの丸めは、1つと同じダブルに常にはたどり着きません。
ほとんどの場合、16の有効数字を持つ数値の最後のビットの差であるため、誰も気づきません。次に、ゲームは数値に37、52、または1,000,000を掛けて、整数に丸め下げます。最後のビットは、36.9999999999999が36になるか37になるかを決定するビットです。明らかな方法で構築された検証者は、小数の完全に正直なラウンドを拒否し、拒否を見たプレイヤーは正直な丸めエラーを不正なサーバーと区別する方法がありません。
エンジンがそれをどのようにステップバイステップで処理するか
修正は、ブラウザがサーバーと同じ回数、つまり1回だけ丸めるようにすることです。エンジンは最初の4バイトを整数として読み込み、これはダブルに正確に適合し、次の3~4バイトを別の整数として読み込み、これも正確に適合します。それぞれは2の累乗で除算されます。これはダブルでは正確な操作です。次に、この2つが追加されます。この単一の加算は精度が失われる唯一の場所であり、Kotlinの単一の変換と同じ方法で失われます。
ENGINE-VERIFIED_shared/rng.ts u56: 「最初の7バイト → 均一[0,1)、サーバーのLong→Double丸めとビット単位で一致:hi/2^32およびlo/2^56は両方とも正確なダイアディックダブルであるため、1つのIEEE加算は56ビット値を1回だけ丸めます — これはKotlinのtoDouble()が実行する単一の丸めと同じです。(ダブルでのナイーブな56ビット累積は2^53を超えて繰り返し丸めることができ、フロアエッジで異なる場合があります。)」u64は「サーバーのULong→Double変換をLimboService.outcomeで100に一致させる」同じ構造を8バイトで使用し、すべてのシードペアオリジナルインポートが行う関数です。
| 関数 | 読み込みバイト | 構造 | 一致 |
|---|---|---|---|
| u56 | 最初の7 | hi ÷ 2³² + lo ÷ 2⁵⁶、1つの加算 | サーバーのLong → Double変換 |
| u64 | 最初の8 | hi ÷ 2³² + lo ÷ 2⁶⁴、1つの加算 | サーバーのULong → Double変換(Limbo) |
| h52 / bust100 | 最初の6.5 (13進法) | 正確なBigInt整数演算、doubleは一切使用しない | サーバーのCrashDerivation.crashPoint100 |
すべてのシード対の元データ(ビンゴ、ブラックジャック、ホールデム、小判、おみくじ、シックボー、ビデオポーカー、花火、福袋、ルーレット)はrng.tsからu64をインポート;ジェネレーターとベリファイアーは同じインポートを使用
Crashは引数をその結論まで実行します。その爆発ポイントはbustabitの公式に従い、floor((100 × 2⁵² − h) ÷ (2⁵² − h))です。ここでhはハッシュの上位52ビットであり、この除算はdoubleが1だけずれる可能性がある境界に正確に当たります。したがって、エンジンはdoubleを使用しません。これはサーバーと同じ方法で、ブラウザ内で任意精度整数を使用して公式を計算します。ソースのコメントは理由を1行で説明しています:浮動小数点近似はfloor境界での整数除算と異なり、ベリファイアーが正しいラウンドを却下することになります。
ENGINE-VERIFIED_shared/rng.ts bust100: h = h52(digest)、最初の13進文字をBigIntとして;p = (100·2⁵² − h) ÷ (2⁵² − h)をBigInt除算で、[100, 1,000,000]に制限;コメントは「サーバー(NewWhiteBack CrashDerivation.crashPoint100)と完全に一致するBigInt数学 — 浮動小数点近似はfloor境界での整数除算と異なり、ベリファイアーが正しいラウンドを却下することになります。」BigInt値はリテラルではなくBigInt()呼び出しで構築されているため、ファイルはプロジェクトのes5ターゲット下でコンパイルされます。
1つの実装、2つのジョブ
同じファイルにはもう1つの静かな決定があります。ハッシュを数値に変換する関数は、ゲーム用と検査用で2回書かれません。これらはジェネレーターとベリファイアーで共有されるモジュールに1度書かれており、ヘッダーはこのモジュールについてそう説明しています。ブラウザでサインアウトしたラウンドをプレイするデモエンジンは、フェアネスパネルがそれらをチェックするために呼び出すのと同じ関数を呼び出すことで結果を生成します。
ENGINE-VERIFIED_shared/rng.ts ヘッダー:「Crypto プリミティブはprovably-fair GENERATORで共有されます(demoLocal.ts、ゲームごとのderivedモジュール)とVERIFIER(fairness.tsx)。fairness.tsxから抽出され、ゲームの導出は循環インポートなしで両側でインポートできます。」hmacSha256Utf8はキーとメッセージを「RandomUtils.generateHashがサーバーで消費するのとまったく同じように」消費するものとして説明されています...1つの実装なので、デモはベリファイアーが受け入れるものから決して漂流することはできません。」
その設計には、述べることが簡単で述べる価値がある結果があります。ベリファイアーがラウンドが成功したと言う場合、それはサーバーのおおよその計算が許容値内でサーバーと一致していると言っているのではありません。同じ関数が同じ入力で同じ出力を生成したと言っており、許容する何もないので許容値はありません。ラウンドが成功しなかったと言う場合、それはノイズではありません。入力が異なることを意味し、これはベリファイアーが存在する唯一のものです。
サーバーとは異なるように丸めるベリファイアーは、時々狼少年を泣く声を上げるベリファイアーです。最初の誤検知の後、誰も本当のものに耳を傾けません。丸めが重要な理由
それから何を取るか
- 「Provably fair」は算術に関する主張であり、算術には境界があります。コミットメントスキームはヘッドラインです;ビット単位の導出はヘッドラインを実行可能にするものです。
- 不一致は十分にまれで、警報を鳴らす必要があります。このエンジンでは、正直なラウンドは丸め処理のため検証に失敗することはできないため、失敗は成果物ではなく情報です。
- 関数を読むことができます。構造は数行であり、検証ウォークスルーはライブラウンドでそれぞれがどのように使用されるかを示します。
この記事はクライアント導出モジュールについて説明し、それがミラーするサーバーについてのコメントを引用しています;サーバー自体は公開リポジトリにはなく、ハウスサービスは支払い当局です。これは認証ではなく、エンジニアリングノートです。
FAQ
ベリファイアーとサーバーが同じハッシュを使用する場合、なぜ意見が異なることがありますか?
ハッシュを0から1の間の数値に変換するには、53ビットの精度に丸める必要があり、1回の丸めはいつも複数回の丸めと同じ結果を与えるわけではありません。ループでバイトを蓄積するベリファイアーは、一度に変換するサーバーとは異なる場合があり、最後のビットで異なります。
1ビットは本当に重要なのか?
数値に37や52や100万を掛けて切り下げた場合、最後のビットがどの整数になるかを決定する可能性があります。つまり、異なるポケット、カード、またはアイテムになるということです。
エンジンはどのようにしてそれを回避するのか?
2つの正確に表現可能な部分から数値を構築します。最初の4バイトは2³²を超え、次のバイトは2⁵⁶または2⁶⁴を超えます。その後、1回加算し、サーバーの整数からダブルへの変換の単一丸めと一致させます。Crashは正確な整数演算を使用し、ダブル型をまったく使用しません。
VerifierはGameとは別のプログラムですか?
いいえ。ハッシュと数値関数は1つの共有モジュール内に存在し、デモエンジンとフェアネスパネルの両方がインポートするため、ジェネレーターはVerifierから逸脱することはできません。
このエンジンで検証失敗は何を意味するのか?
サーバーが使用した入力と異なることを意味します。なぜなら、このエンジンではroundingが誤った失敗を引き起こすことができないためです。これはVerifierが信号を送るために存在する証拠です。
ソースと参考資料
- Betkyo engine source: _shared/rng.ts (u56, u64, h52, bust100, hmacSha256Utf8 およびそれらのコメント)
- IEEE 754-2019 — 浮動小数点演算の標準 (binary64: 仮数部精度53ビット)
- Bustabit provably fair crash-point formula、構造bust100が反映
この記事のゲーム
Limbo — ルール & 無料プレイ →Crash — ルール & 無料プレイ →Roulette — ルール & 無料プレイ →