Skip to content

Commit 35401b5

Browse files
authored
Merge pull request #10826 from mimi89999/trng_rp2350
Use TRNG as additional entropy source on RP2350
2 parents 52ad29d + 3840b81 commit 35401b5

File tree

1 file changed

+139
-14
lines changed

1 file changed

+139
-14
lines changed

ports/raspberrypi/common-hal/os/__init__.c

Lines changed: 139 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,159 @@
1818

1919
#include <string.h>
2020

21+
#ifdef HAS_RP2350_TRNG
22+
#include "hardware/structs/trng.h"
23+
#include "hardware/sync.h"
24+
#endif
25+
2126
// NIST Special Publication 800-90B (draft) recommends several extractors,
2227
// including the SHA hash family and states that if the amount of entropy input
2328
// is twice the number of bits output from them, that output can be considered
24-
// essentially fully random. If every RANDOM_SAFETY_MARGIN bits from
25-
// `rosc_hw->randombit` have at least 1 bit of entropy, then this criterion is met.
29+
// essentially fully random.
30+
//
31+
// This works by seeding `random_state` with entropy from hardware sources
32+
// (SHA-256 as the conditioning function), then using that state as a counter
33+
// input (SHA-256 as a CSPRNG), re-seeding at least every 256 blocks (8kB).
2634
//
27-
// This works by seeding the `random_state` with plenty of random bits (SHA256
28-
// as entropy harvesting function), then using that state it as a counter input
29-
// (SHA256 as a CSPRNG), re-seeding at least every 256 blocks (8kB).
35+
// On RP2350, entropy comes from both the dedicated TRNG peripheral and the
36+
// ROSC. On RP2040, the ROSC is the only available source.
3037
//
3138
// In practice, `PractRand` doesn't detect any gross problems with the output
3239
// random numbers on samples of 1 to 8 megabytes, no matter the setting of
33-
// RANDOM_SAFETY_MARGIN. (it does detect "unusual" results from time to time,
40+
// ROSC_SAFETY_MARGIN. (it does detect "unusual" results from time to time,
3441
// as it will with any RNG)
35-
#define RANDOM_SAFETY_MARGIN (4)
42+
43+
// Number of ROSC collection rounds on RP2040. Each round feeds
44+
// SHA256_BLOCK_SIZE bytes into the hash; we do 2*N rounds so the
45+
// raw-to-output ratio satisfies 800-90B's 2:1 minimum.
46+
#define ROSC_SAFETY_MARGIN (4)
3647

3748
static BYTE random_state[SHA256_BLOCK_SIZE];
49+
50+
// Collect `count` bytes from the ROSC, one bit per read.
51+
static void rosc_random_bytes(BYTE *buf, size_t count) {
52+
for (size_t i = 0; i < count; i++) {
53+
buf[i] = rosc_hw->randombit & 1;
54+
for (int k = 0; k < 8; k++) {
55+
buf[i] = (buf[i] << 1) ^ (rosc_hw->randombit & 1);
56+
}
57+
}
58+
}
59+
60+
#ifdef HAS_RP2350_TRNG
61+
62+
// TRNG_DEBUG_CONTROL bypass bits:
63+
//
64+
// bit 1 VNC_BYPASS Von Neumann corrector
65+
// bit 2 TRNG_CRNGT_BYPASS Continuous Random Number Generator Test
66+
// bit 3 AUTO_CORRELATE_BYPASS Autocorrelation test
67+
//
68+
// We bypass Von Neumann and autocorrelation but keep CRNGT.
69+
//
70+
// Von Neumann (bypassed): ~4x throughput cost for bias removal.
71+
// Redundant here because SHA-256 conditioning already handles
72+
// biased input -- that's what the 2:1 oversampling ratio is for.
73+
//
74+
// Autocorrelation (bypassed): has a non-trivial false-positive rate
75+
// at high sampling speeds and halts the TRNG until SW reset on
76+
// failure. SHA-256 is not bothered by correlated input. ARM's own
77+
// TZ-TRNG 90B reference configuration also bypasses it (0x0A).
78+
//
79+
// CRNGT (kept): compares consecutive 192-bit EHR outputs. Flags if
80+
// identical -- false-positive rate 2^-192, throughput cost zero.
81+
// This is our early warning for a stuck oscillator or a successful
82+
// injection lock to a fixed state.
83+
#define TRNG_BYPASS_BITS \
84+
(TRNG_TRNG_DEBUG_CONTROL_VNC_BYPASS_BITS | \
85+
TRNG_TRNG_DEBUG_CONTROL_AUTO_CORRELATE_BYPASS_BITS)
86+
87+
// Collect 192 raw bits (6 x 32-bit words) from the TRNG.
88+
// Returns false on CRNGT failure (consecutive identical EHR outputs).
89+
//
90+
// Holds PICO_SPINLOCK_ID_RAND (the SDK's lock for this peripheral)
91+
// with interrupts disabled for the duration of the collection, which
92+
// takes ~192 ROSC cycles (~24us at 8MHz).
93+
static bool trng_collect_192(uint32_t out[6]) {
94+
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND);
95+
uint32_t save = spin_lock_blocking(lock);
96+
97+
trng_hw->trng_debug_control = TRNG_BYPASS_BITS;
98+
// One rng_clk cycle between samples. The SDK uses 0 here, but it
99+
// also sets debug_control = -1u (full bypass). The behavior of
100+
// sample_cnt1 = 0 with health tests still active is undocumented,
101+
// so we use 1 to be safe.
102+
trng_hw->sample_cnt1 = 1;
103+
trng_hw->rnd_source_enable = 1;
104+
trng_hw->rng_icr = 0xFFFFFFFF;
105+
106+
while (trng_hw->trng_busy) {
107+
}
108+
109+
if (trng_hw->rng_isr & TRNG_RNG_ISR_CRNGT_ERR_BITS) {
110+
// Drain ehr_data so the hardware starts a fresh collection.
111+
// (Reading the last word clears the valid flag.)
112+
for (int i = 0; i < 6; i++) {
113+
(void)trng_hw->ehr_data[i];
114+
}
115+
trng_hw->rng_icr = TRNG_RNG_ISR_CRNGT_ERR_BITS;
116+
spin_unlock(lock, save);
117+
return false;
118+
}
119+
120+
for (int i = 0; i < 6; i++) {
121+
out[i] = trng_hw->ehr_data[i];
122+
}
123+
124+
// Switch the inverter chain length for the next collection, using
125+
// bits from the sample we just read. Only bits [1:0] matter -- they
126+
// select one of four chain lengths, changing the ROSC frequency.
127+
// This is borrowed from pico_rand's injection-locking countermeasure.
128+
// (The SDK uses its PRNG state here instead of raw output; either
129+
// works since the real defense is SHA-256 conditioning, not this.)
130+
trng_hw->trng_config = out[0];
131+
132+
spin_unlock(lock, save);
133+
return true;
134+
}
135+
136+
#endif // HAS_RP2350_TRNG
137+
38138
static void seed_random_bits(BYTE out[SHA256_BLOCK_SIZE]) {
39139
CRYAL_SHA256_CTX context;
40140
sha256_init(&context);
41-
for (int i = 0; i < 2 * RANDOM_SAFETY_MARGIN; i++) {
42-
for (int j = 0; j < SHA256_BLOCK_SIZE; j++) {
43-
out[j] = rosc_hw->randombit & 1;
44-
for (int k = 0; k < 8; k++) {
45-
out[j] = (out[j] << 1) ^ (rosc_hw->randombit & 1);
141+
142+
#ifdef HAS_RP2350_TRNG
143+
// 384 bits from TRNG + 384 bits from ROSC = 768 bits into the hash,
144+
// giving a 3:1 ratio over the 256-bit output (800-90B wants >= 2:1).
145+
// Two independent sources so a failure in one doesn't zero the input.
146+
147+
// TRNG: 2 x 192 bits.
148+
for (int i = 0; i < 2; i++) {
149+
uint32_t trng_buf[6] = {0};
150+
for (int attempt = 0; attempt < 3; attempt++) {
151+
if (trng_collect_192(trng_buf)) {
152+
break;
46153
}
154+
// CRNGT failure. If all 3 retries fail, trng_buf stays zeroed
155+
// and we rely entirely on the ROSC contribution below.
47156
}
157+
sha256_update(&context, (const BYTE *)trng_buf, sizeof(trng_buf));
158+
}
159+
160+
// ROSC: 2 x 24 bytes = 384 bits.
161+
for (int i = 0; i < 2; i++) {
162+
BYTE rosc_buf[24];
163+
rosc_random_bytes(rosc_buf, sizeof(rosc_buf));
164+
sha256_update(&context, rosc_buf, sizeof(rosc_buf));
165+
}
166+
#else
167+
// RP2040: ROSC is the only entropy source.
168+
for (int i = 0; i < 2 * ROSC_SAFETY_MARGIN; i++) {
169+
rosc_random_bytes(out, SHA256_BLOCK_SIZE);
48170
sha256_update(&context, out, SHA256_BLOCK_SIZE);
49171
}
172+
#endif
173+
50174
sha256_final(&context, out);
51175
}
52176

@@ -61,10 +185,11 @@ static void get_random_bits(BYTE out[SHA256_BLOCK_SIZE]) {
61185
}
62186

63187
bool common_hal_os_urandom(uint8_t *buffer, mp_uint_t length) {
64-
#define ROSC_POWER_SAVE (1) // assume ROSC is not necessarily active all the time
188+
#define ROSC_POWER_SAVE (1) // assume ROSC is not necessarily active all the time
65189
#if ROSC_POWER_SAVE
66190
uint32_t old_rosc_ctrl = rosc_hw->ctrl;
67-
rosc_hw->ctrl = (old_rosc_ctrl & ~ROSC_CTRL_ENABLE_BITS) | (ROSC_CTRL_ENABLE_VALUE_ENABLE << 12);
191+
rosc_hw->ctrl = (old_rosc_ctrl & ~ROSC_CTRL_ENABLE_BITS)
192+
| (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB);
68193
#endif
69194
while (length) {
70195
size_t n = MIN(length, SHA256_BLOCK_SIZE);

0 commit comments

Comments
 (0)