Skip to main content
Local Draws

How we draw

Every Local Draws result can be reproduced by anyone, from information we publish on the result page. This is the method, in enough detail to do it. It is the same method described in section 10 of the Draw Rules.

The order things happen in

The sequence is the whole point, so it is worth stating plainly before the detail.

  1. When a draw opens, we generate a random seed and publish a fingerprint of it. At that moment there are no entries at all, so nobody — including us — can know what the result will be.
  2. When entries close, the list of eligible entries is frozen and fingerprinted.
  3. The winner is worked out from the seed and the frozen list. There is no randomness left at this step: the same seed and the same list always give the same winner.
  4. The seed is published on the result page, alongside the result. Anyone can then check it against the fingerprint we published in step 1.

A draw that has no fingerprint published in advance does not run. Our own system refuses it rather than drawing anyway.

The commitment

The seed is 32 random bytes, written as 64 hexadecimal characters. The fingerprint we publish before entries close is:

sha256("<drawId>:<sequence>:<seed>")

sequence is 1 for the original draw and increases by one for each redraw. The draw id and the sequence are inside the hash rather than merely stored next to it, so a commitment cannot be lifted from one draw and presented as another, and a redraw cannot reuse the original draw’s commitment.

How entries are ordered and numbered

Every entry has a range of entry numbers — one number for a single entry, several consecutive numbers if somebody bought more than one. Entries are ordered by their first entry number, ascending. A free postal entry is ordered and counted exactly like a paid one; there is no separate pool and no weighting.

The frozen list is then written as canonical JSON (format snapshot_json_v1), with fields in a fixed order so the same list always produces the same text. Its SHA-256 is the Verification Fingerprint shown on every result page.

From seed to winning number

Method seed_commit_hmac_v1. Let units be the total number of entry numbers in the frozen list, and message be the canonical JSON above. Then, starting at attempt = 0:

  1. If attempt is 0, use message unchanged. Otherwise append # and the attempt number to it.
  2. Compute digest = sha256(seed + " " + message), where the seed is its 64-character hex text and the separator is a single space.
  3. Read the first 6 bytes of the digest as a big-endian unsigned integer. Call it value.
  4. Let limit = floor(2^48 / units) * units. If value < limit, the winning number is (value mod units) + 1. Otherwise increase attempt by one and go back to step 1.

That last step is the only part that is not obvious. Simply taking value mod units would very slightly favour the lowest numbers, because 248 does not divide evenly by most entry counts. The bias would be far too small for anyone to notice — and it would still be real, and we promise every entry an equal chance, so we discard the small unusable tail instead. With realistic entry counts this happens roughly never.

The winning number is then matched to whichever entry’s range contains it. A draw with only one entry number skips all of the above and returns 1.

Redraws

When a prize goes unclaimed and we draw again, the new draw gets its own seed and its own commitment — never the original’s. Because a redraw happens after entries have closed, “before the close” would prove nothing, so a redraw’s commitment is published at least an hour before the redraw runs instead. The result page shows which redraw you are looking at.

If it does not match

Tell us. Email hello@localdraws.com with the result reference and what you found, and a person will look at it. Checking our draws is not interference — it is the opposite, and nothing in our terms treats it as misuse.