px-rng

Cryptographically-secure random number service for casino games — dice, slots, deck shuffles, provably-fair commit/reveal.

Rust 1.95ChaCha20axum tokio4 shards100k req/test passed

Description

What it is

Single-source RNG for our gaming platform. One Rust process with four independent ChaCha20Rng shards, each seeded from the OS entropy pool (getrandom, 32 bytes). Round-robin balancing distributes incoming requests across shards via an atomic counter, so contention scales with shard count.

How seeding works

Provably-fair (commit / reveal)

A separate 32-byte server_seed is generated up-front and never sent to clients. POST /api/commit returns sha256(server_seed) — clients store this hash before the round. POST /api/reveal returns the original seed and rotates to a fresh one; the result is sha256(server_seed | client_seed | nonce) — fully reproducible by the player.

Auth

Every endpoint except /api/health requires a bearer token: Authorization: Bearer <RNG_TOKEN>.

Tests

Real-world capacity
2.12 M random numbers / second
Achieved on this single-server deployment with the parameters below. The pattern matches how a real casino backend talks to RNG — bundle a round's randomness into one API call.
ParameterValue
Concurrent backend connections10
Requests per connection1,000
Random numbers per request (count)1,000
EndpointPOST /api/range
Total HTTP requests10,000
Total numbers delivered10,000,000
Wall time4.71 s
Errors0
Numbers / second2,125,035
p99 latency6.93 ms

What this means in game terms (at 2.12 M numbers/s): ~424,000 slot spins/s (5 reels) · ~2.12 M roulette rounds/s (1 number/round) · ~40,000 deck shuffles/s (52-card Fisher-Yates). More than two orders of magnitude above the load any operator below the global top-10 would generate.

Load test — realistic workload (10 conn × 1,000 req × 1,000 numbers)

10,000 HTTP requests against POST /api/range with 1,000 numbers per request — 10 million numbers total. Concurrency=10 reflects how a real backend client would batch RNG calls. Loopback, sharded service.

Throughput
2,125 rps
2.12 M random numbers/s
Errors
0 / 10,000
100% success
Latency p50
4.64 ms
Latency p95
6.10 ms
Latency p99
6.93 ms
Max latency
10.89 ms

Stress test — 1,000 concurrent workers × 100 requests

100,000 HTTP requests with 1,000 simultaneous open connections — saturation scenario. Latency here reflects HTTP queue depth (Little's Law: 1,000 inflight ÷ 2,600 rps ≈ 385 ms wait), not RNG cost.

Throughput
2,595 rps
25,953 random numbers/s
Errors
0 / 100,000
100% success
Latency p50
369 ms
Latency p95
479 ms
Latency p99
555 ms

Shard distribution after the stress run was perfectly even — 250,000 calls per shard (round-robin AtomicUsize, no skew). Raw RNG core throughput measured separately at ~4.1M numbers/s on count=4096 batches.

Randomness — ent (8 MB sample, 64 M bits)

Sample of 8,192,000 full-range i64 values collected via the API and analysed with the industry-standard ent tool. Expected values for a perfect CSPRNG in parentheses.

MetricValueIdealVerdict
Entropy (bits/byte)7.9999978.000000PASS
χ² distribution (bytes)268.11 (p=27%)~256 ± 22 (p≈50%)PASS
Arithmetic mean (bytes)127.4924127.5000PASS
Monte Carlo π estimate3.142322 (err 0.02%)3.141593PASS
Serial correlation0.0000220.000000PASS
Bit entropy (bits/bit)1.0000001.000000PASS

Randomness — NIST SP 800-22 subset (8 M bits)

Six core NIST tests applied to the same sample. P-values above 0.01 indicate the sequence is statistically indistinguishable from random.

TestP-valueVerdict
Frequency (Monobit)0.78381PASS
Block Frequency (M=128)0.53391PASS
Runs0.39613PASS
Longest Run of Ones (M=10000)0.45846PASS
Cumulative Sums (forward)0.96053PASS
Cumulative Sums (reverse)0.81903PASS

Distribution sanity — 50,000 dice rolls (1..6)

Quick chi-square goodness-of-fit on a small sample to verify range generation is unbiased.

FaceCountFrequency
18,14816.30 %
28,40016.80 %
38,26116.52 %
48,41216.82 %
58,33716.67 %
68,44216.88 %

χ² = 7.44 with 5 degrees of freedom — well below the 11.07 threshold (95% confidence). Uniform distribution confirmed.

API

GET /api/health
Service status, shard count, per-shard call counts and seed age. No auth.
POST /api/u64
Single random u64 from the next round-robin shard.
POST /api/range
Body {min,max,count} — returns count integers in [min,max] (max 4096 per request, unbiased).
POST /api/floats
Body {count} — array of f64 in [0,1).
POST /api/shuffle
Body {deck:[…]} — Fisher-Yates shuffle of the input array.
POST /api/commit
Returns sha256(server_seed) + server_seed_id. Use before each round.
POST /api/reveal
Body {client_seed,nonce} — reveals the seed, computes sha256(seed | client_seed | nonce), rotates to a fresh seed.

cURL example

curl -s -X POST https://rnd.alphapari.com/api/range \
     -H "Authorization: Bearer $RNG_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"min":1,"max":36,"count":5}'

Playground

// response will appear here