Mode A: dice only
How many rolls
A fair six-sided die yields log2(6) bits per roll:
log2(6) = 2.584962500721156 bits
To reach a full 256 bits of entropy you need:
256 / 2.584962500721156 = 99.033... -> 100 rolls
nullroute requires 100 rolls and refuses to proceed with fewer.
Note that 99 rolls gives 255.911 bits, which is short of 256. Some documentation (including an earlier draft of this project's own brief) states that 99 rolls is sufficient. It is not, by 0.089 bits. The shortfall is small enough to be academically irrelevant and large enough that a project whose whole claim is checkable arithmetic should not round in its own favour. The device counts to 100.
The running total shown on screen during collection is floor(n * log2(6)),
displayed as "213 of 256 bits" and similar. It is truncated rather than rounded,
so the display never claims more entropy than has been collected.
The canonical encoding
This is the part that must be exact, because it is the part you reproduce by hand.
- Each roll is recorded as a single ASCII digit in
1-6(bytes0x31through0x36). - Rolls are concatenated in the order they were entered, with no separators, no whitespace, no line breaks, and no trailing newline.
- The result is exactly
Nbytes forNrolls. - The entropy is
SHA-256of those bytes, which is exactly 32 bytes.
entropy = SHA-256( ascii(roll_1) || ascii(roll_2) || ... || ascii(roll_100) )
- Those 32 bytes are the BIP-39 entropy for a 24-word mnemonic, converted per
BIP-39 (append the first 8 bits of
SHA-256(entropy)as a checksum, then split the 264 bits into 24 groups of 11 and index the wordlist).
The trailing newline matters. echo appends one and printf does not. If your
hand check disagrees with the device, this is the first thing to look at.
Why SHA-256 of the ASCII, and not a base-6 integer
There are two defensible ways to turn dice into bytes:
- Treat the rolls as a base-6 integer and convert that integer to binary.
- Hash the canonical text of the rolls.
nullroute hashes the text. Both are sound, but only the hash is reproducible
with a tool that is already on every machine. sha256sum ships with coreutils
on Linux, shasum -a 256 ships with macOS, and both agree. A base-6 to binary
conversion of a 100-digit number requires you to trust some other piece of
software to do bignum arithmetic, which reintroduces exactly the problem this
design exists to remove.
Hashing costs nothing in entropy terms. With 100 rolls the input carries 258.5 bits, the output is 256 bits wide, and SHA-256 is modelled as a random oracle here, so the output is full strength. Hashing 258.5 bits into 256 loses nothing that matters.
The same rule, SHA-256 of the ASCII roll string, is what Coldcard, SeedSigner and Krux use when a seed comes from dice alone, so a user can cross-check nullroute against an unrelated implementation. Two differences matter. They accept 99 rolls, so a 99-roll seed from any of them cannot be reproduced here, where 100 is the minimum; and SeedSigner takes exactly 99, so a 100-roll seed from nullroute cannot be re-entered there. Coldcard's other path, dice mixed with its own entropy, hashes differently and is not reproducible here at all.
Worked example
Do not use this seed. It is a public demonstration sequence and the funds sent to it will be swept immediately. It exists so you can confirm your copy of nullroute computes what it claims to compute.
The 100 rolls, 123456 repeated:
1234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234
Check the length first:
$ printf '%s' '1234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234' | wc -c
100
Now the entropy:
$ printf '%s' '1234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234' | sha256sum
e56403e8522ddeae1b44a1e8148b1ba4d3b4c626ccf20980056eedcc7e0c0f35 -
On macOS, use shasum -a 256 instead of sha256sum. The output is identical.
That 32-byte value is the BIP-39 entropy. It produces this 24-word mnemonic:
1. tornado 7. home 13. deputy 19. forum
2. cactus 8. neither 14. glide 20. swear
3. wheel 9. trend 15. open 21. side
4. picture 10. picture 16. oxygen 22. alcohol
5. target 11. shoulder 17. another 23. devote
6. finish 12. endless 18. ability 24. random
And, with an empty passphrase, this BIP-39 seed:
e50a429924dbdf4500fa0cf03cca5b1a5dab1524cf1cd0efe1868eee17837be0
2e061f3eaae7e08aaae894eb0dd14b6f04f793826f246d9b4960aded98ab8011
(shown on two lines for width, it is one 64-byte value)
If your device shows a different hex value for the same 100 rolls, stop and do not use it. Either the device is not running the code it claims to be running, or you entered a roll wrongly. Check the manifest root hash on the lock screen against the one you compute from the source before you do anything else. No release hash has been published yet.
You can confirm the mnemonic step against any offline BIP-39 tool. The entropy to mnemonic conversion is plain BIP-39 with no nullroute-specific behaviour, and that is deliberate: see INV-INTEROP-1 in the threat model. Nothing about how the seed was generated is needed to recover it.
Pattern warnings
The device warns, loudly and without blocking you, when the roll sequence looks non-random:
- every roll the same value
- a short repeating cycle (the worked example above trips this)
- a strict ascending or descending run
- a chi-squared test on the digit distribution that falls outside a wide acceptance band
These are warnings, not rejections. A fair die genuinely can produce a suspicious-looking sequence, and a device that silently discarded real rolls would be substituting its own judgement for the user's entropy, which is the failure mode this whole document exists to prevent. The user is told what looks wrong and decides.
What the device does not know
nullroute cannot tell whether your dice are fair, whether you rolled them properly, or whether someone watched you do it. It only guarantees that the seed follows from the rolls by a rule you can check.
Roll on a hard surface, use casino-grade dice if you care, and do not do it in front of a camera.
Rendered from docs/ENTROPY.md, the same bytes that ship with the code. SHA-256 ce8ad87ef3fbce5b6ff615296b3ec081a840415ae6bc089bff748df3dc6a348d