← All writing

2026-07-04

The maths of a provably fair draw

How commit-reveal fairness actually works: a public salt, a seed nobody can predict, a deterministic shuffle anyone can rerun.

Every drop needs a way to pick winners, and every way of picking winners eventually gets asked the same question: how do we know you didn’t just pick your friends? Most operators answer with a promise. We’d rather answer with arithmetic anyone can run themselves.

The problem with “trust us”

Say a hundred people enter a raffle for one watch. However the operator picks the winner, screenshotting a random-number app, drawing a name from a hat off-camera, scrolling through a spreadsheet, the entrants are being asked to believe two things: that the full entrant list was actually complete and unedited, and that whatever selection process happened, happened the way it was described, with no thumb on the scale. Neither of those is checkable after the fact. The raffle is over, the winner is announced, and the only evidence anyone has is the operator’s word.

This isn’t necessarily dishonesty. It’s structural: a selection process that lives inside one person’s head, or one company’s private tooling, cannot be re-run by anyone else, so there is nothing independent to check it against. The fix isn’t a more trustworthy operator. It’s a selection process built so that checking it doesn’t require trusting anyone.

Commit, then reveal

The standard tool for this is called commit-reveal, and the idea is simple once you see it: you want a random-feeling outcome that depends on two pieces of information, one of which is announced in advance (the commit), and one of which only comes into existence later, outside anyone’s control (the reveal). Neither half alone determines anything, and by the time both halves exist, it’s too late for either side to have gamed the outcome.

Concretely, ours works like this. Before an entry window closes, we publish a salt, just a random string, out in the open, attached to the drop. That’s the commit. Then the window closes, at whatever exact moment it actually closes, and that timestamp becomes the second half. Neither of us controls when a clock reaches its scheduled time. We take those two values, the salt and the close time, join them with a separator, and run them through SHA-256 to get a seed. That seed is the entire source of “randomness” in the draw, and it did not exist, could not have existed, until both halves were fixed.

From seed to order

A hash is just a fixed string of bits, though, not a shuffle. To turn it into an actual draw order, we feed it into a small deterministic pseudo-random generator (a seeded xorshift routine, if you want the specific one), and use that generator to drive a Fisher-Yates shuffle across the entrant list. Fisher-Yates is the standard algorithm for producing a uniformly random permutation: walk the list from the end backward, and at each step swap the current entry with a randomly chosen entry from everything not yet placed. “Deterministic” is the operative word here. This isn’t drawing on real entropy, a dice roll, atmospheric noise, anything unrepeatable. It’s a function: same seed in, same shuffle out, every single time, on any machine, in any programming language that implements the same steps.

That last part is what makes the whole thing checkable. The salt is public. The close time is public (it’s just when the window closed). The entrant list is whatever was actually submitted. Anyone, us, an entrant, a journalist, a competitor, can take those three things, run the same published algorithm, and get the identical order we got, winners included, byte for byte. We don’t get to run it differently behind the scenes, because there’s no “behind the scenes” step left once the seed is fixed. The public commitment and the deterministic function are the entire mechanism.

Why Instagram-DM drops don’t hold up

Compare that to the common alternative: “comment below, we’ll DM winners.” There’s no published algorithm, so there’s nothing to rerun. There’s usually no fixed, published entrant list either, comments can be deleted, DMs can be missed, screenshots can be cropped, and the actual pool that was “eligible” at the moment of selection is whatever the operator says it was, which is unverifiable by definition. Even an entirely honest operator running that process has no way to prove, after the fact, that they didn’t unconsciously favor a friend’s entry, because the process never produced any evidence beyond its own outcome. It isn’t that these operators are lying. It’s that they built a process where lying and honesty look identical from the outside, and asked people to trust them anyway.

Ours is built so that distinction doesn’t need to exist. The proof isn’t our reputation. It’s a hash function and a shuffle algorithm that anyone can read, and a seed that came from two numbers neither of us could see coming in advance.

If you want to see this rather than take our word for the mechanics, we built a version you can run yourself with your own entrant list and your own salt, at /fair-draw.