Skip to main content
Every settled game exposes a proof with everything needed to recompute the winner: the commitment, the revealed server seed, the per-player client seeds and weights, and the nonce. Nothing is hidden, and the check below uses only the Python standard library — no Luckie code, no dependencies, no network.

1. Get the proof

Pull the proof JSON from the API:
curl -s https://api.luckie.bet/games/<game_id>/proof
Or open the human-readable proof page in the app: https://luckie.bet/games/<game_id>/proof. It looks like this:
{
  "game_id": "a1b2c3…",
  "mode": "coinflip",
  "nonce": "9f2ac8…",
  "server_seed_hash": "sha256 of the server seed, published at game creation",
  "server_seed": "revealed only after the game settles",
  "winner": "<winner wallet>",
  "players": [
    { "user_id": "<wallet A>", "client_seed": "…", "weight": 1000000 },
    { "user_id": "<wallet B>", "client_seed": "…", "weight": 1000000 }
  ]
}
server_seed is null until the game settles. Before settlement you can only see the commitment server_seed_hash — which is the whole point: the outcome is sealed in advance.

2. The verifier

Save this as verify_fairness.py. It’s the exact algorithm Luckie uses, reimplemented with stdlib only so you never have to trust our code:
verify_fairness.py
#!/usr/bin/env python3
"""Luckie fairness verifier — recompute a settled game's winner from its public proof.

Standard library only: no dependencies, no network, no database.

Usage:
    python verify_fairness.py proof.json
    curl -s https://api.luckie.bet/games/<id>/proof | python verify_fairness.py -
"""
import hashlib
import hmac
import json
import sys


def canonical_message(game_id, nonce, players):
    # Sort by user_id so the input is independent of join order — every verifier
    # reconstructs the identical message regardless of who joined first.
    parts = sorted(f"{p['user_id']}:{p['client_seed']}" for p in players)
    return f"{game_id}|{nonce}|{'|'.join(parts)}".encode()


def outcome_int(server_seed, game_id, nonce, players):
    # outcome = HMAC-SHA256(key=server_seed, msg=game_id|nonce|sorted player seeds)
    digest = hmac.new(
        server_seed.encode(), canonical_message(game_id, nonce, players), hashlib.sha256
    ).digest()
    return int.from_bytes(digest, "big")


def pick_winner(mode, server_seed, game_id, nonce, players):
    n = outcome_int(server_seed, game_id, nonce, players)
    ordered = sorted(players, key=lambda p: p["user_id"])
    if mode == "coinflip":
        if len(players) != 2:
            raise ValueError("coinflip requires exactly 2 players")
        return ordered[n % 2]
    if mode == "jackpot":
        total = sum(int(p.get("weight", 1)) for p in players)
        if total <= 0:
            raise ValueError("total weight must be positive")
        target = n % total  # odds are weight-proportional
        cumulative = 0
        for p in ordered:
            cumulative += int(p.get("weight", 1))
            if target < cumulative:
                return p
    raise ValueError(f"unknown mode: {mode!r}")


def verify(proof):
    # 1. The revealed seed must match the commitment published at game creation.
    if hashlib.sha256(proof["server_seed"].encode()).hexdigest() != proof["server_seed_hash"]:
        return False
    # 2. Recomputing the outcome must reproduce the published winner.
    winner = pick_winner(
        proof["mode"], proof["server_seed"], proof["game_id"], proof["nonce"], proof["players"]
    )
    return winner["user_id"] == proof["winner"]


def main(argv):
    if len(argv) != 2:
        print(__doc__)
        return 2
    raw = sys.stdin.read() if argv[1] == "-" else open(argv[1], encoding="utf-8-sig").read()
    proof = json.loads(raw)
    ok = verify(proof)
    print(f"{'PASS' if ok else 'FAIL'}: game {proof['game_id']} winner={proof['winner']!r}")
    return 0 if ok else 1


if __name__ == "__main__":
    raise SystemExit(main(sys.argv))

3. Run it

# straight from the API
curl -s https://api.luckie.bet/games/<game_id>/proof | python verify_fairness.py -

# or from a saved file
python verify_fairness.py proof.json
A passing game prints:
PASS: game a1b2c3… winner='<winner wallet>'
If it ever prints FAIL, that’s cryptographic proof the result didn’t match its committed seed — and we’d want to know immediately.

How it works

1

Commitment check

sha256(server_seed) must equal the server_seed_hash that was published before anyone deposited. This proves the seed wasn’t swapped after the fact.
2

Deterministic outcome

The random integer is HMAC-SHA256(key=server_seed, msg=game_id | nonce | sorted player seeds). Players’ client seeds are mixed in after the server committed, so the server can’t grind a seed to force a winner.
3

Winner selection

Coinflip: players sorted by wallet, winner = outcome % 2. Jackpot: target = outcome % total_weight, then walk players (sorted by wallet) accumulating weight until target falls in a player’s band — so odds are exactly proportional to your buy-in.
The server reveals the seed only after settlement. Before that the outcome is sealed — neither the server nor any player can know or change it.