Random Number Generation in JavaScript Apps
/ 3 min read
Table of Contents
Random Number Generation in JavaScript Apps
Random Number Generation (RNG) is critical in many applications, from gaming to cryptography. In this post, we’ll examine how provably fair RNG systems work and how to implement them in JavaScript.
The Challenge of True Randomness
Generating truly random numbers in software is fundamentally difficult. Computers execute deterministic operations, making true randomness hard to achieve through algorithms alone.
Standard JavaScript RNG
The built-in Math.random()
function provides pseudorandom numbers between 0 and 1:
const randomValue = Math.random();console.log(randomValue); // 0.3942399501622835 (example output)
However, it has limitations:
- It’s deterministic (based on a seed)
- It’s not cryptographically secure
- It’s not provably fair for applications requiring verification
Cryptographically Secure RNG
For applications requiring higher security, use the Web Crypto API instead:
const generateSecureRandomNumber = () => { const array = new Uint32Array(1); window.crypto.getRandomValues(array); return array[0] / (0xffffffff + 1); // Normalize to [0, 1)};
const secureRandom = generateSecureRandomNumber();console.log(secureRandom);
This approach pulls entropy from the operating system, making it more suitable for security-critical applications.
Provably Fair Systems
Provably fair systems allow users to verify that the outcomes weren’t manipulated. Common in gambling platforms, they rely on three key components:
- Server Seed: Secret random value generated by the server
- Client Seed: Public value provided by or visible to the client
- Nonce: Counter that changes with each generation
Implementation Example
const crypto = require('crypto');
class ProvablyFairSystem { constructor(serverSeed, clientSeed) { this.serverSeed = serverSeed; this.clientSeed = clientSeed; this.nonce = 0; }
generateHash() { const message = `${this.clientSeed}:${this.nonce}`; const hash = crypto.createHmac('sha256', this.serverSeed) .update(message) .digest('hex');
this.nonce++; return hash; }
generateRandomNumber(min = 0, max = 1) { const hash = this.generateHash(); const decimal = parseInt(hash.substr(0, 8), 16) / 0xffffffff; return min + decimal * (max - min); }
// For verification after revealing server seed verifyResult(serverSeed, clientSeed, nonce, expectedResult) { const message = `${clientSeed}:${nonce}`; const hash = crypto.createHmac('sha256', serverSeed) .update(message) .digest('hex');
const decimal = parseInt(hash.substr(0, 8), 16) / 0xffffffff; const result = Math.floor(decimal * 100) / 100;
return result === expectedResult; }}
The Verification Process
Provably fair systems work by the following process:
- The server generates a server seed and provides a hash of it to the client
- The client provides or sees their client seed
- The system generates results using both seeds
- After the process completes, the server reveals the original server seed
- The client can verify all results using the disclosed server seed and original client seed
Client-Side Implementation
Here’s how to implement a verification system in the browser:
const verifyFairness = (serverSeed, clientSeed, nonce, result) => { const message = `${clientSeed}:${nonce}`; const hmac = CryptoJS.HmacSHA256(message, serverSeed); const hash = hmac.toString(CryptoJS.enc.Hex);
// Use first 8 characters of hash as the random value source const decimal = parseInt(hash.slice(0, 8), 16) / 0xffffffff; const calculatedResult = Math.floor(decimal * 100) / 100;
return { isValid: calculatedResult === result, expected: calculatedResult, received: result };};
Enhancing Entropy Sources
For increased randomness, combine multiple entropy sources:
const generateEnhancedSeed = () => { const sources = [ Date.now().toString(), performance.now().toString(), navigator.userAgent, screen.width + 'x' + screen.height, new Uint32Array(1).map(() => window.crypto.getRandomValues(new Uint32Array(1))[0]) ];
const combinedSource = sources.join('|'); return crypto.createHash('sha256').update(combinedSource).digest('hex');};
Best Practices for RNG Implementation
- Never rely solely on client-side RNG for security-critical applications
- Use cryptographically secure RNG like Web Crypto API instead of Math.random()
- Implement server-side verification to prevent manipulation
- Provide transparency by clearly documenting your RNG methodology
- Allow result verification through provably fair mechanisms when appropriate
Conclusion
Implementing provably fair random number generation in JavaScript requires careful consideration of security, transparency, and verification processes. While standard RNG methods like Math.random()
are convenient, applications requiring fairness verification demand more sophisticated approaches involving cryptographic techniques and proper seed management.
By implementing the patterns described in this article, you can create RNG systems that not only function properly but also build trust with users through verifiable fairness mechanisms.