Generate a BIP39 mnemonic from physical dice rolls. No random number generator is involved anywhere: the 256 bits behind a 24-word seed phrase are exactly the bits that came off the die.
cargo run --release
Throw a six-sided die and read each throw like this:
1 -> 00 2 -> 01 3 -> 10 4 -> 11 5, 6 -> discard, throw again
Faces 5 and 6 are thrown away rather than folded in. That is the whole trick.
Each face has probability 1/6, so conditioned on the throw being kept, each of
the four outcomes has probability (1/6) / (4/6) = 1/4 exactly — the two bits
are uniform and independent by construction.
128 kept throws is 256 bits. Acceptance is 2/3, so expect around 192 physical throws to get there.
A d6 carries log2(6) ≈ 2.585 bits, which is irrational, so no fixed number of
rolls lands on a whole number of bits. The common workaround treats the rolls as
a base-6 integer and runs SHA-256 over it to compress to 256 bits. That works,
but it moves the guarantee: you are now asserting that SHA-256 is a good
randomness extractor rather than proving your bits are uniform. Rejection
sampling needs no such assertion, and nothing is hashed, stretched, or whitened
anywhere between the die and the entropy.
The cost is throws. That is the only thing you trade.
Standard BIP39: append the first ENT/32 bits of SHA-256(entropy) as a
checksum, split into 11-bit groups, index the 2048-word English list.
256 + 8 = 264 bits = 24 words. The checksum catches transcription errors — it is
not a security property.
diceroll [OPTIONS]
-w, --words <N> 12, 15, 18, 21 or 24 [default: 24]
-e, --show-entropy Print the raw entropy in hex
-v, --verify Type the mnemonic back after writing it down
-r, --rolls <S> Take rolls from the command line (testing only)
-f, --rolls-file <PATH> Read rolls from a file
-h, --help
While entering rolls, type faces as you throw them — several at a time is fine,
spacing does not matter. u undoes the last throw, q gives up.
--rolls puts your entropy in shell history. It exists for testing and prints a
warning; do not use it for a real seed.
--rolls-file takes the same digits, from a file instead of the keyboard:
diceroll --rolls-file rolls.txt
Whitespace and newlines are ignored, so one throw per line or one long run both
work. # starts a comment to the end of the line, so the file can record which
die you used and when. Anything else — a stray letter, a 7 — is an error
rather than a skipped character, since silently dropping it would shorten the
entropy without saying so. Extra rolls past the target are dropped with a note;
too few is an error and no mnemonic is produced. Piping works too —
diceroll < rolls.txt — but the flag is clearer and warns about what the file
now contains.
See examples/rolls.txt for the format. The words that file produces are
public — it is a demonstration, not entropy.
That warning is the point: a roll file is your seed in plaintext, and unlike
the words on screen it persists after the process exits. It is worth having when
you want to roll away from the machine and transcribe once, or when the machine
has no comfortable keyboard. Keep it on tmpfs (/dev/shm, or a container's
in-memory mount), on a disk you are about to wipe, and delete it once the words
are on paper. Deleting does not scrub the blocks on an SSD.
The maths above assumes a fair die. A die that is shaved, loaded, or thrown the same way every time is biased no matter what the extractor does, so the raw throw stream gets two checks:
- a chi-square over the six faces, which catches a die favouring some faces;
- a repeat rate, which catches a stream with the right totals but the wrong ordering — the signature of rolls that were invented rather than thrown, since people badly under-produce consecutive repeats.
Both run at p = 0.001 and are advisory. Passing them is not proof of fairness, and a fair die still trips them about once in a thousand runs.
- Run this offline, on a machine you trust. Treat anything it prints as the key.
- Rolls and entropy are zeroized on drop; the screen is cleared on exit. Neither helps if the machine is compromised or the terminal is being logged.
- Write the words on paper, in order, and use
--verifyto confirm the copy before you rely on it. - Use a casino-grade or at least a sharp-edged die. Rounded-corner dice are the usual source of bias.
cargo test
Covers all 24 official BIP39 vectors in both directions, the uniformity of the
extractor, bit-packing, the audit checks, and the end-to-end path from a fixed
roll sequence to fixed words. tests/cli.rs drives the built binary over the
command-line surface: roll-file parsing, comments, and the refusal paths that
must never print a mnemonic — short input, junk input, unreadable files. The
embedded wordlist is integrity-checked against its published SHA-256 at first
use.