๐Ÿ“– Lucky Spin โ€” Provably Fair Documentation

Everything about how Lucky Spin outcomes are provably fair, and how to verify them yourself.

maczo Lucky Spin โ€” Provably Fair

Welcome. This wiki explains, in full, how the Lucky Spin wheel on maczo.co proves every result is fair โ€” and how you can verify it yourself, with no need to trust us.

Lucky Spin uses a commit-first design: we lock in a secret server seed and publish its hash before you spin, so we can't change the outcome after seeing your bet. Every result is a pure function of three values you can inspect.

Quick links

  • ๐Ÿงฎ Verifying Your Spins โ€” step-by-step, with the browser & CLI tools
  • ๐Ÿ” The Algorithm โ€” the exact HMAC-SHA256 formula + rejection sampling
  • โ“ FAQ โ€” common questions

The three inputs

Value Who controls it When you see it
Server seed maczo (secret) its SHA-256 hash is shown before you spin; the raw seed is revealed when you Rotate
Client seed you (editable on the Lucky Spin page) always
Nonce server counter, +1 per spin shown for each spin

Try the verifier now

  • Live (offline in your browser): https://verify.maczo.co/
  • Source / Node CLI: https://github.com/maczo-co/lucky-spin

Every Verify โ†— link in your spin history on maczo.co opens the verifier pre-filled with that spin's data and computes the result instantly.

Verifying Your Spins

You can verify any spin in three ways. All of them run the same math the server used โ€” nothing is trusted, everything is recomputed.

What you need

From the Lucky Spin page (๐Ÿงพ Spin history & verify), each spin shows: - Server Seed Hash (commitment) โ€” shown before the spin - Client Seed โ€” yours - Nonce โ€” the spin number - Server Seed โ€” appears only after you press Rotate (which reveals it)

To fully reproduce the segment, you also need the weights (segment odds). These are published in weights.maczo.json and are embedded automatically in the site's Verify โ†— links.

Method 1 โ€” One-click (easiest)

On maczo.co open Lucky Spin โ†’ Spin history & verify, press Rotate seed to reveal the server seed, then click Verify โ†— next to any spin. It opens https://verify.maczo.co/ with everything filled in and computes instantly.

Method 2 โ€” Browser, manual

  1. Open https://verify.maczo.co/ (or index.html from the repo โ€” works fully offline).
  2. Paste Server Seed, Commitment, Client Seed, Nonce, and the weights JSON.
  3. Press Verify spin. You'll see:
  4. โœ…/โŒ whether SHA-256(serverSeed) matches the commitment
  5. the reproduced segment index and prize

Method 3 โ€” Node.js CLI

git clone https://github.com/maczo-co/lucky-spin
cd lucky-spin

# verify one spin
node verify.js \
  --commit  <serverSeedHash> \
  --server  <revealed serverSeed> \
  --client  <your clientSeed> \
  --nonce   <n> \
  --weights @weights.maczo.json

# confirm the code matches the spec with bundled known-answer vectors
node verify.js --selftest

What "verified" proves

  • Commitment match โ†’ the server committed to this exact seed before you played; it could not have been chosen after seeing your bet or client seed.
  • Reproduced segment โ†’ the published result is exactly what the algorithm produces from the seeds and weights โ€” not a number the operator picked.

If either check fails, the result was tampered with. (It won't โ€” that's the point.)

The Algorithm

Lucky Spin's outcome is fully deterministic: the same (serverSeed, clientSeed, nonce, weights) always produces the same segment. Here is exactly what the server computes โ€” the verifier in this repo reproduces it byte-for-byte.

1. Commitment

Before your first spin, the site shows:

commitment = SHA-256(serverSeed)      // 64 hex chars, lowercase

The raw serverSeed stays secret while it is active. The commitment proves the seed already existed and cannot be swapped later โ€” any change would produce a different SHA-256.

2. Per-spin randomness

For each spin:

digest = HMAC-SHA256(key = serverSeed, message = "clientSeed:nonce:cursor")   // cursor starts at 0
  • serverSeed is the HMAC key (UTF-8 bytes).
  • message is the UTF-8 string clientSeed + : + nonce + : + cursor.
  • The result is 32 bytes.

3. Unbiased mapping (rejection sampling)

The 32-byte digest is read as four 8-byte big-endian unsigned integers (uint64). To keep the odds exactly as configured we reject the small biased tail:

total  = sum(weights)
limit  = 2^64 - (2^64 mod total)      // largest exact multiple of total

For each 8-byte word in order: if word >= limit, skip it (biased โ€” would distort odds); otherwise it is accepted. If all four words are skipped, increment cursor and re-HMAC (astronomically rare).

4. Segment selection

The accepted word is reduced and walked across the cumulative weights:

pick = acceptedWord mod total
acc  = 0
for i in 0..weights.length-1:
    acc += weights[i]
    if pick < acc: return i        // this is the winning segment index

segmentIndex indexes into the wheel's segment list (weight > 0 only, in prize-table order). The prize shown is segments[segmentIndex].

5. Reveal

When you Rotate the seed, the site permanently retires it and reveals the original serverSeed. You can then check SHA-256(serverSeed) == commitment and re-run every past spin under that seed.

Reference implementations (this repo)

  • verify.js โ€” Node.js (uses crypto), CLI + --selftest
  • index.html โ€” offline browser (Web Crypto subtle.digest / subtle.sign)
  • test-vectors.json โ€” known-answer cases both must reproduce

Both use BigInt for exact uint64 arithmetic. See the server's ProvablyFairWheel for the canonical source.

FAQ

What does "provably fair" actually mean here?

The wheel result is a pure cryptographic function of a server seed (which we commit to before you spin), your client seed, and a nonce. Because the commitment is published first, we cannot change the outcome afterward. You can recompute every result yourself โ€” see Verifying Your Spins.

Why is the server seed hidden at first?

If we revealed it up front, the outcome of every future spin would be predictable. Instead we publish SHA-256(serverSeed) (the commitment). When you Rotate, the raw seed is revealed so you can verify the whole history โ€” and the hash proves we never swapped it.

Can maczo change where the wheel lands?

No. The server seed is fixed and its hash is published before you spin. Changing the seed would break SHA-256(serverSeed) == commitment. The nonce is a server-owned counter that only ever increases, so no result can be replayed.

Do I have to trust maczo's website to verify?

No. The browser verifier (index.html) runs entirely offline โ€” view source and confirm it makes no network calls. Or run the Node CLI. Or re-implement the algorithm in any language.

What are the "weights"?

They are the relative frequency of each wheel segment (the paytable). They are published in full in weights.maczo.json and embedded in the site's verify links, so the exact segment can be reproduced.

Why does the verifier need weights but the site hides some numbers?

It doesn't hide them โ€” the prize values are printed on the wheel and the weights are published. You need the weights only to map the random number to a segment; the cryptography above guarantees that mapping is honest.

The cursor in the formula is almost always 0 โ€” why is it there?

It only advances in the extremely rare case that all four 8-byte words of one HMAC digest fall in the biased tail and are rejected. It guarantees the sampling never runs out of randomness while staying perfectly unbiased.

My verification failed โ€” what now?

Double-check you used the revealed server seed (after Rotate), the exact client seed and nonce shown for that spin, and the published weights. If it still fails, contact support with the values โ€” but the math is deterministic, so a correct input always reproduces the published result.

Is this the same method as Stake / BC.Game?

Yes โ€” the same HMAC-SHA256 commit-reveal family, with rejection sampling to remove modulo bias. The implementation is open source so anyone can audit it.