OSC-2026-0013 · github.com/Coldcard/firmware
critical inconclusive grok-4.5 2026-08-04 signed: nostr
- Repository
- github.com/Coldcard/firmware
- Commit
- bcc2c382a324690a2fcf972c0bac3b79bf923f7b
- Scope path
- stm32/COLDCARD/
- Model
- grok-4.5
- Harness
- grok-build
- Prompts
- prompts/entropy-rng-audit-v1.md
- Independent runs
- 1
- Transcript sha256
- 9487be785e55cbc3d0da0ec8f70001e7e59c30b4f17a2d18d31d6bb3c7a8100a
- Auditor
- TheIcarusWings (theicaruswings)
- Signature
- nostr-schnorr · npub19tzp8lf3klmqj3dz9mz0qnuvjp7uyy9993gmljmyaxs8phztj7wsnujvq5
Scope. Independent Grok re-audit of the publicly disclosed Coldcard seed-entropy vulnerability at the last vulnerable commit (bcc2c382). In scope: entropy/RNG paths feeding BIP-39 seed generation on STM32 builds; TRNG-vs-software-PRNG selection; build-time RNG symbol linkage; SE reseed path on Mk4/Q. NOT in scope: PIN/secure-element auth logic, PSBT handling, USB stack. Cross-model counterpart to Claude run OSC-2026-0001 on the same commit/pack.
Findings
critical OSC-2026-0013-F1 unreviewed
BIP-39 seed generation draws from a software PRNG (MicroPython Yasmarang) instead of the STM32 hardware TRNG. generate_seed() in shared/seed.py:602-609 calls ngu.random.bytes(32) (comment claims TRNG) then sha256d, which cannot expand entropy. Production boards define MICROPY_HW_ENABLE_RNG (0) (stm32/COLDCARD/mpconfigboard.h:77). Board stm32/COLDCARD/rng.c implements a correct HW TRNG reader as static rng_get_or_fault() feeding random_buffer/ckcc.rng_bytes, but pre-fix does not export a global C symbol rng_get(). With MICROPY_HW_ENABLE_RNG==0, MicroPython ports/stm32/rng.c still provides rng_get() as pyb_rng_yasmarang seeded from chip UID and timers. libngu's CHIP_TRNG_32 maps to external rng_get(); seed generation therefore never samples the STM32 RNG peripheral. Matches the public root-cause of the 2021–2026 Coldcard RNG incident. Post-fix (c849c4e0 tree) exports rng_get, poisons yasmarang, and adds build-time nm checks.
high OSC-2026-0013-F2 unreviewed
On Mk4/Q paths, boot rng_seeding() mixes secure-element RNG bytes then keeps only four bytes via ustruct.unpack('I', n[0:4]) before ngu.random.reseed() (shared/mk4.py:39-49). Combined with F1, residual SE entropy injected into the software PRNG stream used by generate_seed is at most 32 bits. Mk2/Mk3 lack this reseed path entirely. Historical pre-fix firmware only — not a claim about post-fix current releases.