I was in the shower around 5am on Labor Day, watching the soapy water swirl and run down the drain. It reminded me of sea foam, and my thoughts drifted from waves to Nazaré before settling on Chaos in Lisbon.
50 wave machines running in unison to produce entropy, used to secure the web.
Form and function.
On January 2nd, 2025 I started tracking the conditions of my home office using a Raspberry Pi 0 W 2 and a Waveshare Environment Sensor HAT (ESH).
Sensor | Measurement Range / Specs |
---|---|
TSL25911 Ambient Light | 0 ~ 88,000 Lux |
BME280 Temp/Humidity/Pressure | -40 ~ 85 °C (±1 °C); 0 ~ 100 %RH (±3 %RH); 300 ~ 1100 hPa (±1 hPa) |
ICM20948 Motion (9-DOF) | Accel: ±2/4/8/16 g; Gyro: ±250/500/1000/2000 °/s; Mag: ±4900 µT |
LTR390-UV-1 UV | 280 ~ 430 nm wavelength |
SGP40 VOC | 0 ~ 1,000 ppm ethanol eq.; <10 s response; <60 s startup; on-chip humidity comp. |
I’ve been using the values for an ongoing visualization project, which can be viewed here: https://office.pure—internet.com/
I knew right away that this was an opportunity to explore ZK a bit more. The documentation for tooling like Circom and Noir is improving rapidly and LLMs can help get you up to speed quickly. Armed with a shower thought and bit of Claude Code and Cursor, I got to work.
I ended up choosing Circom. Noir’s backend agnostic approach was appealing, and I found the docs slightly more approachable, but the Barretenberg prover has issues on ARM systems.
329k rows → single seed
The master secret is the foundation of this system. I processed 329,000+ sensor readings collected since January 2nd, each containing measurements from 6 different environmental sensors (ambient light, temp, humidity, pressure, uv, and voc). The algorithm works like this:
Chunking: Divide the historical data into windows of 100 readings each
Fingerprinting: For each window, extract key statistical features - mean, variance, min/max values across all sensors
Hashing: Use Poseidon hash (ZK-friendly) to create a deterministic fingerprint for each window
Aggregation: Combine all window fingerprints through iterative hashing to produce a single 256-bit master secret
This approach ensures the master secret is:
- Unreproducible: You’d need the exact same sensor readings in the exact same order
- Deterministic: The same data always produces the same secret
- Privacy-preserving: The secret reveals nothing about the actual sensor values
Here’s the clever bit - it also looks at the frequency patterns in the motion data. Those little vibrations from trucks, footsteps, or the washing machine create unique signatures that are nearly impossible to replicate.
All of this gets mixed together with three key ingredients: the master secret I mentioned earlier (which stays hidden), whatever seed you provide (which anyone can see), and a timestamp to make sure we never make the same cocktail twice.
The result is a ZK proof that says “I promise this randomness came from real sensor data and was processed correctly” without ever revealing the details of my office. Even if you use the same seed multiple times, you’ll get different randomness because my office environment is constantly changing.
Building the proof
What am I actually proving when you generate randomness? There are four general promises: I know the master secret (but I won’t tell you what it is), I used real sensor data from my office (but I won’t reveal the readings), I processed everything through the right algorithm (no shortcuts or cheating), and nothing got tampered with along the way. A digital wax seal.
The neat part is what stays hidden versus what everyone can see. The secret stuff like the sensor readings, master secret, and all the intermediate calculations stays locked away. But the public stuff like your seed, the timestamp, the final random number, and a fingerprint of the sensor data is all out in the open for anyone to verify.
Keeping receipts
These proofs are small, about 2KB each, and they’re the evidence that everything happened correctly. I’m using IPFS through Pinata to store them. When a proof is generated and uploaded the CID and random number are store onchain.
The whole thing lives at 0xCf5Ea3Acb389b8a89935BD542273290F05f3054D
on Base Sepolia testnet.
What’s next (and what probably isn’t)
Right now, everything works. You can go to the site and generate verifiable randomness powered by whatever’s happening in my office at that moment. The full pipeline from sensors to ZK proof to blockchain is running, and it’s processed hundreds of proofs already.
But this is just the beginning of what could be possible. I keep thinking about a network of sensor operators contributing entropy to a shared pool. Cross-chain bridges would let this randomness flow to other blockchains. There are optimizations to explore - batching proofs together, using recursive proofs to compress things further.
Maybe someone will even define standards for environmental randomness beacons.
Does the world need another source of randomness? Maybe. Maybe not. Solutions like LavaRand and Chainlink VRF already provide APIs ate scale.
There’s something poetic about utilizing the chaos of dust particles, air currents, and temperature fluctuations for randomness. It’s a reminder that entropy is everywhere.
This project won’t revolutionize, but it might inspire someone to look at their environment differently. Whatever they come up with will be random in its own right.