Slot Reward Algorithm
This explains how the reward outcome for a randomly picked slot request is generated in a provably fair way. Anyone can reproduce the random value from the disclosed seeds and verify whether the reward was legitimately won.
Algorithm Overview
- A server_seed is generated using a cryptographically secure RNG (hex 64 chars).
- A client_seed is constructed:
kick_username:id:slot_call.
- A nonce is set to the numeric slot request
id.
- The three values are concatenated with colons:
server_seed:client_seed:nonce.
- SHA-256 hash of that string is computed.
- The first 8 hex characters of the hash are converted to an integer (0–4294967295).
- The integer is reduced:
(value % 10000) / 100 producing a number in 0.00–99.99.
- If this value is strictly less than the configured win chance percent, the reward is awarded.
Verifying A Result
After a pick, the API returns a JSON structure containing server_seed, client_seed, nonce, proof_hash, the numeric chance, and the derived random_value. You can recompute the hash and random value as follows:
import hashlib
server_seed = "<server_seed_from_api>"
client_seed = "<client_seed_from_api>" # format: username:id:slot_call
nonce = "<nonce_from_api>"
combined = f"{server_seed}:{client_seed}:{nonce}"
hash_result = hashlib.sha256(combined.encode()).hexdigest()
assert hash_result == "<proof_hash_from_api>" # must match
random_int = int(hash_result[:8], 16) # first 8 hex chars
random_value = (random_int % 10000) / 100 # 0.00 - 99.99
chance_percent = <chance_from_api>
won = random_value < chance_percent
print(random_value, chance_percent, won)
Security Notes
- The server_seed is freshly generated each pick to prevent prediction.
- The client_seed binds the randomness to the specific request context.
- Using only the first 8 hex characters balances transparency with simplicity; full hash remains available via
proof_hash.
- All comparisons use floating point values derived deterministically from the hash; no additional randomness is introduced.
Example JSON
{
"reward": {
"won": true,
"type": "tip",
"amount": "5.00",
"chance": 10.0,
"random_value": 3.27,
"proof_hash": "2b7f8c...",
"server_seed": "f9d1...",
"client_seed": "exampleUser:123:DogHouse",
"nonce": "123"
}
}
If you discover any discrepancy between a provided random_value and a recomputed value from the seeds, please report it immediately.
Raffle Draw Algorithm
The monthly raffle system uses cryptographically secure randomness to select winners from all eligible participants. The draw is fair, transparent, and verifiable.
How It Works
- Ticket Assignment: Each participant earns tickets throughout the month via:
- Watchtime: 10 tickets per hour
- Gifted Subs: 15 tickets per sub
- Shuffle Wagers: 20 tickets per $1000 wagered
- Bonus: Admin-awarded tickets
- Sequential Entry Pool: All tickets are arranged sequentially:
- User A (10 tickets) = entries #1-10
- User B (25 tickets) = entries #11-35
- User C (5 tickets) = entries #36-40
- ...and so on
- Provably Fair Random Selection:
- Generate server_seed: 64-character cryptographically secure random hex string
- Create client_seed:
period_id:total_tickets:total_participants
- Set nonce:
period_id
- Concatenate:
server_seed:client_seed:nonce
- Compute SHA-256 hash of the combined string
- Convert first 16 hex characters to integer
- Map to winning ticket:
(integer % total_tickets) + 1
- Winner Determination: The user whose ticket range contains the winning number is selected as the winner.
- Draw Recording: All draw details including provably fair data are permanently stored in the database.
Verifying A Draw
After a draw, you can verify the result was fair by recomputing the hash and winning ticket:
import hashlib
server_seed = "<server_seed_from_draw>"
client_seed = "<client_seed_from_draw>" # format: period_id:total_tickets:total_participants
nonce = "<nonce_from_draw>"
# Recompute hash
combined = f"{server_seed}:{client_seed}:{nonce}"
hash_result = hashlib.sha256(combined.encode()).hexdigest()
assert hash_result == "<proof_hash_from_draw>" # must match
# Derive winning ticket
random_int = int(hash_result[:16], 16) # first 16 hex chars
total_tickets = <total_tickets_from_draw>
winning_ticket = (random_int % total_tickets) + 1
print(f"Winning ticket: {winning_ticket}")
print(f"Hash verified: {hash_result == '<proof_hash_from_draw>'}")
Cryptographic Security
The system uses SHA-256 hashing combined with cryptographically secure random seed generation:
- server_seed: Generated using Python's
secrets.token_hex(32) - cryptographically secure
- client_seed: Derived from public raffle data (period ID, ticket count, participant count)
- Deterministic: Given the same seeds, the hash and winning ticket are always identical
- Unpredictable: Cannot predict the winning ticket without knowing the server_seed beforehand
- Verifiable: Anyone can verify the draw was fair using the disclosed seeds
Exclusions System
Admins can exclude specific users from winning (e.g., staff members, previous winners) via the raffle_exclusions table. Excluded users:
- Can still earn and accumulate tickets
- Appear on the leaderboard
- Are excluded from the participant pool during drawing
- Exclusions are matched by Discord ID or Kick username
Multiple Winners
The system supports drawing multiple winners from the same period (e.g., 1st, 2nd, 3rd place). Each subsequent draw automatically excludes previous winners to ensure unique recipients.
Draw History Example
{
"period_id": 3,
"winner_discord_id": "123456789012345678",
"winner_kick_name": "exampleUser",
"winner_shuffle_name": "shuffleUser123",
"winner_tickets": 1250,
"winning_ticket": 8734,
"total_tickets": 15420,
"total_participants": 87,
"win_probability": 8.11,
"drawn_at": "2025-11-30T23:58:42",
"drawn_by_discord_id": "987654321098765432"
}
Verification
While the raffle doesn't use seeds like the slot reward system (due to the nature of OS-level cryptographic randomness), transparency is maintained through:
- Public Leaderboard: All participants and their ticket counts are visible before the draw
- Recorded Results: Every draw is permanently logged with complete details
- Deterministic Pool: Participants are ordered by database ID, ensuring consistent ticket ranges
- Open Source: The drawing algorithm is available for review
Why secrets.randbelow()?
From Python documentation:
"The secrets module is used for generating cryptographically strong random numbers suitable for managing data such as passwords, account authentication, security tokens, and related secrets. In particular, secrets should be used in preference to the default pseudo-random number generator in the random module, which is designed for modeling and simulation, not security or cryptography."
This ensures that the winning ticket cannot be predicted or manipulated, even with knowledge of system internals.
If you discover any discrepancy between a provided random_value and a recomputed value from the seeds, please report it immediately.