How to Build a Provably Fair Gaming System on Blockchain
Contents
In the early days of online gaming, I often found myself questioning the fairness of game outcomes. Traditional platforms operated like black boxes — opaque algorithms determined wins and losses, and players had no way to verify anything. You had to trust the operator's word, and trust without verification is fragile.
This uncertainty sparked my interest in blockchain for fair play in iGaming — a technology that promises verifiable fairness through its transparent and immutable nature. My journey began with a simple question: How can we guarantee that every game outcome is fair and independently verifiable by any player?
That question led me into the intersection of cryptography, randomness theory, and smart contract engineering. In this guide, I'll share everything I've learned — from the mathematical foundations to production-ready code — so you can build a provably fair gaming system that earns player trust through mathematical proof, not promises.
What Is a Provably Fair System?
Provably fair is a cryptographic method that allows players to independently verify that a game's outcome was determined fairly and was not manipulated after a bet was placed. Instead of relying on blind trust or third-party audits, the system provides a cryptographic "receipt" for every round — containing the inputs, the function, and the mapping — so anyone can reproduce the exact result.
The concept was popularized by early Bitcoin gambling platforms like SatoshiDice (launched in 2012) and has since become the gold standard for transparency in crypto casinos and blockchain gaming.
How It Works at a High Level
- Before the game: The server commits to a secret value (server seed) by publishing its cryptographic hash. This is the commitment phase.
- During the game: The player provides their own seed. The outcome is determined by combining the server seed, client seed, and a nonce through a keyed cryptographic function.
- After the game: The server reveals the original seed. The player recomputes the hash, verifies it matches the pre-game commitment, and independently reproduces the game outcome.
This three-phase process is formally known as a commit-reveal scheme — one of the oldest and most fundamental patterns in cryptography. It provides two guarantees: the binding property (the server can't change the seed after committing) and the hiding property (the player can't predict the outcome before betting).
Provably Fair vs. Traditional RNG: Why It Matters
Understanding the difference between provably fair systems and traditional Random Number Generators (RNGs) is critical — both for developers building these systems and for the players who use them. This distinction also matters for ensuring fairness in algorithmic decision-making more broadly.
| Feature | Traditional RNG | Provably Fair |
|---|---|---|
| Transparency | Closed-source; players can't see the algorithm | Open; inputs, function, and outputs are verifiable |
| Verification | Requires third-party audits (eCOGRA, iTech Labs, GLI) | Any player can verify any round independently |
| Trust Model | Trust the operator + auditor | Trust the math (trustless verification) |
| Manipulation Risk | Operator could theoretically alter outcomes between audits | Cryptographically impossible to alter committed outcomes |
| Player Involvement | None — player is passive | Player contributes a seed that influences the outcome |
| Audit Trail | Internal logs (may not be public) | Every round is a public, verifiable record |
Traditional online casinos rely on proprietary RNGs validated by third-party testing labs. You never see the actual draw — you trust that the certification is valid. Provably fair adds a verifiable receipt for each round, shifting the paradigm from trust-based to verification-based gaming.
Key insight: Provably fair doesn't replace RNG — it wraps RNG in a cryptographic commitment scheme that makes the randomness auditable.
Core Cryptographic Components
The Three Key Variables
At the heart of every provably fair system are three inputs:
-
Server Seed: A high-entropy random value generated by the server. It's kept secret during gameplay and only revealed after the round (or seed rotation). Before the game, the server publishes a SHA-256 hash of this seed as a commitment.
-
Client Seed: A random value generated by (or chosen by) the player. This ensures the player has direct influence over the outcome — the server alone cannot determine results.
-
Nonce: A monotonically increasing counter that increments with each bet. It guarantees that even with the same server seed and client seed, every round produces a unique outcome.
The Commit-Reveal Scheme Explained
The commit-reveal pattern is the cryptographic backbone that makes provably fair gaming possible:
Phase 1 — Commit:
commitment = SHA-256(server_seed) → Published to the player BEFORE the game starts
Phase 2 — Play:
outcome = HMAC-SHA256(server_seed, client_seed + ":" + nonce) → Game result is determined
Phase 3 — Reveal & Verify:
Player receives: server_seed (plaintext) Player computes: SHA-256(server_seed) Player checks: computed_hash === original_commitment Player recomputes: HMAC-SHA256(server_seed, client_seed + ":" + nonce) Player confirms: recomputed_outcome === displayed_result
Why HMAC-SHA256, Not Plain SHA-256
This is a critical distinction that many tutorials (including my original version of this article) get wrong. The industry standard for combining seeds is HMAC-SHA256 (or HMAC-SHA512), not a plain SHA-256(serverSeed + clientSeed + nonce) concatenation.
Why does this matter?
-
Plain SHA-256 is vulnerable to length-extension attacks. Given
SHA-256(secret || message), an attacker can computeSHA-256(secret || message || padding || attacker_data)without knowing the secret. This is a well-documented vulnerability of all Merkle–Damgård hash constructions. -
HMAC (Hash-based Message Authentication Code, defined in NIST FIPS 198-1 and RFC 2104) uses a double-hashing construction with inner and outer padding that is immune to length-extension attacks:
HMAC-SHA256(key, message) = SHA-256((key ⊕ opad) || SHA-256((key ⊕ ipad) || message))
In provably fair systems, the standard pattern is:
outcome_bytes = HMAC-SHA256(key = server_seed, message = client_seed + ":" + nonce)
This produces 32 bytes of cryptographically secure, unpredictable-during-play, but fully-recomputable-after-reveal randomness. Platforms like Stake, Primedice, and Bustabit all use HMAC-based constructions.
The Math: From Hash to Game Outcome
1. Generating Verifiable Randomness
The HMAC output gives us 32 bytes (256 bits) of pseudorandom data. But how do we turn raw bytes into a dice roll, a crash multiplier, or a roulette number?
2. Avoiding Modulo Bias with Rejection Sampling
A common mistake — and one I made in my original code — is using naive modulo arithmetic:
// BIASED: introduces modulo bias
const result = parseInt(hash.slice(0, 8), 16) % 100;
The problem: If 2^32 (4,294,967,296) isn't evenly divisible by your target range N, some outcomes are slightly more likely than others. For a dice roll (1–6), 2^32 mod 6 = 4, meaning four outcomes have a probability of 715,827,883 / 4,294,967,296 while two have 715,827,882 / 4,294,967,296. This is modulo bias.
The solution is rejection sampling — discard values that fall outside the largest evenly-divisible range:
function getUnbiasedResult(hashHex, maxValue) {
const MAX_INT = 2 ** 32;
const threshold = MAX_INT - (MAX_INT % maxValue);
for (let i = 0; i <= hashHex.length - 8; i += 8) {
const value = parseInt(hashHex.slice(i, i + 8), 16);
if (value < threshold) {
return (value % maxValue) + 1;
}
}
// Fallback (astronomically unlikely with 256-bit hash)
return (parseInt(hashHex.slice(0, 8), 16) % maxValue) + 1;
}
3. How House Edge Is Embedded in the Algorithm
In real-world implementations like Stake's Crash game, the house edge is a transparent mathematical constant embedded in the outcome mapping:
// Stake's publicly documented Crash formula
const crashPoint = Math.max(1, (2 ** 32 / (decimalValue + 1)) * (1 - 0.01));
Where 0.01 is the 1% house edge. Because the formula is public, players can verify that the edge is exactly as advertised — no hidden manipulation. The 2 ** 32 (4,294,967,296) represents the maximum value for a 32-bit unsigned integer.
Building the System in JavaScript (Off-Chain)
The following code examples demonstrate the correct cryptographic patterns (HMAC, commitment scheme, rejection sampling) used by real platforms. They're written in JavaScript and intentionally simplified for learning — not production-ready.
1. Seed Generation and Commitment
const crypto = require('crypto');
// === SERVER SIDE ===
function generateServerSeed() {
return crypto.randomBytes(64).toString('hex');
}
function createCommitment(serverSeed) {
return crypto.createHash('sha256').update(serverSeed).digest('hex');
}
const serverSeed = generateServerSeed();
const commitment = createCommitment(serverSeed);
console.log(`Server Seed: ${serverSeed}`); // Keep SECRET during gameplay
console.log(`Commitment Hash: ${commitment}`); // Share with player BEFORE game
// === CLIENT SIDE ===
function generateClientSeed() {
return crypto.randomBytes(32).toString('hex');
}
const clientSeed = generateClientSeed();
console.log(`Client Seed: ${clientSeed}`);
2. HMAC-Based Outcome Calculation
// Generate outcome using HMAC-SHA256 (NOT plain SHA-256)
function generateOutcome(serverSeed, clientSeed, nonce) {
const hmac = crypto.createHmac('sha256', serverSeed);
hmac.update(`${clientSeed}:${nonce}`);
return hmac.digest('hex');
}
// Convert hash to game result using rejection sampling (no modulo bias)
function hashToResult(outcomeHash, maxValue) {
const MAX_INT = 2 ** 32;
const threshold = MAX_INT - (MAX_INT % maxValue);
for (let i = 0; i <= outcomeHash.length - 8; i += 8) {
const value = parseInt(outcomeHash.slice(i, i + 8), 16);
if (value < threshold) {
return (value % maxValue) + 1;
}
}
return (parseInt(outcomeHash.slice(0, 8), 16) % maxValue) + 1;
}
// Example: Dice roll (1-100)
const nonce = 1;
const outcomeHash = generateOutcome(serverSeed, clientSeed, nonce);
const diceRoll = hashToResult(outcomeHash, 100);
console.log(`Outcome Hash: ${outcomeHash}`);
console.log(`Dice Roll (1-100): ${diceRoll}`);
3. Player Verification Flow
function verifyGame(revealedServerSeed, originalCommitment, clientSeed, nonce, claimedResult, maxValue) {
// Step 1: Verify commitment (server didn't change the seed)
const recomputedHash = crypto.createHash('sha256').update(revealedServerSeed).digest('hex');
const commitmentValid = recomputedHash === originalCommitment;
// Step 2: Recompute the game outcome
const outcomeHash = generateOutcome(revealedServerSeed, clientSeed, nonce);
const recomputedResult = hashToResult(outcomeHash, maxValue);
const resultValid = recomputedResult === claimedResult;
return {
commitmentValid,
resultValid,
isFair: commitmentValid && resultValid,
recomputedResult,
outcomeHash
};
}
const verification = verifyGame(serverSeed, commitment, clientSeed, nonce, diceRoll, 100);
console.log(`Commitment valid: ${verification.commitmentValid}`);
console.log(`Result valid: ${verification.resultValid}`);
console.log(`Is the game fair? ${verification.isFair ? "✅ Yes" : "❌ No"}`);
4. Seed Rotation and Hash Chains
In production, server seeds should be rotated regularly. Advanced implementations use hash chains — a technique popularized by Bustabit — where millions of outcomes are pre-committed using a chain of hashes. This is conceptually similar to how automated crypto trading systems use deterministic, auditable algorithms.
function generateHashChain(terminatingHash, length) {
const chain = [terminatingHash];
for (let i = 1; i < length; i++) {
const previousHash = chain[chain.length - 1];
const nextHash = crypto.createHash('sha256').update(previousHash).digest('hex');
chain.push(nextHash);
}
return chain.reverse(); // First game uses chain[0]
}
const CHAIN_LENGTH = 10_000_000;
const terminatingHash = crypto.randomBytes(32).toString('hex');
console.log(`Terminating Hash (public): ${crypto.createHash('sha256').update(terminatingHash).digest('hex')}`);
console.log(`Commits to ${CHAIN_LENGTH.toLocaleString()} future game outcomes`);
Why hash chains matter: They prove that ALL future outcomes were determined before the first game was played. The operator cannot selectively choose favorable outcomes because the entire chain is cryptographically locked.
On-Chain Implementation: Solidity Smart Contracts
A truly blockchain-based provably fair system puts game logic on-chain in an immutable smart contract. The rules can't be changed, payouts are automatic, and the audit trail lives on the blockchain. This is where the concept intersects with DeFi vs traditional finance — trustless execution replaces intermediary-dependent processes.
The Blockchain Randomness Problem
Blockchain networks are deterministic by design — every node must reach identical results. You cannot generate truly unpredictable random numbers on-chain using block variables:
// VULNERABLE: Never do this!
function unsafeRandom() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % 100;
// Validators can manipulate block.timestamp and block.prevrandao
}
Basic Commit-Reveal Dice Contract (Educational)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ProvablyFairDice {
struct Game {
address player;
bytes32 serverSeedHash; // Commitment
bytes32 clientSeed;
uint256 nonce;
uint256 betAmount;
uint256 result;
bool revealed;
}
mapping(uint256 => Game) public games;
uint256 public gameCount;
event GameCreated(uint256 indexed gameId, address indexed player, bytes32 serverSeedHash);
event GameRevealed(uint256 indexed gameId, uint256 result, bool won);
function createGame(bytes32 _serverSeedHash, bytes32 _clientSeed) external payable {
require(msg.value > 0, "Bet required");
gameCount++;
games[gameCount] = Game({
player: msg.sender,
serverSeedHash: _serverSeedHash,
clientSeed: _clientSeed,
nonce: gameCount,
betAmount: msg.value,
result: 0,
revealed: false
});
emit GameCreated(gameCount, msg.sender, _serverSeedHash);
}
function revealAndSettle(uint256 _gameId, bytes32 _serverSeed) external {
Game storage game = games[_gameId];
require(!game.revealed, "Already revealed");
require(keccak256(abi.encodePacked(_serverSeed)) == game.serverSeedHash, "Invalid seed");
uint256 result = uint256(keccak256(abi.encodePacked(
_serverSeed, game.clientSeed, game.nonce
))) % 6 + 1;
game.result = result;
game.revealed = true;
bool won = result > 3;
if (won) {
// Checks-Effects-Interactions: state updated BEFORE transfer
payable(game.player).transfer(game.betAmount * 2);
}
emit GameRevealed(_gameId, result, won);
}
}
Important: This basic contract is for educational purposes. For production, you need oracle-based randomness (see below) and security hardening.
Integrating Chainlink VRF for Verifiable Randomness
Chainlink VRF (Verifiable Random Function) is the industry standard for generating cryptographically secure, provably fair randomness on-chain. It solves the blockchain randomness problem by combining off-chain randomness generation with on-chain cryptographic proof verification through a decentralized oracle network.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2Plus.sol";
import "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract ProvablyFairDiceVRF is VRFConsumerBaseV2Plus {
IVRFCoordinatorV2Plus private immutable i_vrfCoordinator;
uint256 private immutable i_subscriptionId;
bytes32 private immutable i_keyHash;
uint32 private constant CALLBACK_GAS_LIMIT = 500000;
uint16 private constant REQUEST_CONFIRMATIONS = 3;
uint32 private constant NUM_WORDS = 1;
struct DiceGame {
address player;
uint256 betAmount;
uint256 prediction;
uint256 result;
bool fulfilled;
}
mapping(uint256 => DiceGame) public games;
event DiceRollRequested(uint256 indexed requestId, address indexed player);
event DiceRollResult(uint256 indexed requestId, uint256 result, bool won, uint256 payout);
constructor(
address vrfCoordinator,
uint256 subscriptionId,
bytes32 keyHash
) VRFConsumerBaseV2Plus(vrfCoordinator) {
i_vrfCoordinator = IVRFCoordinatorV2Plus(vrfCoordinator);
i_subscriptionId = subscriptionId;
i_keyHash = keyHash;
}
function rollDice(uint256 prediction) external payable returns (uint256 requestId) {
require(msg.value > 0, "Bet required");
require(prediction >= 1 && prediction <= 6, "Pick 1-6");
requestId = i_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: i_keyHash,
subId: i_subscriptionId,
requestConfirmations: REQUEST_CONFIRMATIONS,
callbackGasLimit: CALLBACK_GAS_LIMIT,
numWords: NUM_WORDS,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
games[requestId] = DiceGame(msg.sender, msg.value, prediction, 0, false);
emit DiceRollRequested(requestId, msg.sender);
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
DiceGame storage game = games[requestId];
require(!game.fulfilled, "Already fulfilled");
uint256 result = (randomWords[0] % 6) + 1;
game.result = result;
game.fulfilled = true;
bool won = (result == game.prediction);
uint256 payout = 0;
if (won) {
payout = game.betAmount * 5;
(bool success, ) = payable(game.player).call{value: payout}("");
require(success, "Transfer failed");
}
emit DiceRollResult(requestId, result, won, payout);
}
receive() external payable {}
}
Security Patterns
Protecting smart contracts from common attack vectors is non-negotiable. This is closely related to the principles I cover in server hardening and security — defense in depth applies to on-chain code just as much as infrastructure. For cryptocurrency privacy and security, the same cryptographic rigor is essential.
- Reentrancy: Always update state BEFORE external calls (checks-effects-interactions pattern).
- Access Control: Use OpenZeppelin's
AccessControlorOwnablefor admin functions. - Emergency Pause: Implement circuit breakers with OpenZeppelin's
Pausable. - Front-Running Prevention: Chainlink VRF inherently protects against this since the random value is generated off-chain after the request transaction is confirmed.
Real-World Implementations {#real-world-examples}
Understanding how production platforms implement provably fair systems provides invaluable context. These platforms are frequently showcased at iGaming conferences and technology events.
Stake Originals (Crash Game)
Stake uses HMAC-SHA256 with a game hash and a Bitcoin block hash as entropy source. Their publicly documented formula: crashPoint = max(1, (2^32 / (int + 1)) * (1 - 0.01)). The 0.01 is their transparent 1% house edge. They conduct public seeding events disclosed on Bitcointalk forums.
Bustabit (Hash Chain Pioneer)
Bustabit pioneered the hash chain approach: pre-generating 10 million game hashes using SHA-256 chains, publishing the terminating hash before the first game. After the chain completes, the entire sequence is verifiable. This is the gold standard for pre-commitment.
Primedice (Dice Game)
Primedice uses the classic three-variable model: server seed + client seed + nonce → HMAC-SHA512, mapped to a 0–99.99 range. Players can rotate seeds at any time and verify all previous bets.
What Is a Public Seeding Event?
A public seeding event is when a game provider publicly discloses the specific Bitcoin block hash they use as a randomness source. This disclosure includes the timestamp, the formula, and the hash of the chosen block — allowing independent verification of all future game outcomes.
Scaling for Production
Layer 2 Solutions
On-chain gaming on Ethereum mainnet is expensive. Layer 2 networks dramatically reduce costs while maintaining security:
| Network | Avg. Transaction Cost | Block Time | Best For |
|---|---|---|---|
| Ethereum Mainnet | $1–$10+ | ~12 seconds | High-value, low-frequency games |
| Polygon | $0.001–$0.01 | ~2 seconds | High-frequency casual games |
| Arbitrum | $0.01–$0.10 | ~1 second | DeFi-integrated gaming |
| Base | $0.001–$0.01 | ~2 seconds | Consumer-facing gaming apps |
Infrastructure Stack
For a production system, consider the same Infrastructure as Code security principles I apply to enterprise platforms:
- Key Management: AWS KMS, Google Cloud KMS, or Azure Key Vault for server seed encryption
- Blockchain Interaction: Infura or Alchemy for reliable RPC endpoints
- Backend Scalability: Google Kubernetes Engine (GKE) or containerized microservices
- Off-Chain Logging: MongoDB Atlas or DynamoDB for game logs, with on-chain anchoring via Merkle Trees or IPFS decentralized storage
- Stress Testing: Apache JMeter or k6 to identify bottlenecks
Regulatory and Licensing Context
Provably fair technology doesn't replace regulatory compliance — it complements it. This is relevant for anyone involved in iGaming affiliate program optimization or operator-side technology.
| Jurisdiction | Stance on Provably Fair | Crypto-Friendly? | Key Requirement |
|---|---|---|---|
| Malta Gaming Authority (MGA) | Published DLT guidance; recognizes provably fair | Yes | Technical audits + player protection |
| Curaçao Gaming Authority (CGA) | Crypto-friendly under new LOK law (2023) | Yes | AML/KYC compliance |
| UK Gambling Commission (UKGC) | Must meet same standards as traditional platforms | Cautious | Full regulatory equivalence |
| Isle of Man / Gibraltar | Varying levels of blockchain acceptance | Moderate | Case-by-case evaluation |
For player identity verification on blockchain platforms, decentralized identity management solutions offer privacy-preserving KYC alternatives.
Important: "Provably fair" guarantees outcome integrity, not business integrity. Players should still look for valid licensing, responsible gambling tools, and transparent terms. Similarly, operators building on crypto earning and staking models need the same compliance rigor.
Common Pitfalls
Not all "provably fair" implementations are created equal. Watch out for:
- No pre-commit hash — If the server doesn't publish a hash before the game, they could change the seed after seeing the bet.
- Using plain SHA-256 instead of HMAC — Vulnerable to length-extension attacks. Always use HMAC-SHA256 or HMAC-SHA512.
- Modulo bias in outcome mapping — Naive
hash % Ncreates non-uniform distributions. Use rejection sampling. - No seed rotation or reveal log — If the server never reveals seeds, players can't verify anything.
- Vague documentation — Saying "we use SHA-256" without specifying the exact function, inputs, and byte order is a red flag.
- On-chain randomness from block variables — Using
block.timestamporblockhashalone is insecure. Use Chainlink VRF or equivalent oracle. - No reentrancy protection — Smart contracts that send ETH before updating state are vulnerable to reentrancy attacks.
Personal Insights
When I first started exploring blockchain-based provably fair gaming, I was struck by how much trust we place in systems we can't see. It got me thinking: how do you create a system where trust isn't just a promise but something you can verify yourself?
One of the biggest eye-openers was the challenge of true randomness. It's easy to take randomness for granted, but in gaming, even the smallest predictability undermines the entire system. Understanding the difference between HMAC-SHA256 and plain SHA-256, or why modulo bias matters, transformed how I think about fairness at a mathematical level.
Then there's the human side: helping players actually use the verification tools. Building a provably fair system is one thing; making it intuitive enough for anyone to understand is another. Clear verification interfaces, step-by-step guides, and accessible documentation aren't just features — they're necessities. Transparency locked behind complexity isn't really transparency at all.
As a technology leader who has spent 16+ years building enterprise systems, I can tell you that the principles behind provably fair gaming — commitment schemes, verifiable computation, trustless execution — extend far beyond gambling. They're the foundation of a more transparent digital economy.
For more deep dives into blockchain, AI, and software architecture, explore my technical articles. And if you want to discuss provably fair systems, smart contract security, or blockchain architecture for your project, contact me for a free consultation.
FAQ
What is a provably fair gaming system?
A provably fair gaming system is a cryptographic method that allows players to independently verify that every game outcome was determined fairly and not manipulated. It uses a commit-reveal scheme where the server commits to a secret seed before the game, the outcome is generated using HMAC-SHA256 combining server seed, client seed, and nonce, and after the game the server reveals the seed so players can recompute and verify the result.
How does provably fair differ from traditional RNG?
Traditional RNG systems are closed-source and require trust in third-party auditors (like eCOGRA or GLI). Provably fair systems are open and verifiable — any player can independently reproduce and verify any game outcome using the published seeds and algorithm. The key difference is shifting from trust-based to verification-based fairness.
Can provably fair games be hacked?
The cryptographic foundation (HMAC-SHA256, SHA-256 commitments) is extremely robust. Cracking SHA-256 would require computational power that doesn't exist — even quantum computers are estimated to be decades away. However, implementation flaws (poor seed generation, missing commitments, insecure smart contracts) can create vulnerabilities. The math is sound; the risk is in the engineering.
Why use HMAC-SHA256 instead of plain SHA-256?
Plain SHA-256 is vulnerable to length-extension attacks — a well-documented weakness of Merkle–Damgård hash constructions. HMAC-SHA256 uses a double-hashing construction with inner and outer padding (defined in NIST FIPS 198-1) that is immune to this attack. Every major provably fair platform (Stake, Bustabit, Primedice) uses HMAC, not plain SHA-256.
What is modulo bias and why does it matter?
Modulo bias occurs when 2^32 isn't evenly divisible by the target range N, making some outcomes slightly more probable than others. For example, mapping a 32-bit integer to a 1–6 dice roll via % 6 gives four outcomes a probability of 715,827,883/4,294,967,296 and two outcomes 715,827,882/4,294,967,296. The fix is rejection sampling: discard values outside the largest evenly-divisible range.
What blockchains are best for provably fair gaming?
Ethereum offers the strongest security and ecosystem (Chainlink VRF, OpenZeppelin). Polygon and Arbitrum provide lower costs for high-frequency games. Base (Coinbase's L2) is gaining traction for consumer-facing apps. The choice depends on your transaction frequency, gas budget, and target audience.
Do I need a gambling license for a provably fair platform?
Yes. Provably fair technology guarantees outcome integrity, not regulatory compliance. You still need appropriate licensing (MGA, Curaçao CGA, UKGC, etc.), responsible gambling tools, AML/KYC procedures, and transparent terms of service. Provably fair complements licensing — it doesn't replace it.