Don't Trust. Verify.
We believe in total transparency. Here is a step-by-step guide on how to take the raw data from any draw and verify the result yourself, independent of our servers.
Step 1: Get the Data
Go to the Past Draws page and click "View & Verify" on any completed draw. Scroll down to the Verifiably Fair Data section to find these values:
- Server Seed (The randomness from Drand)
- Client Seed (The timestamp)
- Round ID (The unique UUID for the draw)
- Static Salt (A constant value used for security)
- Tickets Sold
- Max Tickets
Step 2: Verify Drand Source
First, verify that the Server Seed actually came from Drand and wasn't invented by us.
Copy the Round Number (External Round ID) and check it against the Drand Quicknet public beacon using the Drand API (Raw Data). "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/{ROUND_NUMBER}"
Step 3: Run the Calculation
We use HMAC-SHA256 to combine the seeds and Rejection Sampling to determine the winner. This ensures a uniform distribution with no modulo bias.
You can run this PHP script in any online sandbox (like OnlinePHPFunctions) to reproduce the result:
<?php
// 1. INPUT YOUR DATA HERE
$serverSeed = 'REPLACE_WITH_SERVER_SEED'; // From "Server Seed (Revealed)"
$clientSeed = 'REPLACE_WITH_CLIENT_SEED'; // From "Client Seed" (Draw Timestamp)
$roundId = 'REPLACE_WITH_ROUND_ID'; // From "Round ID"
$staticSalt = 'REPLACE_WITH_STATIC_SALT'; // From "Static Salt"
$ticketsSold = 100; // Replace with actual "Tickets Sold"
$maxTickets = 1000; // Replace with actual "Max Tickets"
// 2. COMBINE SEEDS (HMAC-SHA256)
$data = "{$clientSeed}:{$roundId}:{$staticSalt}:{$ticketsSold}:{$maxTickets}";
$combinedHash = hash_hmac('sha256', $data, $serverSeed);
echo "Combined Hash: " . $combinedHash . "\n";
// 3. GENERATE RESULT (Rejection Sampling)
$hashBin = hex2bin($combinedHash);
$hashLength = strlen($hashBin);
$result = null;
for ($i = 0; $i < $hashLength; $i += 4) {
if ($i + 4 > $hashLength) break;
// Unpack 4 bytes into an unsigned integer
$value = unpack('L', substr($hashBin, $i, 4))[1];
// Calculate rejection limit to avoid modulo bias
$limit = 0xFFFFFFFF - (0xFFFFFFFF % $maxTickets);
if ($value < $limit) {
$result = ($value % $maxTickets) + 1;
break;
}
}
// Fallback (extremely rare)
if ($result === null) {
$result = "Requires re-hashing (rare)";
}
echo "Winning Ticket: " . $result . "\n";
?>
Alternatively, you can run this JavaScript directly in your browser's console (Right Click -> Inspect -> Console):
// 1. INPUT YOUR DATA HERE
const serverSeed = 'REPLACE_WITH_SERVER_SEED'; // From "Server Seed (Revealed)"
const clientSeed = 'REPLACE_WITH_CLIENT_SEED'; // From "Client Seed" (Draw Timestamp)
const roundId = 'REPLACE_WITH_ROUND_ID'; // From "Round ID"
const staticSalt = 'REPLACE_WITH_STATIC_SALT'; // From "Static Salt"
const ticketsSold = 100; // Replace with actual "Tickets Sold"
const maxTickets = 1000; // Replace with actual "Max Tickets"
// 2. COMBINE SEEDS (HMAC-SHA256)
async function verify() {
const data = `${clientSeed}:${roundId}:${staticSalt}:${ticketsSold}:${maxTickets}`;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(serverSeed),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
const combinedHash = bytesToHex(new Uint8Array(signature));
console.log("Combined Hash:", combinedHash);
// 3. GENERATE RESULT (Rejection Sampling)
const hashBytes = new Uint8Array(signature);
let result = null;
for (let i = 0; i < hashBytes.length; i += 4) {
if (i + 4 > hashBytes.length) break;
// Read 4 bytes as unsigned 32-bit integer (Little Endian)
const view = new DataView(hashBytes.buffer, i, 4);
const value = view.getUint32(0, true); // true for Little Endian to match PHP unpack('L')
// Calculate rejection limit
// 0xFFFFFFFF is 4294967295
const limit = 4294967295 - (4294967295 % maxTickets);
if (value < limit) {
result = (value % maxTickets) + 1;
break;
}
}
console.log("Winning Ticket:", result);
}
// Helper functions
function hexToBytes(hex) {
return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
}
function bytesToHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
verify();
Why can't I use a standard converter?
You might be tempted to take the hash and put it into a generic "Hex to Decimal" converter online. This will give you the wrong result.
Standard converters simply turn the entire massive hash into one giant number and use a basic
modulo
(%) operation. This introduces Modulo Bias, meaning some tickets
would have a slightly higher chance of winning than others.
To guarantee that every single ticket has the exact same mathematical probability of winning, we must use Rejection Sampling. This algorithm is slightly more complex, which is why you need to use the specific scripts above to verify the result correctly.