Generating a random number - true random numbers, not the pseudo-random numbers generated in every language - is a bit involved.
In this project, I use the entropy in the background radioactivity to generate these numbers.
Wikipedia defines background radiation as the level of ionising radiation present in the environment at a particular location that is not due to the deliberate introduction of radiation sources.
There are both terrestrial and extraterrestrial sources of it. Low, naturally occurring ores of potassium-40, uranium-238, and thorium-232 form most of the terrestrial sources. Plus radon-222, which is a gas that naturally emanates from the ground - varies obviously by location, ventilation, and season.
Extraterrestial sources are primarily from cosmic rays, which are high-energy photons. When they slam into atomic neuclei in the upper atmosphere, they set off a shower of sub-atomic pions, which then decay into muons. 10,000 of them, on average roughly per square metre per minute, travelling at nearly the speed of light.
Even though the decay of a large number of atoms can be predicted by it's half life, the decay of a single ratioactive atom is guaranteed to be unpredictable by the laws of quantum physics.
We will use a Geiger-Muller counter. Yes, the same clicky things from the movies. Good ones used for actual safety detection are pretty pricey. But for our purposes, a cheap one - one that might not detect all ambient radiation is no problem. I bought an unassembled kit as a kit for around £20 as I loved the opportunity to solder some simple through-hole components. You can get a fully assembled kit for around £22 here (link might expire).
The only thing to ensure is that the board is the same as below: RadiationD-v1.1(CAJOE)
If you want, you can replace the Chinese J tube with a Russian SBM-20 tube if you will use this to work as a radiation detector. Tube comparisons can be consulted for a better fit. Below is a chart of characteristics of the J tubes that usually come in the package.
How a Geiger-Muller detector works is pretty clever - explanation on Wikipedia
Well, this is fairly simple: we can count the time in between detections. As the events are random, the time interval between them is also random.
We can use any microcontroller or even a decade counter chip or circuit to do this. But I wanted an API end point, so any computer on the network could request a random number from this. I used an ESP32 for the following reasons:
- inbuilt WiFi: I can connect to my router and respond to requests.
- dual-core: one core can generate the random numbers in real time while the other listens and responds to the API endpoints.
A ESP32 dev board costs ~£10, like this (The link might expire.)
The 3 connections between the ESP32 and the Geiger-Muller (GM) detecter board that we need are:
- ESP +5v to GM P3 5v pin (we will run the detector from the USB plugged into the ESP32, so it's a single-wire system)
- ESP GND to GM P3 GND (common ground; this is needed even if you power the boards separately).
- ESP pin 13 (configured in main.cpp) to GM P3 Vin pin
The ESP32 controller has two cores.
Core 1:
Core 1 will set up an interrupt on pin 13 (the pin connected to the GM detector) and start counting from 0 to 35565, resetting back to 0 if it goes over. When the GM counter gets triggered, the program will save the current number it was on. The number is saved in a queue - a FIFO data structure. Then it will reset the counter to 0 and start all over again. It will continue to fill the queue with random numbers until the queue is full (configured to 50,000 numbers).
Core 2:
Core 2 will set up a wifi connection, obtain an IP address, and listen for GET requests on its endpoint. If it receives a request to check the current queue depth, it will return the depth as an integer. If it receives a request for a random number, it will pop the first number in the queue and return it. Optionally, the random number request can specify the "max" (instead of the default max of 65535). The program then converts the saved random number (0-65535) to the requested range (0-"max") and sends it through. While doing so, to retain the entropy of the original number, it discards the number it picks from the queue that is greater than the largest integral multiple of "max" which is less than 65535.
A single usb cable powers both the microcontroller and the GM detector.
See this Jupyter Notebook for an example of how to call this API.
Some thoughts:
- Slow generation. But you should be worried if the generation is quick - as that will imply you're in a high radiation environment :-)
- The default implementation of queue in C++ is not thread-safe. As we have both cores accessing the queue, had to implement a custom thread safe version of it.
- If you don't like incessant clicking then remove the jumper J1 on the GM detector - this disconnects the speaker. This also reduces the power consumption.