This page shows a complete example you can reproduce locally.
server_seed_hex = "3f1e2d4c5b6a79880796a5b4c3d2e1f0ffeeddccbbaa99887766554433221100"
client_seed = "be8a2f9d5c0fb0e3a1c4d7e9f2a5b8c1d3e5f7094a6b8c0d1e2f3a4b5c6d7e8f"
uuids (ordered) = [
"00112233-4455-6677-8899-aabbccddeeff",
"1456abcd-7890-4def-8123-456789abcdef",
"9f82aa12-34cd-4a56-9b78-0a1b2c3d4e5f",
]
In real giveaways, you’ll use the actual published values (commitment, client seed, and the committed UUID list).
We publish the SHA-256 hash of the raw bytes of the server seed at commit time.
$serverSeedBin = hex2bin("3f1e2d4c5b6a79880796a5b4c3d2e1f0ffeeddccbbaa99887766554433221100");
$commitHash = hash('sha256', $serverSeedBin);
// commitHash = "eb3e4eebc26e2e834008a30579f11657902b680a86c9ac006a3e2a062a28e79d"
We use the Cloudflare drand beacon as a public, unpredictable client seed. This will always be published on the giveaway page. In this example:
round = xxxxxx
curl https://drand.cloudflare.com/public/{round} | jq -r .randomness
// client_seed used for this example:
client_seed = "be8a2f9d5c0fb0e3a1c4d7e9f2a5b8c1d3e5f7094a6b8c0d1e2f3a4b5c6d7e8f"
At commit time, we freeze the ordered list of all the entry UUIDs and publish a hash over that list + count. The UUIDs list is published after the drawing has ended so people can verify that against the hash.
$uuids = [
"00112233-4455-6677-8899-aabbccddeeff",
"1456abcd-7890-4def-8123-456789abcdef",
"9f82aa12-34cd-4a56-9b78-0a1b2c3d4e5f",
];
$count = count($uuids); // 3
$entriesHash = hash('sha256', implode("\n", $uuids) . "|" . $count);
// entriesHash = "616fbb37239adab95d1e0e145988e212b6c01d179fea7cdbea08d98f3dd784b7"
For each UUID, compute a score with HMAC-SHA256 using the server seed (as key) and client_seed:giveaway_id:entry_uuid as the message. Then sort by score (asc), breaking ties with UUID.
// Scores (HMAC-SHA256) for each UUID in this example:
"00112233-4455-6677-8899-aabbccddeeff"
=> "1297fb856086dae32a0ba93ab3af083d6f49e255dcb60fe7c3a486256a280ab3"
"1456abcd-7890-4def-8123-456789abcdef"
=> "52c40c70b8e333973296dec7f13970211371c9546581855aa4c83dd3ba882c88"
"9f82aa12-34cd-4a56-9b78-0a1b2c3d4e5f"
=> "a15f448f552e745592f16c900692846e0abe0b5063194d607102b5cda8f1099c"
sorted permutation (UUIDs) = [
"00112233-4455-6677-8899-aabbccddeeff",
"1456abcd-7890-4def-8123-456789abcdef",
"9f82aa12-34cd-4a56-9b78-0a1b2c3d4e5f",
];
permutation_digest = "ad08fc58fbbbe6ab570978aa860853e63460e20cd2aabf227a7c46e50c2acf48"
The winner at nonce k is simply permutation[k]. The nonce always starts at 0 and auto-increments. The nonce will be published for every winner.
nonce 0 → "00112233-4455-6677-8899-aabbccddeeff"
nonce 1 → "1456abcd-7890-4def-8123-456789abcdef"
nonce 2 → "9f82aa12-34cd-4a56-9b78-0a1b2c3d4e5f"
This way, anyone can independently confirm that each winner was chosen fairly. 🎲✨