Provably Fair – Verification Guide

This page shows a complete example you can reproduce locally.

Example inputs:

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).

1. Server Seed (committed before drawing starts)

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"

2. Client Seed (Public Randomness)

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"

3. Entries Commit (Frozen Snapshot)

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"

4. Ranking (Deterministic Permutation)

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"

5. Winner at Nonce k

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"

6. Verification Checklist

  • - Server seed: compute sha256(hex2bin(server_seed_hex)) and match the published commitment.
  • - Client seed: fetch the published drand value (in this example, it’s the hex string above).
  • - Entries commit: take the ordered UUID list, compute sha256.
  • - Scores: for every UUID, compute HMAC_SHA256.
  • - Permutation: sort by (score, uuid). Winner for nonce k is permutation[k].
  • - (Optional): Compare your joined permutation against permutation_digest.

This way, anyone can independently confirm that each winner was chosen fairly. 🎲✨