Brandon T. Elliott
Vibe Building a Random Number Generator Inspired by Cloudflare's Lava Lamp Wall

Does Randomness Exist?
Not the “rawr XD so random” kind of random from the Myspace era. Actual randomness.
Or are we living in a simulation, and what we call randomness is just the simulation’s PRNG (Pseudo-Random Number Generator)? Quantum mechanics is said to be random, but what if it’s just computationally expensive noise from whatever engine is running underneath? What if the universe is deterministic and we just haven’t found the seed?
Simulation theory is a fun thought experiment, but one thing is provably certain: insecurely seeded pseudo-randomness has very real security consequences.
For example, Joe Grand helped someone recover a Bitcoin wallet holding 43.6 BTC (~$3 million at the time) after the owner lost the password. It was possible because the password generator used the system clock as its seed. Joe reverse-engineered the binary, narrowed down the creation date, brute-forced the time window, and recovered the exact password, which he found was generated on May 15, 2013 at 4:10:40 PM GMT.
The Inspiration
Cloudflare quite famously has a wall of about 100 lava lamps in their San Francisco lobby that they use as an entropy source. The concept is simple: a camera records the chaotic motion of the lava lamps, the video gets hashed and mixed with system entropy from two separate machines, and that feeds into their cryptographic infrastructure.
I first learned about it from this Tom Scott video several years ago and it left a lasting impression in my mind. Recently, I wanted an escape from my other side projects and decided to experiment and vibe build my own version in my homelab.
Although lava lamps are certainly one of the best choices for a physical entropy source, I thought it would be boring to do what has already been done and decided to look for alternatives which may be feasible enough for my homelab that would both give a source of raw entropy and also be visually appealing at the same time. Ultimately, I decided to go with a bubble wall.
While planning out the project, I had an interesting thought: what if I created a built in feedback loop by displaying data derived from the system’s randomly generated data on LED panels above the bubble wall? This effectively would allow us to have a visual display that:
- is (partially) derived from the randomly generated data itself
- is then captured in the webcam view used to generate the random data
- but is not the actual random data
- and can’t be used to predict the actual random data
With the project planned out, I bought the bubble wall, a Raspberry Pi, two 64x32 LED panels, and a couple of other required accessories. I already had a webcam lying around that turned out to have a good enough resolution (Higher resolutions and FPS allow us to capture more raw entropy from the moving bubbles, lighting, etc.).
The Architecture
Bubba continuously captures video of the bubble wall, extracts entropy from the video using six different methods in parallel, and mixes the result with /dev/urandom and a rotating cryptographic key through SHA3-512 to produce the final output.
The Pi pulls random data from Bubba over mTLS (mutual TLS), runs it through destructive mixing with its own /dev/urandom and a rotating key, and displays the result as glitchy scrolling hex on the LED panels. The webcam captures the LED display as part of the next frame, creating a feedback loop where the randomness feeds itself.
Here is a full look at the overall architecture:

Entropy Sources
After benchmarking ten different extraction methods against the webcam feed, I settled on six sources that run in parallel per frame: XOR frame diff, LSB sensor noise, per-channel optical flow, spatial block hashing (32x32 grid), raw frame hash, and CPU timing jitter. Optical flow was the standout at 6.09 bits/byte, nearly double the raw frame diff at 3.09.
Each source produces a SHA3-512 digest that gets XORed into a 2,048-byte entropy pool. The key performance trick was XOR-folding: compressing ~6MB frame buffers to 4KB via XOR-reduce before hashing. Every byte still influences the output, but hash time dropped from 35ms to 0.05ms, allowing us to generate random data at a much higher rate.
Mixing
The raw webcam entropy is biased and noisy on its own. But it doesn’t need to be perfect, because it’s never used alone. Every output combines three independent entropy sources:
- Visual entropy from the webcam (bubble motion, LED display, sensor noise)
/dev/urandomfrom Bubba (the main box’s kernel CSPRNG, or Cryptographically Secure Pseudo-Random Number Generator)- A rotating cryptographic key that changes with every single output
extract = hashlib.sha3_512(
len_prefix(urandom_bytes) + len_prefix(entropy_pool) +
len_prefix(self._key) + counter + timestamp
).digest()
self._key = hashlib.sha3_512(extract + urandom_bytes).digest() # rotate
output = hashlib.shake_256(extract).digest(num_bytes)
The output is never worse than /dev/urandom alone. The visual entropy and the rotating key only add to it. And even if the webcam was pointed at a blank wall, the output would still be cryptographically strong.
The Pi pulls random data from Bubba via mutual TLS, mixes it with its own /dev/urandom and a rotating key via SHA3-512, then deliberately truncates half of the hash output, and finally expands the remaining half through SHAKE-256 to generate the display data. This destructive mixing means the displayed output is cryptographically disconnected from the internal state of either machine.
Normally truncating a hash is a bad idea since it weakens collision resistance, but here we’re not using the hash for integrity or authentication. We’re deliberately destroying information within the hash to strengthen the confidentiality of the random data displayed. The truncated half no longer exists anywhere, making it even more infeasible to reconstruct if the displayed output is observed by an attacker.
All in all, the system is effectively combining /dev/urandom from two independent machines, plus the physical entropy from the bubble wall and LED display, all through SHA3-512, which is believed to be quantum-resistant.
Threat Model
To compromise the output of this system, an attacker would need to simultaneously:
- Compromise
/dev/urandomon Bubba. This is the kernel’s CSPRNG, seeded from hardware interrupts, disk timing, and other system entropy. Compromising it means compromising the kernel itself. - Compromise
/dev/urandomon the Pi. A completely separate machine with its own independent entropy pool. - Predict or observe the webcam feed. Even if an attacker could see the bubble wall, they’d also need to account for camera sensor thermal noise (different per-pixel, per-frame) and the exact timing of frame capture.
- Recover the rotating key. The key changes every single output, derived from the previous round plus fresh urandom. It never touches disk. It only exists in process memory and is overwritten each cycle. This also provides forward secrecy: even if an attacker captures the key at time T, they can’t derive any output from time T-1, because the previous key was destroyed when the current one was derived from it.
- Do all of the above at the exact same nanosecond. The mixer includes a nanosecond timestamp and monotonic counter, so even with everything else compromised, the attacker needs the precise timing of each call.
In practice, if an attacker has kernel-level access to both machines simultaneously, they already own everything anyway, and the randomness quality is the least of my problems. The system is designed so that compromising any single component (the webcam, one machine’s urandom, the network connection, or even the LED display) isn’t enough. Even if the two machines get disconnected entirely, both continue producing cryptographically secure output on their own since each still mixes with its local /dev/urandom.
One of the questions that came up during development was: does the XOR-folding optimization (compressing ~6MB frame buffers to 4KB before hashing) reduce security? After all, an attacker now only needs to guess 4KB of data instead of 6MB. But the 4KB is never exposed. It goes straight into SHA3-512 alongside 64 bytes of fresh urandom, the rotating key, a counter, and a nanosecond timestamp. To verify a guess of the 4KB, you’d also need to guess all those other inputs. And even in isolation, 4KB is 32,768 bits, which is a search space of 2^32768. For context, there are roughly 2^266 atoms in the observable universe.
At the end of the day, the system’s security floor is /dev/urandom. That’s the baseline you get even if the webcam is unplugged, the Pi is disconnected, and the bubble wall is turned off. Everything else (visual entropy, the feedback loop, the second machine’s urandom, the rotating key) layers on top of that. The real question is whether the system adds any meaningful entropy beyond what the kernel already provides.
Does It Random?
I tested 1GB of output from each source across 100 runs and averaged the results. Bubba’s output is statistically indistinguishable from the system’s own RNGs:
Bubba /dev/urandom /dev/random
Shannon entropy (b/B) 8.0000 8.0000 8.0000
Min-entropy (b/B) 7.9798 7.9798 7.9802
Chi-squared p (%) 51.03 45.72 50.44
Serial correlation 0.000007 0.000003 0.000003
FIPS 140-2 pass/fail 419,061/339 419,055/345 419,047/353
Tested with three separate tools: ent, rngtest, and dieharder. Shannon entropy rounds to 8.0000 for all three, and min-entropy is at 0.9975 bits/bit, which is well above the NIST (National Institute of Standards and Technology) threshold of 0.8. Bubba actually had the fewest FIPS failures of the three (apparently a small amount of failures such as 0.08% is to be expected even from high quality sources). The biggest personal takeaway from my testing, however, was actually seeing for myself how well /dev/urandom and /dev/random perform as CSPRNGs.
The API
All of this entropy is useless if you can’t actually consume it. Bubba also runs a Flask API that any machine in my homelab can access to request random data. In practice, the primary use case for output like this is to seed a CSPRNG, which can then generate data at a much higher rate while inheriting the unpredictability of the physical source.
Here are some API examples:
# 32 bytes of raw randomness
curl localhost:5000/api/v1/random/bytes?n=32
18B30D30464F8328E834FC02A47D0E8BCE9EC082C3B8827329EEB52EBA746B01
# 12-word BIP-39 mnemonic seed phrase
curl localhost:5000/api/v1/seed/bip39?words=12
argue grit couple audit myth flee tuition fork steak goose wall noise
# UUID
curl localhost:5000/api/v1/random/uuid
851141cc-142a-4ee6-bebb-90aeb2b19055
There are endpoints for random integers, floats, UUIDs, URL-safe tokens, symmetric keys, X25519 keypairs, coin flips, and more. Everything is seeded directly from Bubba’s random data output rather than the system’s default CSPRNG.
Lessons Learned
Nothing used here is truly random. The bubble wall isn’t truly random. It’s governed by fluid dynamics, air pressure, water temperature, room lighting, and the geometry of the air pump. In theory, if you knew all of the variables precisely enough, you could predict every bubble. Cloudflare’s lava lamps aren’t truly random either. They’re driven by heat transfer, wax density, bulb temperature, room lighting, and convection currents.
Even without “true” randomness, it’s not all that difficult to create something so sensitive to initial conditions that predicting it is computationally infeasible. And that’s what really matters.
Security is about layering, not perfection. No single component of this system is unbreakable on its own. The webcam entropy is biased. /dev/urandom is algorithmic. The rotating key lives in memory. But stacked together, each one covers the others’ weaknesses. That’s the whole idea behind defense in depth.
The hardest part of this project was the physical part. Building the entire entropy pipeline, mixing, API, and test suite was all vibe coded via a well-refined plan and only took a fraction of the time it took to get the two LED panels powered, connected, and working without glitches.
Although Bubba is far from cryptographically proven and was really just a fun side project for my homelab, it allowed me to use creativity and deepen my knowledge just a little bit more.
Coin Flip
As a final note, I will now have Bubba perform a coin flip and let him decide for himself if he is actually secure (heads: yes, tails: no):
curl localhost:5000/api/v1/random/coin
heads
Bubba has spoken! /s