The 30-second summary
Every modern runtime ships a CSPRNG: crypto.randomUUID() and crypto.getRandomValues() in browsers and Node.js, secrets in Python, SecureRandom in the JVM. They're fast, free, and cryptographically strong. For session IDs, CSRF tokens, password salts, request IDs — anything where the only consumer of the randomness is your own server — they're the correct answer.
They become the wrong answer the moment a second party has to verify that the result you reported is the result your code actually produced. An in-process CSPRNG is unobservable from the outside; there is no artifact a third party can check.
Feature matrix
| Capability | Provable.io | crypto.randomUUID() / getRandomValues() |
|---|---|---|
| Cryptographic strength | HMAC-SHA256 keyed stream | OS CSPRNG (e.g. /dev/urandom, BCryptGenRandom) |
| Reproducible from seed | Yes — that's the whole point | No — fresh entropy on every call |
| Third-party verifiability | Yes — publish seed + hash, anyone re-derives | No — the call is invisible from outside the process |
| Pre-commitment | Yes — serverHash published before the result | No |
| Latency | ~tens of ms (network) | Sub-microsecond (in-process) |
| Auditable history | Yes — every outcome persisted and addressable by short ID | No — gone the moment the process exits |
| External dependency | Yes — an HTTP call | No — language-level |
| Best for | Anything a user, auditor, or regulator might dispute | Internal tokens, IDs, salts, padding, jitter |
When crypto.randomUUID() is the right answer
Don't reach for an HTTP API when a local function call does the job. Stick with the in-process CSPRNG for:
- Session tokens, CSRF tokens, password reset tokens. Nobody outside your server should ever see these, much less verify them.
- Database primary keys / request IDs. Latency matters, verification doesn't.
- Cryptographic key material. Generate your TLS private keys, your AES keys, your HMAC keys with the OS CSPRNG — never with a seeded RNG.
- Internal jitter and backoff. Same reasoning: no audience.
When you actually need a verifiable RNG
The "who needs to check this?" question is the whole tell. If the answer is anyone outside your own process, the local CSPRNG can't help — there's nothing to publish. Reach for Provable.io when:
- A user might dispute the outcome. "Why did my entry not win?" "Why was I in the control group?" "Why did the deck deal that hand?" See raffle picker, A/B bucketing, card shuffler.
- An auditor needs to re-derive a past decision. A regulator asking "show me that this user really was bucketed into the treatment arm on this date" is a non-starter with
crypto.randomUUID(). - You're publishing the result to a public audience. Giveaways, NFT trait reveals, jackpots, dice rolls in a public game lobby.
What the API call looks like
Same shape as a Crypto API call, just over HTTP — and the response carries the proof in addition to the value.
curl "https://api.provable.io/api/bytes?clientSeed=compare-crypto-demo&count=16&encoding=hex"
The response includes a serverHash. Anyone with the seed and that hash can confirm — via the open-source provable-core library — that the bytes you reported are the bytes the algorithm produced.
A common mistake: using both for the same job
We see two anti-patterns regularly:
- Calling Provable.io for session tokens. You don't need verification, and you're adding a network round trip to every login. Use the language CSPRNG.
- Calling
crypto.randomUUID()for a public raffle winner. The draw happens, the winner is announced, the runners-up ask "how do we know?" — and there's no answer. Use Provable.io and publish theserverHash.
The line is about audience, not about strength. Both primitives are cryptographically sound; they answer different questions.
FAQ
Is Provable.io a CSPRNG?
Its output is computationally indistinguishable from a CSPRNG, but the trust model is different: it's verifiable rather than unobservable. That's a feature for public-audience use cases and a footgun for secret material. Never use the same Provable.io seed for both visible draws and secret keys.
What about Math.random()?
Math.random() is not cryptographically strong and shouldn't be used for either job. If a value matters, use crypto.getRandomValues() for in-process needs and Provable.io for verifiable ones.
Can I cache outcomes?
Yes. Because the call is deterministic in (serverSeed, clientSeed, cursor, nonce), you can cache the result keyed by the inputs. The seed-based design is what makes both verification and caching cheap.