# How to Run a Random Draw That People Can Trust

> Why Math.random is not good enough for a raffle, what multiple entries do to the odds, and the commit-and-reveal method that makes a draw verifiable.

Source: https://toolpantry.app/blog/how-to-run-a-random-draw

A draw can be random and still be arguable. The generator is one part of it — the entry list is usually the bigger one, and the part nobody publishes. Here is what each of the three requirements actually means, and how to get a result anyone can check.

## Quick answer

**Short answer:** a trustworthy draw needs **no repeats, an unbiased source and a published record**. Use the browser’s cryptographic random source, not Math.random() — V8’s own documentation says the latter is not cryptographically secure, and it cannot be reproduced on another machine, so nobody can check it. Give every entrant one number instead of letting them choose it, publish the entry list before the draw, and keep the timestamp and the result together.

## What a fair draw actually needs

Three separate things have to hold, and people usually only think about the first one:

1. **No repeats.** A draw that hands out the same number twice is not a draw, it is a coincidence.
2. **An unbiased source.** The numbers have to come from a generator that does not favour some outcomes over others — and, if anyone will ever question the result, from one that *cannot* be shown to favour them.
3. **A record.** The list of entries, the moment of the draw and the result, kept so that the outcome can be checked afterwards rather than argued about.

Get the second one wrong and the first and third cannot save you. That is the part worth reading on.

## Why Math.random is not good enough

Math.random() is a pseudo-random number generator. V8 — the engine behind Chrome, Edge and Node — uses **xorshift128+**, a 128-bit state machine with a period of 2128−1, and it passes the standard TestU01 statistical suite. It is a good generator. V8’s own write-up of it says plainly that it is **still not cryptographically secure**, and points at crypto.getRandomValues() for anything where that matters.

Three practical consequences for a public draw:

- **It cannot be reproduced.** The seed is internal process state, so the same code on another machine gives a different sequence. Nobody can re-derive your result.
- **It cannot be verified.** A third party has nothing to check. There is no published input to recompute from.
- **The mapping to a range can tilt.** The usual shortcut — take a random number and multiply it by your range size — favours the lowest outcomes very slightly. Measured worst-case extra chance for the most favoured outcome: about **1.6×10−11%** for Math.random() * n and **0.000016%** for uint32 % n. Tiny, but a uniform way exists: reject the values that would tilt the odds and redraw, which is what a careful implementation does.

None of this makes Math.random() bad for animations, shuffling a UI, or picking a practice question. It makes it the wrong tool for a draw where money or a prize is involved.

## The entry problem: one person, ten tickets

The most common flaw in a small draw is not a rigged generator. It is the entry list. If there are 100 entries and one person submitted 10 of them, that person has a **10 % chance** of winning, while the person with a single entry has 1 %. Nothing about the random source is unfair; the odds were fixed before the draw began.

Two ways out. **One entry per person**, and give every entrant a number instead of letting them choose it — then the draw is over the list of people, not the list of tickets. Or **weighted entries**, where a legitimate reason to enter more (a larger order, a longer subscription) is recorded as a weight, and the number of tickets reflects it. What does not work is leaving it undocumented: if the entry list is not published before the draw, nobody can check.

## Drawing without repeats

When you draw several winners, the draws are not independent. After the first name comes out, that person is out of the pool, and every remaining person’s chance of the *next* draw goes up by exactly the same amount — they stay equal with each other. Over a full sweep where you draw everyone, every entrant wins exactly one prize: the chance of being picked at some point is 1, no matter how many prizes there are.

So a draw without replacement is fairer in one specific way: the person with ten tickets cannot be drawn ten times. The [random number generator](https://toolpantry.app/random-number-generator) has a **unique** mode for exactly this — it keeps drawing until it has the number of values you asked for, with no repeats, and it refuses to run if you ask for more values than the range can hold.

## The method that makes a draw checkable

Public giveaways have settled on a pattern called **provably fair**, and it is worth understanding because it is simple:

1. **Commit first.** The organiser generates a random secret called the **server seed** and publishes only its SHA-256 hash. That hash is a promise: whatever happens next, the secret behind it was fixed before anyone chose anything.
2. **The entrant contributes.** A **client seed** is chosen — often by the entrant, sometimes derived from something public like the order number. The draw combines both with a counter called the **nonce**: HMAC-SHA256(key = serverSeed, message = "clientSeed:nonce:round") produces a stream of bytes, which becomes the result.
3. **Reveal afterwards.** Once the draw is over, the organiser publishes the server seed. Anyone can check that its hash equals the committed hash, and recompute the whole result from the three inputs.

That last step is what makes it verifiable rather than merely random. The organiser cannot change the seed after seeing the entrant’s input, because the hash was published first; the entrant cannot grind client seeds to predict the result, because the key is secret until after. And the bytes are mapped to integers with rejection, so every outcome is exactly as likely as every other.

You do not need a service to do this for a small draw. You need the same three pieces and a willingness to publish the record.

## The record is half of it

Whatever the generator, keep four things together: the **list of entries** (one line per person, with the number or ticket assigned), the **timestamp**, the **settings** (the range, how many values, whether repeats were allowed), and the **result**. If the draw is done in the browser, the generator writes the sorted list into a text file you can save — that file plus the timestamp is your record, and it is what you show if anyone asks.

For a public giveaway with a real prize, use a service or a process with a published audit trail rather than a browser tab. For an office draw, a classroom pick, a secret assignment or a random order, the generator below is enough, as long as you keep the record and you say how the numbers were produced.

## Notes

Written by the Tool Pantry team on 22 October 2026. The details of Math.random() come from V8’s own write-up of its generator; the commit-and-reveal method is the one public giveaways use, and the uniform-mapping detail follows the published reference implementation. We are describing how a draw is made checkable, not giving legal advice — if you are running a public draw with a prize, look up the rules that apply to it.

## FAQ

**Is Math.random good enough for a raffle?**

For a draw where a prize is involved, no. It is a pseudo-random generator, and V8's own write-up of it says it is not cryptographically secure. More practically: it cannot be reproduced on another machine, so nobody can check your result, and the usual way of mapping it to a range favours the lowest outcomes very slightly. Use the browser's cryptographic random source instead, and keep a record of the draw.

**How do I stop one person entering many times?**

Give every entrant one number and draw over the list of people, so a repeat entry is impossible. If there is a legitimate reason to enter more, record it as a weight in advance and publish the entry list before the draw. The problem is rarely the generator — it is an entry list nobody has seen.

**What does drawing without replacement change?**

Every remaining entrant's chance of the next draw goes up by the same amount, so they stay equal with each other, and one person cannot win twice. Over a full sweep where everyone is drawn, every entrant wins exactly one prize.

**What is a provably fair draw?**

A method where the organiser publishes the hash of a secret seed before the draw, the entrant contributes a second seed, and the result is produced by HMAC-SHA256 over the two plus a counter. The organiser reveals the secret afterwards, so anyone can check the hash and recompute the result. The organiser cannot change the seed after seeing the entrant's input, and the entrant cannot predict the outcome.

**What should I keep after the draw?**

Four things: the list of entries, the timestamp, the settings you used, and the result. If the draw ran in the browser, the generated list saved as a text file is the result — keep it with the timestamp and the settings, and that is the record you show if anyone asks.

**Is this tool suitable for a public giveaway with money?**

No. It is for ordinary picks, classroom exercises, ticket assignments and practice draws. A public giveaway with a real prize needs a process with a published audit trail — and if you are running one, check your local rules about draws and contests as well.
