Skip to content

PROVABLY FAIR

1 · The seed is locked in before anyone enters

When a giveaway is created, the system generates a random 256-bit seed and publishes only its SHA-256 fingerprint — the commitment — on the giveaway page. The seed itself stays sealed, so nobody (including us) can steer the outcome, and nobody can predict it.

2 · Every entry is checked at close

When entries close we verify each task directly with X and Discord. Entries that pass every check are eligible. If a check can't be completed on our side, the entry goes to manual review — it is never dropped silently.

3 · The draw is a reproducible shuffle

Eligible entry ids are sorted and hashed. A stream of random numbers is derived from HMAC-SHA256(seed, list hash, counter), and a Fisher–Yates shuffle with unbiased sampling picks the winners, then the reserves in order. Because the list hash feeds the stream, changing a single eligible entry would change the whole result.

4 · Check it yourself

Once winners are drawn, the seed is revealed. Download the draw inputs from the results section of the giveaway page and run:

// Node 20+: node verify.mjs draw.json
import { createHash, createHmac } from "node:crypto";
import { readFileSync } from "node:fs";

const d = JSON.parse(readFileSync(process.argv[2], "utf8"));
const sha = (s) => createHash("sha256").update(s).digest("hex");
if (sha(d.seed) !== d.seedCommitment) throw new Error("seed does not match commitment");

const ids = [...d.eligibleEntryIds].sort();
const listHash = sha(ids.join("\n"));
if (listHash !== d.eligibleListHash) throw new Error("eligible list differs");

let counter = 0, buf = [];
const next = () => {
  if (!buf.length) {
    const b = createHmac("sha256", Buffer.from(d.seed, "hex")).update(`${listHash}:${counter++}`).digest();
    for (let o = 0; o < 32; o += 8) buf.push(b.readBigUInt64BE(o));
  }
  return buf.shift();
};
const uniform = (n) => {
  const N = BigInt(n), limit = (2n ** 64n / N) * N;
  for (;;) { const v = next(); if (v < limit) return Number(v % N); }
};
const k = Math.min(ids.length, d.giveaway.winners + d.giveaway.reserves);
for (let i = 0; i < k; i++) {
  const j = i + uniform(ids.length - i);
  [ids[i], ids[j]] = [ids[j], ids[i]];
}
console.log("winners ", ids.slice(0, d.giveaway.winners));
console.log("reserves", ids.slice(d.giveaway.winners, k));

Your own entry id is shown on the giveaway page after you enter, so you can find yourself in the eligible list.