Luckotto

Demosino

GameDemosino
Playerdemo

Draw calculator

Replay the weighted tile-elimination draw from a minor seed, draw block hash, and final ticket CSV.

Loaded /rounds/2.csv (final)
Tickets125
Jackpot Prize0.00027705 BTC
Draw resultReady
Final draw tilesPending
Eliminated tiles0
Live ticketsPending
Elimination orderPending
Winning ticketHidden until complete
Next possible eliminationElimination 1
125 live tickets, 0 eliminated tiles
13.09%
22.76%
32.82%
42.87%
52.90%
62.86%
72.70%
83.13%
92.61%
102.77%
112.74%
122.70%
132.81%
142.84%
152.70%
162.73%
172.60%
182.62%
192.46%
202.88%
212.73%
222.70%
232.67%
242.63%
253.08%
262.85%
272.67%
282.68%
292.76%
302.85%
312.91%
322.90%
332.68%
342.67%
352.87%
362.75%
Draw paper

Slow public randomness with a suspense reveal

The draw is deliberately split into two jobs. First, Luckotto commits to the exact final ticket list and minor seed hash before the random block exists. Second, once a future Bitcoin block supplies the public chain entropy and the minor seed is revealed, the winner is revealed through intentionally slow tile eliminations that anyone can replay from public data.

The slow hash chain is a security feature, not an accident.The draw block hash is public entropy, but a miner who finds a block could theoretically withhold it long enough to test whether that block makes their own ticket win. Luckotto makes that test expensive in wall time: every elimination sample runs a fixed protocol constant of 200,000 sequential SHA-256 hashes, and a sequential chain cannot be skipped to the end or parallelized.
Commitment timeline
1Ticket list lockedThe exact final ticket CSV is hashed and committed on chain.
2Commitment blockThe commitment transaction confirms before the random seed exists.
3Future block hashThe block 2 blocks later supplies the public draw seed.
4Slow eliminationEach elimination sample runs a long sequential SHA-256 chain.

Threat Model And Rationale

A normal verifier does not need hidden server state. They need the final CSV bytes, the committed CSV hash, the commitment transaction, the committed minor seed hash, the revealed minor seed, the commitment block height, and the draw block hash at commitment_block_height + 2. The operator cannot change the ticket list after seeing that later block without breaking the CSV hash commitment.

The remaining randomness attack worth designing around is block withholding. If checking a candidate block were instant, a miner with a ticket could find a block, privately run the draw, publish favorable blocks, and discard unfavorable ones. The slow SHA-256 chain raises the cost of that decision. The miner must spend scarce time after finding a candidate block before knowing whether withholding is useful, while the rest of the network may find and publish a competing block.

Why The Reveal Is Incremental

A slow draw creates a UX problem: a direct one-shot draw would leave the page silent until the full computation finished. Luckotto instead uses the slow work to eliminate losing tiles. Each eliminated tile is a real part of the settlement calculation. The live ticket set stays as large as the odds allow until the process leaves exactly one public ticket.

The incremental reveal is therefore not decorative. It is the public shape of the draw algorithm: the same slow eliminations that create suspense are the eliminations that determine the winner.

Weight Invariant

Each ticket's weight is its fair value: the confirmed sats credited to that ticket minus the partner's posted fee, exactly as committed in the round CSV. On-time confirmations credit the selected ticket; late confirmations roll forward before the next unlocked round's CSV is committed. Ticket IDs, partner IDs, CSV order, and player metadata are public identity data; they do not add probability. At every step, the live candidate set only contains tickets that have not lost a tile, and every next-elimination branch is weighted by the draw weight behind the tickets that would remain if that tile were eliminated.

seed = hex(bytes(minor_seed) || bytes(draw_block_hash))
tickets = committed_csv_rows_with_positive_weight
eliminated = []
candidates = tickets

while count(unique_tile_sets(candidates)) > 1:
  choices = next_elimination_choices(candidates, eliminated)
  digest = sha256_chain(seed, position, counter, hashes_per_elimination)
  tile = rejection_sample(digest, choices)
  eliminated.append(tile)
  candidates = tickets in candidates not containing eliminated tiles

winner = weighted_pick_by_ticket_id(candidates) if count(candidates) > 1 else candidates[0]
draw_tiles = winner.tiles

Rejection sampling removes modulo bias: a 256-bit digest is accepted only if it falls inside the largest exact multiple of the current total choice weight. If it falls outside that range, the calculator increments the counter and runs another slow chain. If duplicate tickets share the final tile set, the next chain sample picks one ticket UUID by weight.

Reimplementation notes

The calculator is not a second protocol.

The draw calculator runs the same public replay path as the round auditor: final CSV bytes, minor seed, draw block hash, the fixed hash count, and the canonical draw core. It is useful for inspecting intermediate state, but the byte-level contract lives in the technical specification. A separate implementation should reproduce the spec vectors, then replay a real round using the same CSV and block hash shown here.

1. Parse committed CSV rows in file order.
2. Drop zero-weight tickets; reject duplicate ids.
3. Seed one SHA-256 chain with '<draw_seed_hex>:draw:chain'.
4. For each elimination, advance the same chain draw_hashes_per_elimination steps.
5. Rejection-sample the digest across weighted elimination choices.
6. Remove tickets containing the eliminated tile.
7. Stop when one tile set remains; if multiple tickets remain, weighted-sample by ticketId.