Change: use combined-source RNG for security-sensitive values
What changed, and why it matters
This commit changes how the COLDCARD hardware wallet generates random numbers used to protect backups, encrypted files, and two-factor authentication secrets. Previously these sensitive values were produced using only the STM32 chip's built-in random number generator (TRNG). Now they are produced through a stronger combined method: a Hash-DRBG (a NIST-style deterministic random-bit generator) that is seeded from both secure elements and the TRNG. The change is defensive hardening rather than a fix for a known exploitable bug, but it reduces the risk that a weakness in the STM32 TRNG alone could weaken user secrets.
Treat this as a security-hardening change. Verify that ngu.random.bytes() is indeed implemented as a Hash-DRBG seeded from both secure elements and the STM32 TRNG, and that the DRBG is reseeded appropriately. Review remaining ckcc.rng_bytes() callers to ensure raw TRNG is only used for non-secret purposes as claimed. No immediate user action is required, but the change should be included in the next release notes.
Security signals we found
RNG source changed from single TRNG to combined-source Hash-DRBG for secrets
Backup password entropy source hardened
Encrypted backup salt/IV entropy source hardened
2FA secret entropy source hardened
Changelog explicitly frames change as security-relevant ('TRNG-seeded SHA-256 Hash-DRBG for backup passwords, encryption salt/IV, and 2FA secrets')
No removal of existing ckcc.rng_bytes() calls outside these paths in the diff
Evidence from the diff
The patch replaces direct calls to ckcc.rng_bytes() (the STM32 TRNG driver) with ngu.random.bytes(), which the changelog describes as a ‘TRNG-seeded SHA-256 Hash-DRBG’. Affected callers are: backup password generation in shared/backups.py, salt/IV generation in shared/compat7z.py’s urandom(), and 2FA secret generation in shared/users.py. The changelog also notes that master-seed generation already mixes both secure-element entropy with the TRNG. The change removes the direct dependency on ckcc for randomness in these files and centralizes randomness generation in ngu.random, improving the entropy source for security-sensitive material.
Changed components
shared/backups.pyshared/compat7z.pyshared/users.pyngu.random module (firmware randomness subsystem)Backup encryption password generation7z backup encryption salt/IV generationUser 2FA/HMAC secret generationInspect captured patch +7 / −10
### releases/Next-ChangeLog.md
@@ -4,6 +4,8 @@ This lists the new changes that have not yet been published in a normal release.
# Shared Improvements - Both Mk and Q
+- Change: Use TRNG-seeded SHA-256 Hash-DRBG for backup passwords,
+ encryption salt/IV, and 2FA secrets; use raw TRNG for non-secret uses only.
- Improvements to Entropy Generation:
- Master seed generation mixes entropy from both Secure Elements with
the STM32 TRNG (previously TRNG only).
### shared/backups.py
@@ -2,7 +2,7 @@
#
# backups.py - Save and restore backup data.
#
-import compat7z, stash, ckcc, chains, gc, sys, bip39, uos, ngu
+import compat7z, stash, chains, gc, sys, bip39, uos, ngu
from ubinascii import hexlify as b2a_hex
from ubinascii import unhexlify as a2b_hex
from utils import deserialize_secret, swab32, xfp2str
@@ -310,9 +310,8 @@ async def restore_from_dict(vals, raw):
async def pick_backup_password(write_sflash=False, secret_opt=False, what="money for free"):
# Pick a password: like bip39 but no checksum word
#
- b = bytearray(32)
while 1:
- ckcc.rng_bytes(b)
+ b = ngu.random.bytes(32)
# b2a_words(32 bytes) gives 24 BIP39 words. Keep the leading 12 by dropping the tail,
# which includes checksum bits; this is a wordlist password, not a valid BIP39 mnemonic.
# * keep pwd as a string for the encryption/settings paths
### shared/compat7z.py
@@ -6,7 +6,7 @@
# always does AES-256. Not really expecting to be able to read any 7z file, except
# those we created ourselves.
#
-import os, sys, ckcc, ngu
+import os, sys, ngu
from ubinascii import hexlify as b2a_hex
from ubinascii import unhexlify as a2b_hex
from ubinascii import crc32
@@ -19,9 +19,7 @@ def masked_crc(bits):
return crc32(bits) & 0xffffffff
def urandom(l):
- rv = bytearray(l)
- ckcc.rng_bytes(rv)
- return rv
+ return ngu.random.bytes(l)
def encode_utf_16_le(s):
# emulate: str.encode('utf-16-le')
### shared/users.py
@@ -177,9 +177,7 @@ def delete(cls, username):
def pick_secret(cls, auth_mode):
# always 10 bytes for no reason => 80 bits of entropy
# return binary secret, and encoded value for new user to see
- import ckcc
- b = bytearray(10)
- ckcc.rng_bytes(b)
+ b = ngu.random.bytes(10)
picked = b32encode(b)
if auth_mode == USER_AUTH_HMAC: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.