rng: boot-time self-test proving rng_get() enters the hardware read path
What changed, and why it matters
This commit adds a startup safety check for the hardware random-number generator (RNG) in the COLDCARD MK4 firmware. Before anything else can use randomness, the device now verifies that the rng_get() function actually reaches the hardware RNG and returns real entropy. If the check fails, the device deliberately halts ('bricks' itself) rather than boot. The change is defensive: it does not fix a known exploit, but it is designed to prevent a future bug or supply-chain issue where the firmware might accidentally use a non-hardware (and therefore weaker) source of randomness.
No immediate user action required. Treat as a hardening improvement. Review whether a similar boot-time RNG linkage self-test exists for other COLDCARD variants (e.g., Q) and consider extending it if absent. Monitor future releases for any reported boot failures attributed to this self-test.
Security signals we found
Boot-time self-test of critical RNG linkage
Fatal halt on RNG linkage failure
Counter state intentionally not exposed to higher layers
Warm-up samples discarded and not used as entropy
Comment explicitly frames change as preventing 'non-hardware implementation' linkage
Evidence from the diff
The patch introduces rng_selftest(), called from ckcc_early_init() before the MicroPython VM is initialized. It enables the STM32 RNG peripheral, waits for RNG_SR_DRDY, arms a temporary counter, calls rng_get() eight times, then verifies the counter incremented exactly eight times. This proves the rng_get() symbol used by libngu’s CHIP_TRNG_32() and other entropy consumers resolves to the hardware-backed implementation in rng.c rather than a stub or alternate implementation. Failure calls __fatal_error(), causing a boot-time halt. The counter and self-test state are static, not exposed to Python or USB.
Changed components
stm32/COLDCARD_MK4/rng.cstm32/COLDCARD_MK4/rng.hstm32/COLDCARD_MK4/modckcc.cCOLDCARD MK4 boot/init pathInspect captured patch +64 / −0
### releases/Next-ChangeLog.md
@@ -26,6 +26,7 @@ This lists the new changes that have not yet been published in a normal release.
- Change: When a BIP-39 passphrase is active, View Seed Words now shows only the effective extended private key instead of the underlying seed words.
- Change: Backup System, Clone Coldcard, and Key Teleport’s Full COLDCARD Backup now capture the wallet secret currently in effect, including temporary seeds and BIP-39 passphrase wallets, and warn before export.
- Bugfix: View Seed Words and backup workflows incorrectly treated the master seed as the parent of every BIP-39 passphrase wallet. When a passphrase was applied to a temporary seed, they could not access that immediate parent seed.
+- Enhancement: RNG self-test proving rng_get() enter the hardware read path. Brick device otherwise.
# Mk Specific Changes
### stm32/COLDCARD_MK4/modckcc.c
@@ -283,6 +283,10 @@ void ckcc_early_init(void)
{
// Add system-wide init code here.
+ // Prove rng_get() is wired to the hardware TRNG before anything can
+ // consume entropy; hard-faults (no boot) otherwise.
+ rng_selftest();
+
// Disable ^C to interrupt code... but see mp_hal_set_interrupt_char()
// for best disable code.
mp_interrupt_char = -1;
### stm32/COLDCARD_MK4/rng.c
@@ -48,6 +48,8 @@ static void rng_init(void) {
if (!(RNG->CR & RNG_CR_RNGEN)) {
__HAL_RCC_RNG_CLK_ENABLE();
RNG->CR |= RNG_CR_RNGEN;
+
+ // first samples after enable are discarded by rng_selftest() at boot
}
}
@@ -64,6 +66,11 @@ static void rng_init(void) {
static uint32_t last_value;
+// Counting is armed only by rng_selftest() for a few words at boot, then
+// disabled permanently. Not exposed anywhere (no Python, no USB).
+static bool rng_count_active;
+static uint32_t rng_count;
+
// Recover from a seed error.
static void rng_recover(void)
{
@@ -138,6 +145,9 @@ static uint32_t rng_get_or_fault(void)
if (rng_try_once(&value)) {
last_value = value;
+
+ if (rng_count_active) rng_count++;
+
return last_value;
}
@@ -156,6 +166,49 @@ uint32_t rng_get(void)
return rng_get_or_fault();
}
+// rng_selftest()
+//
+// Boot-time proof that rng_get() -- the exact symbol libngu's
+// CHIP_TRNG_32() and the rest of the firmware's entropy consumers link
+// against -- enters the hardware read path above. Peripheral health
+// itself (seed/clock error flags, recovery, zero-word rejection) is
+// enforced per-call by rng_get_or_fault(); here we only check linkage.
+// Runs before the Python VM exists, so failure is fatal.
+//
+extern void __fatal_error(const char *msg); // NORETURN, ports/stm32/main.c
+
+void rng_selftest(void)
+{
+ rng_init();
+
+ // Wait at register level for the first word: proves entropy is flowing,
+ // and guarantees the rng_get() calls below find DRDY set and can never
+ // reach their mp_raise() timeout path (no VM/nlr handler installed
+ // yet, so an exception here would be an uncontrolled crash).
+ uint32_t start = HAL_GetTick();
+ while(!(RNG->SR & RNG_SR_DRDY)) {
+ if(HAL_GetTick() - start >= RNG_TIMEOUT_MS) {
+ __fatal_error("rng: no entropy");
+ }
+ }
+
+ rng_count = 0;
+ rng_count_active = true;
+
+ // discard these warm-up words; not exposed anywhere
+ for(int i = 0; i < 8; i++) {
+ (void)rng_get();
+ }
+
+ rng_count_active = false;
+
+ if(rng_count != 8) {
+ // rng_get() did not pass through the hardware read path: it must
+ // have resolved to a non-hardware implementation
+ __fatal_error("rng: bad linkage");
+ }
+}
+
/// \function pyb_rng_get()
//
/// Return a 30-bit hardware generated random number: or fail!
### stm32/COLDCARD_MK4/rng.h
@@ -3,7 +3,13 @@
*/
#pragma once
+#include <stdint.h>
+
uint32_t rng_get(void);
+// Boot-time self-test: hard-faults unless rng_get() verifiably passes
+// through the hardware read path.
+void rng_selftest(void);
+
MP_DECLARE_CONST_FUN_OBJ_0(pyb_rng_get_obj);
MP_DECLARE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj);Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.