06
The bug
A preprocessor guard checked whether the RNG macro existed, not whether it was enabled. The build passed and used a predictable fallback.
The faulty guard
// board config: macro defined, as zero
#define MICROPY_HW_ENABLE_RNG (0)
// faulty guard
#ifndef MICROPY_HW_ENABLE_RNG
#error "get a HW TRNG plz"
#endif
// correct check
#if !MICROPY_HW_ENABLE_RNG
#error "get a HW TRNG plz"
#endifThe guard passed, so rng_get() resolved to the Yasmarang software fallback rather than the hardware RNG. Both carry the same signature, so nothing at the call site distinguished them. Its seed inputs were not secret.
Seed search space
Bit figures follow Block's derivation, which assumes the device UID is known.
Intended — BIP-39 minimum128 bits
12-word floor; COLDCARD's default 24-word seed is 256 bits
Mk4 / Q / Mk5 — UID known, timers unknown73.3 bits
Upper bound over unknown timer states
Mk3 — UID known, timers unknown40.7 bits
Upper bound over unknown timer states
Mk4 / Q / Mk5 — reseed succeeded32 bits
Only 4 bytes reach reseed()
Mk3 — only SysTick unknown16.3 bits
UID and RTC known; about 80,000 reachable SysTick states
Mk3 — UID and timers both known0 bits
The same inputs reproduce the same seed
- 1Enumerate states
- 2Derive seeds
- 3Derive addresses
- 4Check balances