diff --git a/README.md b/README.md index 0c804df..8ff84a5 100644 --- a/README.md +++ b/README.md @@ -1 +1,355 @@ # rorand + +Ring oscillator-based TRNG for the RP2040 + +## What is it + +`rorand` provides a true random number generator (TRNG) using only hardware +that's available inside the +[RP2040](https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html) +chip. It can be used either in its 'raw' form, or with an extra +whitening/compression step based on [Ascon](https://ascon.iaik.tugraz.at/)-Xofa. + +## Comparison + +| Name | Throughput | Entropy assurance | Extra components | Whitening | URL | +|:--------------------- |:------------------------ |:------------------------------ |:---------------- |:---------- |:------------- | +| rorand (this work) | 40 kbit/s | passes Dieharder (see below) | none | Ascon-Xofa | you are here! | +| Adafruit Trinkey TRNG | (40 kbit/s comms. limit) | Common Criteria EAL6+ | Infineon Trust M | ? | https://learn.adafruit.com/trinkey-qt2040-true-random-number-generator/overview | +| caprand | ? | ? | one 10nF capacitor | ChaCha20 | https://github.com/mkj/caprand | +| rp2040-entropy | 300 kbit/s? | fails a few dieharder tests | none (ADC noise) | none | https://github.com/hashky/rp2040-entropy | +| rprand | ~10 kbit/s | ? | none (ROSC+DMA) | CRC32 | https://github.com/alastairpatrick/rprand | +| pico-sdk | ? (probably >1Mbit/s) | ? (seed only, RNG is Xorshiro) | none (ROSC seed) | Xorshiro128 | https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_rand/rand.c | +| Cornell ECE4760 | ? | convergence to normal distribution | none (ROSC seed) | newlib/RAND48 | https://people.ece.cornell.edu/land/courses/ece4760/RP2040/C_SDK_random/index_random.html | + +## Requirements + +* Pico C SDK +* System must not run from ROSC, and the system clock must be at least 48 MHz + (80 MHz or higher preferred, 125 MHz or higher ideal). +* SysTick not used by something else + +## How it works + +The RP2040 includes a [ring +oscillator](https://en.wikipedia.org/wiki/Ring_oscillator) (unimaginatively +called ROSC). Such an oscillator is nothing more than a chain of inverters +wired up in a loop. If the number of inverters is odd, the circuit will +oscillate. + +Ring oscillators (just like many other oscillators) exhibit a phenomenon called +*jitter*: the length of the clock period varies ever so slightly from one +period to the next. This stems from various forms of noise (thermal noise, shot +noise, etc.) in the semiconductor, which are ultimately caused by the inherent +randomness of quantum physics. + +The RP2040 ROSC has a register called +"[RANDOMBIT](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-rosc-RANDOMBIT)", +which is basically a way of sampling the current state of the oscillator. On +first sight, this *sounds* like it would be random. However, much of the +perceived 'randomness' would actually stem from +[aliasing](https://en.wikipedia.org/wiki/Aliasing), because the code reading +this register would be undersampling. + +Instead, let's use the jitter phenomenon slightly differently: the phase +(jitter) noise from multiple oscillator cycles accumulates. Thus, the actual +real-world time it takes to complete `N` clock cycles has a larger variance +than the jitter of just one clock cycle. This is visualized in the image below: + +![A diagram of multiple jittery squarewaves overlaid. They all start at the +same time, but their ending time varies a lot (while the first few edges all +happen at mostly the same moment). Image from slides by Adriaan Petermans.](img/jitter.png) + +By using the [COUNT](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-rosc-COUNT) +register of the RP2040 ROSC, we can time how long it takes for it to reach zero +(compared to eg. the ARM SysTick timer running from the CPU clock). We can use +the least-significant bit of this timing difference (counted in CPU cycles) as +the randomness output! + +(As a sidenote: 'real professional' ring oscillator-based TRNGs typically use a +"transient effect ring oscillator" (TERO) design, which is a bit more robust. +Though, it also needs multiple oscillators and counters, and we don't have that +available here. So, the basic RO TRNG it is.) + +Well, almost. We still need to do a crucial final step: [entropy +extraction](https://en.wikipedia.org/wiki/Randomness_extractor). There are two +types of entropy extractors used most often: the parity filter (which XORs the +last N output bits together), and the Von Neumann filter (described in the link). +The Von Neumann filter is able to remove all [statistical +bias](https://en.wikipedia.org/wiki/Bias_(statistics)), but has more +requirements on the probability distribution of the inputs (they must be +"Independent and identically distributed", or IID for short). Meanwhile, the +parity filter has fewer requirements (only independence of inputs), but it +cannot remove all bias in practice (though it converges to this for larger and +larger N). + +For a ring oscillator, one would expect the random bits (generated as described +above) to be IID, so we can use the Von Neumann extractor. (This assumption +seems to hold in practice.) Oh, and we can still XOR this bit with the +RANDOMBIT register, because it's there. + +This is essentially how the "raw" output is generated. `rorand` also provides a +"cooked" output, which feeds the output of the "raw" TRNG into Ascon-Xofa. This +creature is a cryptographic PRNG based on what cryptographers call a "[sponge +construction](https://en.wikipedia.org/wiki/Sponge_function)". Sponge +constructions have all sorts of cool properties (which I won't talk about here +because it'd detract from the article, sadly), but one of them is that you can +build an *extensible-output function* (XOF) from them. XOFs are like hash +functions, but the output length can be made arbitrarily long. This makes it +rather easy to make a cryptographically-secure *reseedable* PRNG. + +Ascon is the finalist for the "lightweight portfolio" of the [CAESAR +competition](https://competitions.cr.yp.to/caesar-submissions.html). It's a +rather new (but promising, and built from solid foundations) cryptographic +cipher, specifically designed for slow embedded devices, such as the RP2040. It +should thus be faster than other options like ChaCha20/BLAKE2 or ARC4Random +(which is now considered insecure). AEGIS and Deoxys-II, the other two CAESAR +finalists for other purposes, are based on the AES round transformation, and +would thus be quite a bit slower. + +## How good is it + +### Entropy + +The randomness obtained from this TRNG seems to pass the +[Dieharder](https://webhome.phy.duke.edu/~rgb/General/dieharder.php) test +suite. The output is given below. + +Note that a few entries are marked as `WEAK`, but those seem to appear and +disappear when I try and retry different runs. This seems to happen due to the +used sample size: one megabyte of random data takes a few minutes to generate, +so much larger samples are quite a bit of a hassle to obtain. Running output +from `/dev/random` with the same sizes also has the `WEAK` warnings pop up and +disappear at random, so it should be fine. + +**WARNING**: However, these `WEAK` warnings seem to pop up more often with +decreasing RP2040 system clock speed (and, slightly, voltage as well). While no +`FAILED` errors have popped up, depending on your needs, only use `rorand` with +a high enough system clock frequency (≥80 MHz preferred, ≥125 MHz ideal). + +Dieharder isn't the most stringent test suite, though. +[TestU01](http://simul.iro.umontreal.ca/testu01/tu01.html)'s BigCrush suite is +much more comprehensive, but requires ***much*** more data (many gigabytes). +Thanks to the limited throughput of a hardware TRNG, using this test isn't very +feasible. Real evaluations such as FIPS 140-2, or Common Criteria EAL4 or +higher, is completely out of my means. + +In conclusion, don't trust someone's life --- neither your own nor someone +else's --- with this. But otherwise, you should be fine. If you need the +assurance (or if you're paranoid), please use a FIPS- or Common +Criteria-certified hardware module, though. + +Dieharder output (raw output, RP2040 running at 250 MHz, core voltage 1.15 V): + +``` +#=============================================================================# + test_name |ntup| tsamples |psamples| p-value |Assessment +#=============================================================================# + diehard_birthdays| 0| 100| 100|0.22538373| PASSED + diehard_operm5| 0| 1000000| 100|0.63794545| PASSED + diehard_rank_32x32| 0| 40000| 100|0.73090642| PASSED + diehard_rank_6x8| 0| 100000| 100|0.99254745| PASSED + diehard_bitstream| 0| 2097152| 100|0.58114397| PASSED + diehard_opso| 0| 2097152| 100|0.18319256| PASSED + diehard_oqso| 0| 2097152| 100|0.93914840| PASSED + diehard_dna| 0| 2097152| 100|0.74871515| PASSED +diehard_count_1s_str| 0| 256000| 100|0.20413208| PASSED +diehard_count_1s_byt| 0| 256000| 100|0.56507750| PASSED + diehard_parking_lot| 0| 12000| 100|0.43004782| PASSED + diehard_2dsphere| 2| 8000| 100|0.68150341| PASSED + diehard_3dsphere| 3| 4000| 100|0.50961304| PASSED + diehard_squeeze| 0| 100000| 100|0.73895828| PASSED + diehard_sums| 0| 100| 100|0.89067279| PASSED + diehard_runs| 0| 100000| 100|0.88549264| PASSED + diehard_runs| 0| 100000| 100|0.17826705| PASSED + diehard_craps| 0| 200000| 100|0.74363795| PASSED + diehard_craps| 0| 200000| 100|0.92809716| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.21033260| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.90826850| PASSED + sts_monobit| 1| 100000| 100|0.66704052| PASSED + sts_runs| 2| 100000| 100|0.82376608| PASSED + sts_serial| 1| 100000| 100|0.40789077| PASSED + sts_serial| 2| 100000| 100|0.11533197| PASSED + sts_serial| 3| 100000| 100|0.57906405| PASSED + sts_serial| 3| 100000| 100|0.88247272| PASSED + sts_serial| 4| 100000| 100|0.35109410| PASSED + sts_serial| 4| 100000| 100|0.06001064| PASSED + sts_serial| 5| 100000| 100|0.24597035| PASSED + sts_serial| 5| 100000| 100|0.42776664| PASSED + sts_serial| 6| 100000| 100|0.34671248| PASSED + sts_serial| 6| 100000| 100|0.63443378| PASSED + sts_serial| 7| 100000| 100|0.34901523| PASSED + sts_serial| 7| 100000| 100|0.87482945| PASSED + sts_serial| 8| 100000| 100|0.83333396| PASSED + sts_serial| 8| 100000| 100|0.98096154| PASSED + sts_serial| 9| 100000| 100|0.18613150| PASSED + sts_serial| 9| 100000| 100|0.98222632| PASSED + sts_serial| 10| 100000| 100|0.06064720| PASSED + sts_serial| 10| 100000| 100|0.53659409| PASSED + sts_serial| 11| 100000| 100|0.48783888| PASSED + sts_serial| 11| 100000| 100|0.44013196| PASSED + sts_serial| 12| 100000| 100|0.89056741| PASSED + sts_serial| 12| 100000| 100|0.55084354| PASSED + sts_serial| 13| 100000| 100|0.55991536| PASSED + sts_serial| 13| 100000| 100|0.79919790| PASSED + sts_serial| 14| 100000| 100|0.99559967| WEAK + sts_serial| 14| 100000| 100|0.60230091| PASSED + sts_serial| 15| 100000| 100|0.91600221| PASSED + sts_serial| 15| 100000| 100|0.29385991| PASSED + sts_serial| 16| 100000| 100|0.80170010| PASSED + sts_serial| 16| 100000| 100|0.89169608| PASSED + rgb_bitdist| 1| 100000| 100|0.25864662| PASSED + rgb_bitdist| 2| 100000| 100|0.41244241| PASSED + rgb_bitdist| 3| 100000| 100|0.80837541| PASSED + rgb_bitdist| 4| 100000| 100|0.93133416| PASSED + rgb_bitdist| 5| 100000| 100|0.70903269| PASSED + rgb_bitdist| 6| 100000| 100|0.29717638| PASSED + rgb_bitdist| 7| 100000| 100|0.71361547| PASSED + rgb_bitdist| 8| 100000| 100|0.03450956| PASSED + rgb_bitdist| 9| 100000| 100|0.14545557| PASSED + rgb_bitdist| 10| 100000| 100|0.90299368| PASSED + rgb_bitdist| 11| 100000| 100|0.53321027| PASSED + rgb_bitdist| 12| 100000| 100|0.65243210| PASSED +rgb_minimum_distance| 2| 10000| 1000|0.29295560| PASSED +rgb_minimum_distance| 3| 10000| 1000|0.28825100| PASSED +rgb_minimum_distance| 4| 10000| 1000|0.84249889| PASSED +rgb_minimum_distance| 5| 10000| 1000|0.53807064| PASSED + rgb_permutations| 2| 100000| 100|0.72182964| PASSED + rgb_permutations| 3| 100000| 100|0.98132372| PASSED + rgb_permutations| 4| 100000| 100|0.32426721| PASSED + rgb_permutations| 5| 100000| 100|0.94033412| PASSED + rgb_lagged_sum| 0| 1000000| 100|0.78964883| PASSED + rgb_lagged_sum| 1| 1000000| 100|0.82881744| PASSED + rgb_lagged_sum| 2| 1000000| 100|0.90805811| PASSED + rgb_lagged_sum| 3| 1000000| 100|0.21428921| PASSED + rgb_lagged_sum| 4| 1000000| 100|0.67964064| PASSED + rgb_lagged_sum| 5| 1000000| 100|0.64463634| PASSED + rgb_lagged_sum| 6| 1000000| 100|0.94268050| PASSED + rgb_lagged_sum| 7| 1000000| 100|0.33288741| PASSED + rgb_lagged_sum| 8| 1000000| 100|0.79565468| PASSED + rgb_lagged_sum| 9| 1000000| 100|0.26291492| PASSED + rgb_lagged_sum| 10| 1000000| 100|0.23887951| PASSED + rgb_lagged_sum| 11| 1000000| 100|0.75511918| PASSED + rgb_lagged_sum| 12| 1000000| 100|0.44806331| PASSED + rgb_lagged_sum| 13| 1000000| 100|0.57950849| PASSED + rgb_lagged_sum| 14| 1000000| 100|0.37109436| PASSED + rgb_lagged_sum| 15| 1000000| 100|0.89758378| PASSED + rgb_lagged_sum| 16| 1000000| 100|0.06000037| PASSED + rgb_lagged_sum| 17| 1000000| 100|0.87051454| PASSED + rgb_lagged_sum| 18| 1000000| 100|0.47509380| PASSED + rgb_lagged_sum| 19| 1000000| 100|0.34523765| PASSED + rgb_lagged_sum| 20| 1000000| 100|0.98418490| PASSED + rgb_lagged_sum| 21| 1000000| 100|0.17652983| PASSED + rgb_lagged_sum| 22| 1000000| 100|0.17374595| PASSED + rgb_lagged_sum| 23| 1000000| 100|0.63170066| PASSED + rgb_lagged_sum| 24| 1000000| 100|0.28953394| PASSED + rgb_lagged_sum| 25| 1000000| 100|0.64849257| PASSED + rgb_lagged_sum| 26| 1000000| 100|0.78468683| PASSED + rgb_lagged_sum| 27| 1000000| 100|0.36484261| PASSED + rgb_lagged_sum| 28| 1000000| 100|0.66712700| PASSED + rgb_lagged_sum| 29| 1000000| 100|0.48604229| PASSED + rgb_lagged_sum| 30| 1000000| 100|0.17591412| PASSED + rgb_lagged_sum| 31| 1000000| 100|0.32433586| PASSED + rgb_lagged_sum| 32| 1000000| 100|0.12971163| PASSED + rgb_kstest_test| 0| 10000| 1000|0.47803450| PASSED + dab_bytedistrib| 0| 51200000| 1|0.29203091| PASSED + dab_dct| 256| 50000| 1|0.06993184| PASSED +Preparing to run test 207. ntuple = 0 + dab_filltree| 32| 15000000| 1|0.38387488| PASSED + dab_filltree| 32| 15000000| 1|0.55295440| PASSED +Preparing to run test 208. ntuple = 0 + dab_filltree2| 0| 5000000| 1|0.24922510| PASSED + dab_filltree2| 1| 5000000| 1|0.98227801| PASSED +Preparing to run test 209. ntuple = 0 + dab_monobit2| 12| 65000000| 1|0.71153312| PASSED +``` + +At 48 MHz and a core voltage of 0.95 V, there would be three or four `WARN`ings. + +### Throughput + +The TRNG is rather CPU-intensive (due to its IRQ-blocking busyloop) and not +very fast. The "cooked" (whitened) throughput is about 10 times higher than the +"raw" throughput. + +| System clock | Raw TRNG throughput | +|:------------ |:------------------- | +| 48 MHz | 10 kbit/s | +| 125 MHz | 40 kbit/s | +| 250 MHz | 50 kbit/s | + +## What is missing? + +* **Online health tests**: I don't know how to do this. I don't know what a + "chi-squared" is. Health tests are rather important for TRNGs (to make sure + nothing weird is happening), so this is not a small downside. +* **DMA+PIO-based implementation**: to relieve the CPU from some of its load. + Currently, it's 100% a spinloop on the CPU, with interrupts disabled. +* **Multicore mutex**: currently, both CPU cores could run this code, which + would then modify the ROSC `COUNT` register in parallell. This is bad and + will lead to bad results. Don't do this. (Or alternatively, add mutex guards, + or bug me to do so.) +* **More useful `rand()` functions**: currently, the code just gives you bits, + and turning these bits into a random integer in a range, or a random float, + ..., is left as an exercise for the reader. +* **Protections against physical attacks**: if an adversary is in the physical + proximity of the device, it's game over. They could attach an SWD debugger to + the RP2040 (it has no protection against this), use a power side channel or + EM emanations to look at the ROSC doing its oscillate-y thing, or tamper with + the environment (e.g. freeze spray or harmonic EM emissions) to slow down the + ROSC or make it lock onto a different frequency. I don't plan to add any + defenses against such attacks. (Please get a properly certified hardware + module if such attacks are part of your threat model, I'm begging you, don't + use this.) + +## Usage + +Here's a quick example: + +```c +#include +#include + +#include "rorand.h" /* raw rng */ +#include "rourand.h" /* 'cooked'/whitened rng */ + + +static void health_alarm(const char* msg) { + // TRNG health alarm raised! + iprintf("TRNG health alarm! %s\n", msg); +} + +int main() { + /** raw randomness **/ + enum rourand_error r = rorand_init(health_alarm); + if (r != rr_ok) { + panic("rorand failed to initialize!"); + } + + // get 128 bits of raw randomness + uint8_t data[16]; + rorand_get(data, sizeof(data)*CHAR_BIT); // specified in bits + + /** 'cooked'/whitened randomness **/ + int rate = 0; // rate at which the CSPRNG will reseed with true random data. 0 is default. + // you (the user) must call rorand_init first before calling rourand_init! + struct rourand_state* ur = rourand_init(rorand_get, rate); + if (!ur) { + panic("rourand failed to initialize!"); + } + + rourand_get(ur, data, sizeof(data)); // specified in bytes + rourand_free(ur); +} +``` + +## License + +License is TBD. + +The [Ascon reference implementation](https://github.com/ascon/ascon-c) is +licensed under CC0 1.0. + +The [Raspberry Pico C SDK](https://github.com/raspberrypi/pico-sdk) is licensed +under the BSD-3-Clause license. + diff --git a/img/jitter.png b/img/jitter.png new file mode 100644 index 0000000..26aa2f2 Binary files /dev/null and b/img/jitter.png differ diff --git a/src/main.c b/src/main.c index bd1fbc8..99dcc29 100644 --- a/src/main.c +++ b/src/main.c @@ -27,6 +27,8 @@ static uint8_t data[32]; int main() { + vreg_set_voltage(VREG_VOLTAGE_1_15); + set_sys_clock_khz(250*1000, true); // high freq, high voltage //vreg_set_voltage(VREG_VOLTAGE_1_25); //set_sys_clock_khz(250*1000, true);