This page lists everything the emulator models, everything it assumes, and everything it deliberately leaves out. The main page keeps the Coldcard centered, with firmware provenance on the left and live RNG registers and call history on the right.
An interactive, source-reflective walkthrough of Coldcard Mk3 firmware
v4.1.9, pinned to the 2023-06-26T1241-v4.1.9 tag. It reproduces the
source menu order and predicates, four-row navigation, story scrolling, boot/PIN
and wallet-state branches, settings choices, redacted new-wallet and backup
quizzes, and source-anchored RNG call sites. It is an audit and teaching tool,
not a wallet-recovery utility.
There is no BIP39 word conversion, no private-key derivation, no address generation, no target matching and no batch scanning. Those are omitted on purpose and will not be added. The emulator will never ask you for a mnemonic or a passphrase, and you should never type a real one into any website.
When the STM32 hardware RNG is unavailable, the affected firmware falls back to a small software generator called Yasmarang. That generator is seeded once per MCU reset from three values that are far more predictable than they look: the low 32 bits of the chip UID, the current SysTick counter value, and two RTC registers. Everything drawn after that point is a deterministic function of those inputs plus the exact sequence of RNG calls the firmware happened to make beforehand.
On the first fallback call after a reset, the generator state is set from
pad = UID_low32 XOR SysTick, n = RTC_TR,
d = RTC_SSR and dat = 0. The capture is lazy: the
values are read when the first fallback call happens, not at boot.
Word 0 of the STM32 UID contains the wafer X and Y coordinates of the die. This lab maps X to the low half of the word and Y to the high half, then assumes each coordinate ranges uniformly over 0…255. That is a research population model. It is not a measured dataset of sold Coldcards, and it is not Coinkite's broader preliminary estimate of roughly 40 bits.
The lab defaults to RTC_TR = RTC_SSR = 0. This is an explicit
research assumption about the power-on state of those registers, not a fact
established by reading the firmware. You can override both registers from the
right-hand register rail. These are counterfactual lab controls, not
live writes to a physical Mk3: the existing modeled call sequence is replayed
from the newly selected initializer and any stale displayed output is cleared.
Both generator instances use the same state machine. One step is:
sum = pad + dat + (d * n) pad = rotl32(sum, 3) n = pad | 2 d = d XOR rotr32(pad, 1) dat = (dat XOR (pad & 0xff) XOR ((d >> 8) & 0xff) XOR 1) & 0xff out = pad XOR (d << 5) XOR (pad >> 18) XOR (dat << 1)
There are two independent Yasmarang instances. The fallback instance
stands in for the hardware RNG and is seeded as described above. The
public mixer instance lives in the ngu.random module and
always starts from the same fixed constants: pad = 0x0A8CE26F,
n = 69, d = 233, dat = 0. Because the
public mixer starts identically on every device, it adds no unpredictability at
all — it only permutes what the fallback generator produced. The lab counts the
two call streams separately, shown as F and P.
Bytes are produced four at a time. Each word takes one fallback output and
one public-mixer output and XORs them, then packs the result little-endian.
Before mixing, the raw fallback word is compared against the previous raw
fallback word within the same call; if they are equal, the call raises
EFAULT. That duplicate check is scoped per call, so it resets every time
bytes() is invoked again. EFAULT aborts that call, but it does not
permanently poison either C generator; a later separate call resumes from the
state already advanced by the failed attempt.
Some firmware paths call the lower-level Coldcard RNG buffer function
directly. Those calls advance the fallback stream without advancing the
ngu.random public mixer. The encrypted-backup password, 7z salt and
IV, user secrets, internal-flash overwrite and MicroSD format paths use this
interface. Its consecutive-word fault check crosses call boundaries through the
C last_value static; the lab tracks that state separately.
One fallback word and one public word are XORed, then masked. If the masked
value is at or above max, the code draws only from the
public mixer again and XORs that in, repeating until the value falls in range.
This is why the two call counters drift apart: rejection sampling advances the
public mixer without advancing the fallback generator.
A Fisher–Yates shuffle over a list of length items performs
length − 1 calls to uniform() with descending maxima.
Most of the firmware's incidental RNG consumption is shuffles.
Replaces only the public mixer's pad. It leaves the public
n, d and dat alone and does not touch the
fallback generator at all. Production v4.1.9 has no call site for it; the lab
exposes it because it is part of the disclosed API surface.
"Call history" is shorthand for the unobserved sequence of RNG calls that advanced the generator before the seed was drawn. The Mk3 does not store or authenticate such a log. Each item below is a real v4.1.9 call site and lists what it costs in fallback words.
Fisher–Yates shuffle of the 32 settings slots in flash.
Same shuffle, but the currently occupied slot is excluded, so 31 items.
Two independent shuffles of ten keys, one per PIN half.
A 32-byte secp256k1 candidate. If the scalar is zero or above the curve order, the code retries exactly once and then fails.
An ephemeral secp256k1 candidate with the same single-retry rule.
A shuffle of 32 slots followed by 48 separate random.bytes(256)
calls. This is by far the largest single consumer.
One uniform(2048) followed by one uniform(1000).
One direct ckcc.rng_bytes(32) call creates the entropy for the
12-word backup password.
The 7z builder makes two separate direct calls:
ckcc.rng_bytes(16) for the salt and another 16 bytes for the IV.
The limited four-question quiz shuffles 11 candidate positions, keeps three
plus the final word, then shuffles those four. Each displayed question normally
costs four more fallback calls: uniform(12),
uniform(2048), and shuffle(3).
The source overwrites 1,024 blocks, each using a direct
ckcc.rng_bytes(512) call, before formatting the card. The lab models
the calls but never touches a card or local file.
A single uniform(5).
One random.bytes(15), which rounds up to four words.
A single uniform(100).
One random.bytes(32).
Each fresh scan window randomizes the order in which the four keypad rows
are driven, using shuffle(4). In the emulator every key press
charges one scan window. On real hardware, presses close together in time can
land inside one still-open window and share a single shuffle, so this is an
upper bound rather than an exact count.
Advances the public mixer and then the fallback generator once each and XORs them. No duplicate test is applied on this path.
Generating a 24-word wallet draws random.bytes(32), so eight
fallback words and eight public words. The firmware then asserts that the 32
bytes contain more than four distinct byte values and refuses the seed if they
do not. Both that assertion and the EFAULT duplicate check are modeled, and
either one will halt that generation attempt in the emulator. The v4.1.9
EmptyWallet menu invokes this operation directly; there is no extra “24 Words”
submenu. The lab then shows 24 redacted placeholders, performs the source's
24-position quiz-order shuffle, and walks all 24 redacted questions. It never
shows the raw 32 bytes—only a truncated audit fingerprint—and never performs
BIP39 conversion, key derivation or address generation.
For an ordinary question, the source calls uniform(24), then
uniform(2048), then shuffle(3), for a minimum of four
fallback calls. Either uniform loop repeats if it picks a duplicate word. Since
the lab deliberately does not materialize or expose the mnemonic, it models the
first-draw-distinct path and labels this boundary. A wrong answer or a detour to
view the words reshuffles the three choices and costs two more fallback calls.
After a passed or explicitly skipped quiz, v4.1.9 calls
set_seed_value() and then goto_top_menu(). Despite a
stale comment in seed.py saying “reboot,” this function contains no
machine.reset() call in the pinned tag. The RNG call history therefore
continues into NormalSystem. By contrast, destroying the seed really
does call machine.reset(), so that path clears the C RNG statics.
All C statics are initialized again. The fallback generator's
seeded flag becomes false and the public mixer returns to its
constants. UID, SysTick and RTC are captured lazily on the next fallback call.
"History zero" here means generator state, not an erased audit log — no log ever
existed.
Between the reset and the seed draw, the settings filler, PIN-map shuffles, keypad scans, paper wallets and USB sessions all advance the state. This is the history the attacker has to guess or enumerate.
The interpreter restarts without an MCU reset, so file-scope and function-static C generator state continues from wherever it was. A soft reset does not give you a fresh generator.
This lab-only control clears the visible trace while preserving the modeled C state and total call counters. It is not a reset and it is not an operation a physical Mk3 can perform: the device never stored an authenticated call-history log in the first place.
Under the model above — X and Y each uniform over 0…255, SysTick over 80,000 reachable states — the number of distinct reachable initial pads is 224. The lab counts this exactly rather than estimating it, by enumerating which 32-bit pad values the XOR can actually produce for contiguous coordinate ranges and SysTick values counted from zero.
224 is a support size: the count of distinct values the initial pad can take inside the stated model. It is not a measured entropy estimate for sold Coldcards, and it is not a claim about the real-world distribution of wafer coordinates or boot timing. Treat it as an upper bound on how much work a brute-force search over that model needs, under assumptions you can change from the emulator.
If you multiply both the single-device candidate count and the global candidate count by the same set of, say, 1,000 candidate histories, the coverage fraction between them is unchanged. Enumerating histories can reveal the target's real history if it happens to be in your search set, but it does not let a fixed UID reach initial pads it could not otherwise reach. These are candidate (pad, history) pairs, not measured unique outputs. A wrong-history trace that matched the same 256-bit seed would require an independent output collision, which is a different and much stronger claim.
It works on any device with any UID. It demonstrates present knowledge of the seed, not which device generated it and not who historically controlled the funds.
Showing a UID, timer value and history that reproduce a given entropy proves the stock algorithm could have produced it. Such a trace can be constructed after the disclosure, so on its own it establishes compatibility, not history.
Pre-dispute descriptors, xpubs, several derived addresses, purchase records, funding records and timestamped commitments. "Could have generated" is not "did generate."
Never accept a single address, a current signature, or possession of a physical device as proof of seed origin. Verify a complete descriptor together with contemporaneous evidence that predates the compromise.
Exact and checked against an independent C reference: the initializer, both Yasmarang state machines, the call ordering, the per-call duplicate check, little-endian byte packing, rejection sampling, the keypair retry rule, the seed byte-diversity assertion and the final SHA-256.
Explicit assumptions you can change: the RTC-zero power-on state, the uniform 256 × 256 wafer-coordinate population, and the 80,000 reachable SysTick states.
Source-exact within the pinned Mk3 profile: the static menu labels and order, Mk3 predicates such as ATECC608 countdown PIN and FAT-RAM user management, the four-row menu rules, chooser values, 17-character story wrapping, five-row story scrolling, and the modeled RNG primitive semantics.
Source-reflective but deliberately substituted: the visible demo-only PIN and prefix confirmation in place of hardware-backed anti-phishing words, redacted seed/backup word screens, compressed dramatic pause timing, and state transitions that would normally cross the bootloader or secure element. Hardware-only USB, MicroSD, secure-element, bootloader, signing, key derivation, address generation and destructive operations are described and never executed. This is not QEMU or a cycle-accurate STM32 emulator.
5 moves up and 8 moves down, matching the Coldcard
convention. 7 and 9 page up and down, and
0 returns to the top of the current menu.
OK selects, X goes back. On a numeric entry screen the
digit keys type, X deletes a digit and OK saves. Your
physical arrow keys, Enter, Escape and number keys
work too. Every key press charges one membrane scan window against the RNG,
which is why the call trace grows as you navigate.
Independent educational research interface. Not affiliated with Coinkite. Never enter a real mnemonic or passphrase into this or any other website.