If you're building an onchain game that needs randomness, you have two real options. Commit reveal is the old school approach. Chainlink VRF is the oracle backed one. I've shipped both in production, and the right choice depends on things most comparison articles skip over entirely.
Let me walk through the actual tradeoffs.
How commit reveal works in practice
The idea is simple. A player commits a hash of their secret. After the commit window closes, they reveal the secret. The contract verifies the hash matches and uses the revealed value (usually XORed with a block hash or another player's reveal) as the random seed.
Here's a minimal version of the commit phase.
mapping(address => bytes32) public commits;
mapping(address => bool) public revealed;
function commit(bytes32 _hash) external {
require(commits[msg.sender] == bytes32(0), "already committed");
commits[msg.sender] = _hash;
}
function reveal(bytes32 _secret) external {
require(commits[msg.sender] != bytes32(0), "no commit");
require(keccak256(abi.encodePacked(_secret)) == commits[msg.sender], "bad reveal");
revealed[msg.sender] = true;
// use _secret as entropy source
}
Gas for the commit transaction runs about 45,000 to 55,000 gas. The reveal is similar, maybe 50,000 to 65,000 depending on what you do with the result. So the full round trip for one player is roughly 100,000 to 120,000 gas total.
That's cheap. On Ethereum L1 at 30 gwei, you're looking at about $0.10 to $0.15 per random number. On Arbitrum or Base, it's fractions of a cent.
How VRF works in practice
Chainlink VRF v2.5 uses a request/callback model. Your contract calls requestRandomWords on the VRF coordinator. Chainlink's oracle network generates a verifiable random number offchain, then calls back into your contract with the result. The proof is verified onchain.
function requestRandom() external returns (uint256 requestId) {
requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: 3,
callbackGasLimit: 100000,
numWords: 1,
extraArgs: VRFV2PlusClient.extraArgs(false)
})
);
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
// use randomWords[0]
}
The request transaction costs about 100,000 to 150,000 gas. But you also pay a LINK premium. On Ethereum mainnet, the premium is 0.25 LINK per request. At $15 per LINK, that's $3.75 just for the oracle fee, on top of the gas. On L2s the premium drops. Arbitrum charges 0.005 LINK per request, so about $0.075.
The latency problem nobody talks about
Commit reveal requires two transactions from the user. In a multiplayer game, you need all participants to commit before anyone can reveal. In practice this means you need timeout logic, and that timeout logic is where bugs live.
What happens when a player commits but never reveals? They've seen everyone else's commits. If the game lets them walk away without penalty, they can selectively abort when the outcome won't favor them. You need to slash their deposit or apply a penalty. That means your "simple" commit reveal scheme now needs a staking mechanism.
VRF has latency too, but it's predictable. After your request transaction confirms, you wait for the oracle callback. On Ethereum that's typically 2 to 4 blocks, roughly 30 to 50 seconds. On Arbitrum it's faster, usually under 10 seconds. The user only sends one transaction. The second one (the callback) is handled by the oracle and paid from your LINK subscription.
For single player games where the player is the only one generating entropy, VRF wins on UX by a wide margin.
Security model differences
Commit reveal is trust minimized but fragile. The security assumption is that no single party can control enough entropy to predict the outcome. If you XOR two players' secrets together, both would need to collude to rig it. That works for two player games. For single player games, commit reveal against the house means the house can predict outcomes unless you add a third party committer, which starts to look like reinventing an oracle.
VRF's security comes from the discrete log assumption and Chainlink's oracle network. You're trusting that Chainlink nodes won't collude to manipulate randomness. In practice, this trust assumption is reasonable for most gaming applications. The VRF proof is verified onchain, so the oracle can't just make up a number. They can only withhold a response, which you can detect and handle.
The real vulnerability with VRF is fulfillment manipulation. A sophisticated attacker who controls a validator could theoretically reorg to choose which VRF result gets included. For low stakes games this is not a concern. For games with pots over $100k, you should increase requestConfirmations to 20 or more.
Gas benchmarks comparison
| Operation | Commit Reveal (gas) | VRF v2.5 (gas) |
|---|---|---|
| User transaction 1 | ~50,000 (commit) | ~120,000 (request) |
| User transaction 2 | ~60,000 (reveal) | None |
| Oracle callback | N/A | ~200,000 (paid by sub) |
| Total user gas | ~110,000 | ~120,000 |
| Oracle fee | None | 0.25 LINK (L1) / 0.005 LINK (L2) |
| USD cost at 30 gwei (L1) | ~$0.12 | ~$0.15 gas + $3.75 LINK |
| USD cost on Arbitrum | ~$0.002 | ~$0.003 gas + $0.075 LINK |
Those LINK premiums are the real cost driver. On L1, VRF is roughly 30x more expensive per random number. On L2s, the gap shrinks but VRF still costs more.
Failure modes
Commit reveal failures
- Player commits but never reveals (griefing). Mitigation needs deposit slashing.
- Front running the reveal transaction. Miners/sequencers can see the revealed secret in the mempool. Use a private mempool or accept this risk for low stakes.
- All players collude. If every participant shares their secret, randomness is compromised. Only matters in competitive multiplayer.
- Block hash manipulation. If you mix in
block.prevrandao, validators have limited influence over it. Post merge, this is less exploitable but not zero.
VRF failures
- Oracle goes down. Your game halts until Chainlink fulfills the request. You need timeout logic to refund users if fulfillment takes too long.
- Subscription runs out of LINK. The callback silently fails. Monitor your subscription balance in production. This has caused real outages.
- Callback gas limit too low. If your
callbackGasLimitis set below whatfulfillRandomWordsactually needs, the callback reverts. The request is wasted and you've lost the LINK premium. Test this carefully. - Reorg attacks on low confirmation counts. Mitigated by increasing
requestConfirmations.
Decision matrix
Use commit reveal when
- You're on L2 and cost per random number matters (high frequency games, thousands of rolls per day)
- Your game is multiplayer and both sides contribute entropy naturally
- You want zero external dependencies
- The stakes per round are under $100
Use VRF when
- Single player vs house games where commit reveal creates a trust problem
- UX is a priority and you can't ask users for two transactions
- Stakes are high enough that the LINK fee is negligible relative to the pot
- You need randomness that's legally defensible (VRF proofs are auditable)
The hybrid approach
On one project we used commit reveal for the frequent low stakes actions (loot drops, crafting outcomes) and VRF for the rare high stakes events (tournament brackets, jackpot draws). The commit reveal path handled about 95% of randomness requests and kept oracle costs manageable. VRF kicked in only when the pot exceeded a threshold.
The contract looked roughly like this.
function resolveAction(uint256 potSize) external {
if (potSize > VRF_THRESHOLD) {
_requestVRF();
} else {
_initiateCommitReveal();
}
}
This saved about 85% on oracle fees compared to using VRF for everything, and the security properties matched the risk profile at each tier.
My recommendation
If you're building on an L2 and your game involves two players who both have skin in the game, start with commit reveal. It's cheaper, simpler to audit, and has no external dependencies. Add deposit slashing for the reveal timeout and you've covered the main failure mode.
If you're building a single player game, a lottery, or anything where the house is one side of the bet, use VRF. Commit reveal in that context requires trust in the operator, which defeats the purpose of putting it onchain. The LINK cost is a line item in your operating budget, not a reason to compromise on randomness quality.
Don't overthink this. Pick the one that fits your trust model, budget for the costs, and handle the failure modes. Both have been battle tested in production for years.