# Provably Fair Verification

Provably fair means a settled game can be independently recomputed from public,
on-chain data — you do not have to trust Arcade Cafe's word that a result was honest.

Arcade Cafe runs on **Protocol V1**: a Solana program for custody and settlement,
with an off-chain operator that executes games and periodically commits state +
bet-log roots on-chain. Every bet is replayable from on-chain inputs, and a
provably-bad outcome can be force-refunded on-chain via `challenge_outcome`.

## The Randomness Model

Each outcome is a deterministic function of four inputs, three of which are
committed *before* the result is known:

| Input | Who commits it | When |
| --- | --- | --- |
| **Operator RNG seed** `S_i` | Operator | Committed as a hash chain *before* play; revealed on-chain afterward (`RngReveal`). |
| **Client seed** | Player | The player sends `commitment = SHA256("v1-client-seed" ‖ clientSeed)` *before* the operator acks; the raw seed is revealed at reveal time. |
| **Slot hash** `H_accept` | Solana | A recent slot hash, fixed in the operator ack before the seed is revealed. |
| **Bet nonce + params** | Player | Part of the signed bet payload. |

Because the operator commits its seed chain ahead of time and the player commits
their seed before the operator picks `H_accept`, **neither side can grind the
result**: the operator never sees the player's seed before fixing its inputs, and
the player can't change their seed after seeing the operator's.

There is **no HMAC and no per-game "server seed"** — that was the legacy model.
V1 uses domain-separated SHA-256 over the four inputs above, matching the on-chain
program byte-for-byte.

## The Outcome Function

The result is derived by rejection-sampling SHA-256 digests (this is exactly what
the on-chain program and the standalone verifier compute):

```text
digest(counter) = SHA256(
  DOMAIN,                # "v1-dice-outcome" or "v1-sweeper-outcome"
  revealedSeed,          # 32 bytes, S_i from the on-chain RngReveal
  clientSeedRaw,         # 32 bytes, the revealed player seed
  betNonce,              # u64 little-endian
  encodedParams,         # canonical Borsh-encoded game params
  slotHash,              # 32 bytes, ack.slotHash
  [counter],            # 1 byte, 0,1,2,... until an unbiased value is found
)
```

**Dice** reads the digest in 4-byte big-endian chunks and takes the first value
below `floor(2^32 / 10000) * 10000` (rejection sampling to avoid modulo bias),
then `slot = value % 10000` (a roll of `slot / 100`). Win/payout follow the
`direction`/`targetSlot` and the 1.5% house edge.

**Sweeper** reads the digest byte-by-byte and takes the first value below
`floor(256 / columns) * columns`, then `resultColumn = value % columns`. Each won
row compounds the per-row 3% edge; cashout settles at the current multiplier.

## What You Need To Verify

After settlement, the bet-log entry and the on-chain `RngReveal` give you
everything:

```json
{
  "revealedSeed": "…32-byte hex (RngReveal.seed at ack.rngEpoch/rngIndex)…",
  "clientSeedRaw": "…32-byte hex…",
  "slotHash": "…32-byte hex (ack.slotHash)…",
  "betNonce": 7,
  "wager": 1000000,
  "params": { "direction": 0, "targetSlot": 5000 },
  "claimedSlot": 4217,
  "claimedWin": true,
  "claimedPayout": 1970000
}
```

To verify:

```text
1. commitment == SHA256("v1-client-seed" ‖ clientSeedRaw)   (binds the player seed)
2. SHA256(revealedSeed) chains back to the committed RngReveal head  (binds the operator seed)
3. recompute slot/column from the outcome function above
4. recomputed slot/column, win, and payout == the claimed values
```

If any recomputed value differs, the outcome is invalid — and anyone can submit
`challenge_outcome` on-chain to force a `RefundCredit` for the underpayment.

## Independent Verifier

A standalone TypeScript verifier that mirrors the on-chain function is published at
[`/verifiers/arcade-cafe-proof.ts`](/verifiers/arcade-cafe-proof.ts). It exports
`verifyDiceProof` and `verifySweeperProof`, needs only `node:crypto` (SHA-256), and
does not call Arcade Cafe to decide whether a result is valid. Run it against any
settled bet's public data.
