I reviewed the entropy path after the Coldcard Mk3 advisory (July 2026) to check whether Specter shares that failure class. It does not — and the design is notably more robust:
boards/STM32F469DISC/mpconfigboard.h sets MICROPY_HW_ENABLE_RNG (1), so the Yasmarang software fallback in ports/stm32/rng.c is compiled out entirely.
- There is no second
rng_get() implementation competing for the same symbol, which was the root cause on Coldcard.
- If the macro were ever misconfigured,
os.urandom would not exist at all (moduos.c), so src/rng.py would fail loudly instead of silently degrading.
- Touchscreen entropy is fed automatically on every
PRESSING event via the decorators in src/gui/decorators.py, not as an opt-in step.
While reading, I noticed two related gaps that I think are worth closing.
1. rng_get() returns 0 on timeout
In f469-disco/micropython/ports/stm32/rng.c:
while (!(RNG->SR & RNG_SR_DRDY)) {
if (HAL_GetTick() - start >= RNG_TIMEOUT_MS) {
return 0;
}
}
If the RNG peripheral stops responding, rng_get() returns zero without signalling an error. os_urandom() then fills its buffer with those zeros, and src/rng.py receives them as if they were entropy.
This is upstream MicroPython behaviour rather than Specter's code, but it is on the seed-generation path.
2. No sanity check in get_random_bytes()
In src/rng.py, the output of get_trng_bytes() is used and fed into the pool without any check:
def get_random_bytes(nbytes):
global entropy_pool
d = get_trng_bytes(nbytes)
feed(d) # why not?
...
Combined with (1), a dead or stalled peripheral is indistinguishable from a working one.
Impact
Low, and I want to be clear about that. The entropy pool mixes in touchscreen timing (time.ticks_cpu() at 180 MHz), so a failed TRNG would not make seeds predictable — it would silently reduce them to whatever the touch entropy contributed. That is defence in depth working as intended.
But it is a side effect rather than a guaranteed property. entropy_pool starts from the constant b"7" * 64, so the residual strength depends entirely on how many touch events happened to be accumulated beforehand — which is not measured anywhere.
Suggestion
Something minimal in get_random_bytes() would close both gaps, e.g. rejecting all-zero or single-repeated-byte TRNG output and surfacing a visible error rather than continuing.
A second, optional idea: track how many entropy contributions the pool has received and require a minimum before allowing seed generation — perhaps by prompting the user to draw on the screen if the count is low.
Happy to open a PR if the maintainers agree on the approach. Thanks for the careful design here; the layering is what makes this a minor issue rather than a serious one.
I reviewed the entropy path after the Coldcard Mk3 advisory (July 2026) to check whether Specter shares that failure class. It does not — and the design is notably more robust:
boards/STM32F469DISC/mpconfigboard.hsetsMICROPY_HW_ENABLE_RNG (1), so the Yasmarang software fallback inports/stm32/rng.cis compiled out entirely.rng_get()implementation competing for the same symbol, which was the root cause on Coldcard.os.urandomwould not exist at all (moduos.c), sosrc/rng.pywould fail loudly instead of silently degrading.PRESSINGevent via the decorators insrc/gui/decorators.py, not as an opt-in step.While reading, I noticed two related gaps that I think are worth closing.
1.
rng_get()returns 0 on timeoutIn
f469-disco/micropython/ports/stm32/rng.c:If the RNG peripheral stops responding,
rng_get()returns zero without signalling an error.os_urandom()then fills its buffer with those zeros, andsrc/rng.pyreceives them as if they were entropy.This is upstream MicroPython behaviour rather than Specter's code, but it is on the seed-generation path.
2. No sanity check in
get_random_bytes()In
src/rng.py, the output ofget_trng_bytes()is used and fed into the pool without any check:Combined with (1), a dead or stalled peripheral is indistinguishable from a working one.
Impact
Low, and I want to be clear about that. The entropy pool mixes in touchscreen timing (
time.ticks_cpu()at 180 MHz), so a failed TRNG would not make seeds predictable — it would silently reduce them to whatever the touch entropy contributed. That is defence in depth working as intended.But it is a side effect rather than a guaranteed property.
entropy_poolstarts from the constantb"7" * 64, so the residual strength depends entirely on how many touch events happened to be accumulated beforehand — which is not measured anywhere.Suggestion
Something minimal in
get_random_bytes()would close both gaps, e.g. rejecting all-zero or single-repeated-byte TRNG output and surfacing a visible error rather than continuing.A second, optional idea: track how many entropy contributions the pool has received and require a minimum before allowing seed generation — perhaps by prompting the user to draw on the screen if the count is low.
Happy to open a PR if the maintainers agree on the approach. Thanks for the careful design here; the layering is what makes this a minor issue rather than a serious one.