Here at Ripple we work hard to solve hard problems. But we like to have fun, experiment and goof off a little bit too. Like most tech companies, we have happy hours or throw the occasional hackathon. But sometimes… Sometimes we come up with crazy science fair-ish ideas and, even when they have dubious practical application, implement them.
One of the more interesting problems in computing is the generation of random numbers. When you ask your computer for a random number, it does so using a mathematical formula. This is really fast, but it’s only what we call pseudorandom. Now, using pseudorandom numbers isn’t a big deal when generating a shuffled deck of cards for a game of solitaire, but sometimes you might want something a little stronger. Like, for example, when you are generating the secret key of a cryptocurrency address.
So, how do you get “true” randomness? One technique is to measure real-world events with a high degree of precision, then use the lowest-order bits of that measurement as your input. Temperature, barometric pressure, time between unrelated events -- any of these can be mined to generate input for an algorithm that will spit out numbers of the desired length. Cloudflare uses lava lamps! But if you want to get capital-‘R’-random, you have to go way down there. I mean waaaay down there into the atomic and subatomic level. I’m talking about radioactive decay.
Using Radioactive Decay
Generating random numbers from radioactive decay has been done before. That site is an oldie, but it’s an excellent source of information presented in a way that’s pretty easy to understand. I encourage everyone to take a look at it. One of the more interesting things you’ll find is a simple algorithm for generating random numbers from radioactive decay (the site also points out some flaws in other methods).
The TLDR; is this: we measure the time between 2 successive Geiger Counter click-events with as much precision as we can. Then we do the same thing for another set of events. We compare the pair of times, A and B. If A > B
we emit a 1
, if A < B
we emit a 0
. (If the results are equal we throw out the numbers so we don’t weight the 1
or 0
disproportionately.) We repeat this process until we have a binary number of appropriate length. In this case we will need 128 bits.
Doing the work
Back during the Fukushima nuclear disaster I got interested in background radiation, so I grabbed one of these devices from Sparkfun.
It is essentially a Geiger tube connected to a microcontroller (ATMega 328p if anyone cares) which then, in turn, connects to a PC over a USB cable. Right out of the box it can very simply and easily measure the CPM (counts per minute) of background radiation in the nearby environment. (Note: there is ALWAYS a certain level of background radiation that is natural and perfectly safe.) The cool people at Sparkfun tried generating random numbers but ended up with numbers that didn’t quite measure up statistically. (There is a discussion on why in the comments section of their tutorial.) While the original code for the device was written in C, the device is Arduino-compatible. I wanted it to be easily accessible to hobbyists so with a little tweaking I was able to implement the algorithm described on the Hotbits website using the Arduino IDE.
The code lives here.
When you install and run the arduino workspace, it prints a JSON payload to the serial port with a binary and hexadecimal representation of a 128-bit number, and the length of time in milliseconds it took to generate that number. Here is some example output.
{
"hex" : "57DB91F7BF5F5036F2D1A854506D38C5",
"bin": "01010111110110111001000111110111101111110101111101010000001101101111001011010001101010000101010001010000011011010011100011000101",
"total_time_ms" : "2602264"
}
Wait, over 2,600,000 milliseconds?! That’s… math, math, math... 43 minutes! Yes, it does take that long. The CPM at my desk just isn’t very high, and that’s probably good news. (Unless I was hoping to develop superpowers as comic books led me to believe.)
For fun, I decided to let it run over the weekend to see how much data I could gather. After saving the serial port datastream to a file, I could run some quick bash commands to see what we got.
Number of results:
$ cat random_numbers.json | jq '. | length'
81
Wow! 81 numbers to play with. Let’s see exactly how long that took to generate.
$ cat random_numbers.json | jq '.[0:80] | map(.total_time_ms) | add | . / 1000 / 60 / 60'
56.37592805555555
Ok, 56 hours to generate 81 numbers. That’s around 41 minutes each!
Generating Wallet Addresses
But how do we use these to generate cryptocurrency addresses? Well from the XRPL wallet_propose
documentation, “Specifying a Seed” section, we learn we can pass in a 128-bit HEX string as the input seed to the function. What a coincidence! Let’s try it out.
The easiest way to get access to the methods described in the docs is to start up a container with the “rippled” server. Since this use case doesn’t need to communicate with other nodes we can start the server in standalone mode.
$ docker run -dit --name rippledvalidator xrptipbot/rippledvalidator:latest -a --start
Next thing to do is call the admin function for wallet_propose
. Let’s test it out by executing a docker command like so:
$ docker exec rippledvalidator rippled wallet_propose
That will generate an address, but not one that uses our generated random numbers. To accomplish that we can feed those into our command like this:
$ cat ./random_numbers.json | \
jq '.[].hex' | \
tr -d \" | \
xargs -I {} docker exec rippledvalidator rippled wallet_propose seed_hex={}
This will generate an address for each hex number it finds in the random_numbers.json
file. Output for a hex number looks like this:
{
"result": {
"account_id": "rsuzdgecj1H….",
"key_type": "secp256k1",
"master_key": "LAVA BUD WOW ….",
"master_seed": "shViZcdk3pmc…."
"master_seed_hex": "81646F6422401A22E….",
"public_key": "aBPeiEY9dRSY2J1hjCy5Cb6tX….",
"public_key_hex": "02DF505026F3E44B3C4C….",
"status": "success",
"warning": "This wallet was generated using a user-supplied passphrase. It may be vulnerable to brute-force attacks."
}
}
Caveats
There are a few things we need to point out and warnings to issue in case you were thinking about trying this out. The use of novel methods in generating random numbers may be fun, but should not be used in any production system without thorough testing by trained professionals. While this method is definitely non-standard, it is meant to be at least as random as built-in tools and, in theory, could be even better than some of them for certain applications. But researchers are always poking holes in real-world implementations of theories and proving them less secure than intended. For those reasons we can’t recommend you use this as a method for generating wallet addresses for storing any significant amount of any cryptocurrency.
A few other things that would need to be improved upon before considering this for any “real” application:
- Usage of the file
random_numbers.json
. This is a shortcut for demonstrating feasibility and one should not be storing seed data like this in a plain text file out in the open like this. Chain of custody is very important when using the data to generate wallet addresses. Grabbing the data right out of the port would be better but still not ideal. Getting the microcontroller code to talk to the rippled server would be even better. - The device we chose uses a USB -> Serial port emulator that has few, if any, security features. If doing anything like this for anything serious it would be a good idea to follow recommendations for an offline, air-gapped machine like the one we outline in this tutorial.
What next?
Fortyish minutes to generate one address is obviously not very practical for any real-world use-cases, but this proves the concept. There are a few things we could do to speed it up.
I used the naturally occurring background radiation for my testing. Here in the San Francisco Bay area, we are near sea level so the background isn’t very high, measuring anywhere from 10 - 30 CPM. But if I moved to someplace with a higher elevation--Denver perhaps--the CPM would go up into the 70s. But I like the office snacks here in SF, so that’s out.
We could use a hotter source of radiation: setting the counter next to some uranium would definitely work! I thought about taking apart a smoke detector but… I think our facilities department would frown on having piles of quickly-fissioning materials lying about.
We could try some things that are “warmer” than normal background, like bananas or salt substitute. Safe enough, but also not much faster. We could also try improving the algorithm or adding more counters to see if parallelization will help.
My weekends could be filled with tweaks and incremental improvements to the methods and results. But, realistically speaking, I’m probably going to move on to other projects and problems before I really crack this one. Still, taking things back to first principles was fun and educational. More knowledge is never a bad thing to have. At some random point in the future, what I learned from this project might be just what’s needed.
If you think solving this and other hard problems is for you, come and work with us!
Cover photo by dylan nolte on Unsplash