A simple algorithm and python script to conduct provably fair raffles. Tickets are generated in a dependent chain, and winner(s) are selected using output from a public source of entropy at a pre-determined time in the future.
Consider a list of N entrants (i.e. potential raffle winners) and define +
as the string concatenation operator.
For each entrant Ei, a hash Hi is first computed:
Next, each raffle ticket Ti in the chain is computed letting the raffle name name serve as a seed:
Note that each ticket depends on the previous tickets in the chain making it impossible to cheat by retroactively injecting tickets.
Finally, the raffle drawing is conducted by calculating a result Ri for each ticket:
where P the NIST Randomness Beacon's output (512 bits of entropy) at the pre-determined moment of the drawing.
The winning ticket is the one with the lowest result. If multiple winners are desired, the nth winner holds the ticket with the nth lowest result.
Works with python 2.7.15
usage: fair-raffle.py [-h] [--unique] [-i INDEX | -u UNIX_TS | -t TS | -l]
ENTRANTS
Provably Fair Raffle Generator
positional arguments:
ENTRANTS path to text file containing raffle entrants (one name per line,
lines beginning with "#" are ignored)
optional arguments:
-h, --help show this help message and exit
--unique ignore duplicate entrants
-i INDEX index of NIST Randomness Beacon pulse used to select winners
-u UNIX_TS Unix timestamp (in ms) of NIST Randomness Beacon pulse used to
select winners
-t TS timestamp of NIST Randomness Beacon pulse used to select
winners. format: "year-month-day hour-minute timezone", e.g.
"2018-11-28 16:03 -0600" or "2018-11-28 22:03 +0000"
-l use latest available NIST Randomness Beacon pulse to select
winners
Generates a raffle ticket chain for the entrants provided. If an NIST
Randomness Beacon (https://beacon.nist.gov/home) pulse is specified, the
raffle drawing is conducted. Raffle tickets and (optionally) sorted results
are written to CSV files.
For a raffle named my_raffle
:
First, publicly announce the date and time (down to the minute) on which the raffle drawing will occur. Let the cutoff in this example be 2018-11-28 22:03 -0600
(i.e. 10:03 PM CST on 2018-11-28).
Collect the names of the raffle entrants and put them in a text file my_raffle.txt
(one entrant per line). Entering someone multiple times proportionally increases their chances of winning.
Generate tickets like this:
python fair-raffle.py /path/to/my_raffle.txt
The ticket chain will be saved to my_raffle-ticketchain.csv
. I suggest making an up-to-date version of this file available to the entrants so that they have their tickets and can inspect the chain. Crucially, entrants must have access to their tickets before the time of the drawing.
As you collect more entrants, update my_raffle.txt
and re-generate the tickets. Note that you should only append to my_raffle.txt
. Any rearrangement or injection corrupts the ticket chain by design.
At any time after the announced drawing time, find winner(s) like this:
$ python fair-raffle.py /path/to/my_raffle.txt -t '2018-11-28 22:03 -0600'
Raffle name: my_raffle
Parsed 10 raffle entrants.
Fetching https://beacon.nist.gov/beacon/2.0/pulse/time/1543464180000 ...
NIST Randomness Beacon output for pulse index 180921 (2018-11-29T04:03:00.000Z): https://beacon.nist.gov/beacon/2.0/chain/1/pulse/180921
00C1D306216BD4B4DC183292E92DEC7842B0C0AF0FB2EF8EE7A057E2240A620EC4971D579A69DABCB134850C1C62B4D0C25EEEED68E83B2BC4FB091BEBE7D176
Winner: joe (for details, see /path/to/my_raffle-results-180921.csv)
The results of the drawing will be saved to my_raffle-results-<pulseIndex>.csv
. They're sorted so that winners come first. If you want only one winner, just pick the first in the list.
See also provided sample entrants, tickets, and sorted results.