Dice-generated and dice-mixed seeds
Pure-dice generation bypasses the affected generator. Mixed dice include its output as a prefix, then add the roll string through SHA-256.
No code. Written to be actionable.
COLDCARD supports two distinct dice workflows. Their security properties are not identical, so the number and quality of rolls matter alongside which workflow was used.
flowchart TB
subgraph PURE["Pure dice: New Seed Words, Advanced, Dice Roll"]
A1["empty starting value"] --> A2["SHA-256 over roll symbols"]
A2 --> A3["12- or 24-word result"]
end
subgraph MIX["Mixed dice: add rolls on the word screen"]
B1["device-generated seed prefix"] --> B2["SHA-256 over prefix and roll symbols"]
B2 --> B3["updated seed"]
end
The pure path does not call the affected generator. The mixed path does, but unpredictable rolls can add a separate source of uncertainty.
The code starts with an empty byte string and hashes the roll symbols.
The affected ngu.random output is absent from this path.
The result is deterministic from the roll string, so it can be reproduced offline with an independently reviewed implementation. The roll string is equivalent to secret seed material and must not be entered into a website or connected computer.
The code hashes the existing device-generated seed followed by the roll symbols. If an attacker can reproduce the prefix, the remaining search still includes the unpredictable roll sequence.
This path does not enforce the pure-dice roll thresholds, so its strength depends on the actual number, fairness, independence and secrecy of the rolls. Coinkite's advisory nonetheless applies the same 50-roll exception here, on those conditions, and the full digest becomes the seed.
Roll counts and output size
| Rolls | Roll entropy | Relevant output |
|---|---|---|
| 50 | about 129.25 bits | The enforced minimum for a standard 12-word pure-dice seed in the firmware state checked on 1 August 2026. The BIP39 entropy portion is 128 bits, so the output caps the result at 128 bits. |
| 99 | about 255.91 bits | The enforced minimum for a standard 24-word pure-dice seed in the firmware state checked on 1 August 2026, just below the 256-bit output size. |
Warning and enforcement changed by workflow
| Workflow | Behaviour |
|---|---|
| v4.0.0 pure dice, 24 words | Warned below 99 rolls, but allowed the user to confirm and continue. |
| Standard pure-dice seed, state checked 1 Aug 2026 | new_from_dice() enforces 50 rolls for 12 words and 99 for 24 words. |
| Ephemeral pure-dice seed, state checked 1 Aug 2026 | Warns below the same thresholds but still allows an override because it does not set enforce=True. |
| Dice mixed on the word screen | Does not enforce the pure-dice thresholds. Security depends on the number and quality of rolls actually entered. |
The in-browser estimator can model pure dice as a replacement for the affected device path, or mixed dice as an independent term capped by the mnemonic output size.
The vendor's dice exception, and how it maps to the code
The Coinkite Mk3 advisory now states the mixed-dice construction and the roll threshold itself, under the heading "If You Added Dice When Creating the Seed". In the capture held from 1 August 2026, the page marked as updated at 9:35 a.m. EDT that day, it says that on affected firmware COLDCARD "hashed the device-generated seed together with every dice roll entered through Add Dice Rolls", that 50 to 98 independent private rolls contributed at least 128 bits and 99 or more contributed approximately 256 bits, and that where at least 50 fair and independent rolls were entered and "the rolls were not recorded or exposed", it does not consider the resulting seed at risk from this RNG issue alone. It attaches two limits: the exception applies to the final seed words shown after the dice were added, and anyone uncertain about which words they used, how many rolls they entered or whether the rolls were private is told to migrate.
The firmware matches that description. add_dice_rolls() builds
a SHA-256 context from the seed it is given, updates it with each roll
symbol as the symbol is pressed, and assigns the complete 32-byte digest
back as the seed. Pure dice pass an empty starting value into the same
function, so the two workflows differ only in the prefix. Nothing on this
path is truncated. The vendor's statement about mixed dice is therefore
supported by a construction this archive has read in the source, at the
affected v4.0.0 release commit and at commit 9a88e1a5, rather
than resting on the advisory alone. The separate roll-count arithmetic still
depends on the output size: 50 rolls fill the 128-bit entropy input of a
12-word mnemonic, and a 24-word result needs 99.
The 50-roll figure reached the public before the vendor published it. Antoine Poinsot advised users that the dice-roll exception required at least 50 rolls, and Praveen Perera gave the same threshold while recommending migration after an independent recovery attempt, both on 31 July 2026. The dice section is absent from the earliest held state of the advisory, timestamped 01:56 UTC on 31 July and recovered from the Internet Archive, and present in every held state from 07:30 UTC that day onward. Those posts are retained as the earlier signal in time. They are no longer the basis for the rule, which the vendor has since stated in its own words.
A stricter 99-roll public rule
James O'Beirne recommended migration for coins spendable by one affected COLDCARD key, or by an affected-key quorum, when the device had been initialised with fewer than 99 dice rolls. He added that a strong passphrase could mitigate the risk while warning that most users cannot reliably judge passphrase strength.
That is a stricter incident-response rule than the public 50-roll exception. In the firmware, 99 rolls is the pure-dice threshold for a 24-word output, while 50 is the enforced threshold for a 12-word output. O'Beirne's post does not distinguish pure-dice generation from dice mixed into a device-generated seed. It is therefore retained as attributed advice, not as a replacement for the workflow-specific calculation above.
Why the 32-bit reseed truncation does not truncate the dice
add_dice_rolls() creates a SHA-256 context from the existing
seed, updates it with each roll symbol and returns the digest. It does not
call ngu.random.reseed(). The separate 32-bit truncation in
shared/mk4.py therefore does not apply to the roll string.
Pure dice starts from an empty value. Mixed dice starts from the already
generated seed. Both statements are verified in the affected
implementation and in commit 9a88e1a5c32c69ba7ace7f22c26da90f8442b745,
checked on 1 August 2026.
This is what allows the vendor's dice exception to reach the mixed workflow at all. If the roll string were folded through the truncating reseed rather than through this digest, added rolls could not carry the 128 or 256 bits the advisory credits them with.