A from-first-principles, self-contained randomness extractor using CPU timing jitter, with a built-in statistical test harness.
This is a completed, feature-complete v0.1.0 release. From this point forward, any functional change to the entropy pipeline, the RNG core, or the statistical tooling bumps the version number — v0.1.0 is a fixed, known snapshot of this codebase.
- Warm-up period (
warmupBytes/warmupIterations) added ahead of production hashing, soglobalAvghas converged before any bit that ends up in an output digest is extracted EntropyAnalyzer— full byte-frequency distribution report (mean, std deviation, min/max frequency vs. ideal uniform)TestNIST— a from-scratch implementation of all 15 tests in the NIST SP 800-22 Rev. 1a Statistical Test Suite, with a live console dashboard- Locked, auto-refilling, securely-erased
BinaryEntropyPool - Standalone SHA-256 (NIST FIPS 180-4), no OpenSSL/libcrypto dependency
- Single-file implementation, C++23, no OS randomness APIs called anywhere in the pipeline
- Produces output that passes the full NIST STS battery across the tests you've run it against — no detected bias, periodicity, or short-range correlation in the sample sizes tested
- Produces a byte distribution statistically consistent with uniform, per
EntropyAnalyzer - Runs entirely self-contained: no
/dev/urandom, noBCryptGenRandom, no hardware RNG instruction — every bit traces back to measured CPU timing jitter, hashed through a hand-written SHA-256 - Is now stable enough as a pipeline (warm-up added, underflow bugs fixed, header/body encoding issues resolved) to treat as a real v0.1.0 baseline rather than a moving target
This is worth being precise about, because it's the crux of what this project is and isn't.
NIST STS checks whether a bit sequence is statistically distinguishable from random — that is, whether there's detectable bias, periodicity, or structure in the output. It says nothing about whether that output was unpredictable to an attacker before it was generated. Those are different properties, and only the second one is what cryptographic security depends on.
Concretely: SHA-256 whitening makes almost any input look statistically uniform in its output. A deterministic counter, hashed through SHA-256, would also pass every test in this suite. So passing STS confirms the whitening stage is working as designed — it is not evidence about how much real, attacker-unpredictable entropy went in on the other side.
This project has not been shown to have sufficient min-entropy or attacker-unpredictability to be relied on for real key material, wallets, transaction signing, or anything securing actual funds or secrets. Specifically:
- No min-entropy estimate. There's no measurement anywhere in the pipeline of how many bits of real unpredictability each timing sample actually contributes before it's folded into the ring buffer. "Passes NIST STS" and "has N bits of min-entropy per sample" are different claims — this project only supports the first one.
- CPU timing jitter is a well-studied weak entropy source. On modern hardware it's shaped by CPU frequency scaling, branch prediction warm-up, thermal throttling, and OS scheduler behavior — several of which are at least partially observable or influenceable by another process on the same machine.
- The SHA-256 implementation is hand-written from spec, unaudited, and not constant-time. Even if functionally correct, it has none of the side-channel hardening or track record of libsodium, OpenSSL, or BoringSSL's implementations — including against timing leaks in the hashing step itself.
- No independent third-party audit, no formal security proof, no head-to-head validation against a known-good CSPRNG.
This is standard, close-to-universal security guidance, not a hedge specific to this project: don't roll your own crypto for anything that needs to actually hold up against a real attacker. That guidance applies here regardless of how solid the rest of the engineering is.
The realistic next step isn't "prove the jitter source is good enough" — it's stop relying on it alone. Mix your jitter-derived bits in as a supplement to an OS-provided CSPRNG (/dev/urandom on Linux, BCryptGenRandom on Windows) rather than as the sole entropy source, e.g. XOR or HKDF-combine them before use. That way even if the jitter source turns out to contribute near-zero real entropy, security still rests on the OS CSPRNG underneath it — and you keep the self-contained jitter harvesting as a legitimate additional input rather than a single point of failure.
High-resolution timing via std::chrono::high_resolution_clock, exposed as raw nanoseconds since epoch.
Independent streaming implementation following NIST FIPS 180-4, used to whiten raw jitter bits. Exposes hashBytes, hashString, hashBinary, hashHex, plus incremental update()/digest() for streaming use.
Entropy source — nanosecond duration of a trivial busy-wait loop:
volatile int x = 10;
auto start = systemClock.getNanoseconds();
while (x > 0) {
int tmp = x;
x = tmp - 1;
}
long long duration = systemClock.getNanoseconds() - start;Pipeline, per run():
- Warm-up (
warmupIterations= 10,000): samples run through the same threshold/ring-buffer mechanics as production, lettingglobalAvgconverge. The 512-slot ring buffer (localBits) fills well before warm-up ends, but nothing is hashed yet. - Production (512 further iterations): each timing sample is thresholded against the now-converged
globalAvg(bit = duration < globalAvg ? 0 : 1), written into the ring buffer, and the full 512-bit window is packed into 64 bytes and hashed through SHA-256. Every production iteration emits one 256-bit digest. - One
run()call therefore emits exactly 512 × 256 = 131,072 bits from 10,512 total timing samples.
Wraps RandomNumberGenerator behind a locked, auto-refilling, securely-erased bit pool:
POOL_CAPACITY= 512 × 256 = 131,072 bits — onerun()worth of outputPOOL_RESERVED=POOL_CAPACITY× 2 — upfront reservation, memory-locked viaVirtualLock/mlockLOW_WATERMARK= 512 × 128 — refill trigger, halfway through capacity- Bits are zeroed and erased from the pool immediately after being handed out, so nothing is reused
- Thread-safe via
poolMutex
Feeds generated bits in as bytes and reports the full 256-value frequency distribution, plus mean, standard deviation, and min/max frequency against the ideal uniform expectation.
Full from-scratch implementation of the 15 tests in NIST SP 800-22 Rev. 1a: Frequency (Monobit), Block Frequency, Runs, Longest Run of Ones, Binary Matrix Rank, DFT (Spectral), Non-Overlapping Template Matching, Overlapping Template Matching, Maurer's Universal, Linear Complexity, Serial, Approximate Entropy, Cumulative Sums, Random Excursions, and Random Excursions Variant. Live console dashboard tracks progress test-by-test.
main() prompts for a number of bits, pulls that many from the pool via bep.request(amount), then:
- Writes
entropy.bin— the raw '0'/'1' bit string - Writes
entropy_info.txt— bits generated, elapsed time (ms/s), throughput (bits/sec, Mbps) - Runs
EntropyAnalyzerand prints the full byte distribution table - Runs the full NIST STS battery via
TestNISTand prints pass/fail, statistic, and p-value for all 15 tests
This README describes v0.1.0 exactly as it stands. Any change to the RNG core, the entropy pool, the analyzer, or the NIST test implementations from this point forward requires a version bump — v0.1.0 should be treated as a fixed, citable snapshot, not a rolling label.
👤 Author
oiko-nomikos
Built from first principles, for understanding — not shortcuts.