A macOS wizard for deriving EFF diceware passphrases and BIP-39 seeds from physical dice rolls. It converts entropy you already generated with dice; it is never an entropy source. Governing document: SECURITY_SPEC.md.
- Never accepts an existing seed or passphrase as input. There is no code path, in any mode, for any reason, that takes seed material in. This tool generates and verifies its own output only.
- Never touches the network. No network client, no server, no telemetry, no auto-update. The App Sandbox entitlements are empty except for the sandbox flag itself — the OS enforces this, and you can verify it (below).
- Never writes files. No preferences, no caches, no logs of rolls or words, no state restoration, no crash reporter payloads.
- Never copies, exports, QRs, or prints anything. No clipboard, no QR codes, no file export, no printing. Words appear on screen once, after an explicit reveal, and are meant to be transcribed to paper by hand.
Clones of seed tools are an active phishing pattern. The verification instructions below are the only durable defense — use them.
Scope of this verification: CastLots verifies the dedicated dice-only derivation in which the signer hashes the ASCII dice-roll sequence directly. On Coldcard, this corresponds to Import Existing → Dice Rolls. It does not test the normal New Wallet random-number generator or the separate Add Dice Rolls workflow, which mixes your rolls with an existing device-generated seed.
In July 2026, Coinkite disclosed that affected Coldcard firmware had generated some seeds with dangerously weakened internal entropy. Coinkite also confirmed that Coldcard's dedicated Import Existing → Dice Rolls path hashes the user's roll sequence directly and does not depend on the device's internal generator.
CastLots was created after that incident to reproduce and verify that deterministic dice-only result independently. It does not inspect or certify Coldcard's internal random-number generator or firmware.
- Coinkite security advisory
- Coinkite technical background
- Coldcard direct dice-only firmware implementation
If you roll dice into a hardware wallet, you are trusting that the device actually derives the seed from your entropy, by the algorithm it claims. A signer that quietly ignored some of your rolls, truncated them, or substituted its own randomness would produce a perfectly valid-looking mnemonic, and you would have no way to notice. The only way to check is to compute the same derivation independently and compare.
That is what CastLots is for. It is a second implementation you control.
Procedure — use throwaway rolls, never the rolls for a wallet you intend to fund:
- Roll dice and write the sequence down: at least 50 rolls for a 12-word seed, 99 for 24 words.
- On Coldcard, use the dedicated Import Existing → Dice Rolls flow. For
another signer, first confirm that its documentation specifies the same
direct derivation:
SHA256of the ASCII dice-roll sequence. Select the same word count, enter the exact sequence, and let the signer show you the resulting words. - Enter the exact same sequence into CastLots, in the same word count.
- Compare the two word lists. Every word, in order, must match.
If they match, the device produced the expected result for this throwaway
test using the documented direct derivation: SHA-256 over the ASCII roll
string, truncated to 16 or 32 bytes, with the BIP-39 checksum appended. This
empirically confirms compatibility for the tested word count and roll
sequence, including the 12-word truncation case. It does not prove that the
device will behave identically in every future run.
If they do not match, stop and investigate before putting funds behind that device.
Then throw the test seed away. Having verified the documented dice-only derivation with a throwaway sequence, generate your real seed on the signer with fresh rolls that never touch a computer. This provides evidence of algorithm compatibility without exposing the roll sequence or seed you actually use.
You can run the same comparison without installing the app at all, using
reference/reference.py (standard library only, no dependencies) — see
"Verifying the app" below.
A match proves the device computed the correct words from the entropy you gave it. It does not prove the device's own internal random number generator is sound, that its firmware is authentic, or that it has no other flaws — those need separate verification. It closes one specific, important hole: whether your dice actually became your seed.
A dedicated hardware signer remains the generator of record for real funds; this app is the independent cross-check implementation. On a Mac it is also a generator of last resort under the Offline Procedure in SECURITY_SPEC.md.
1. Entitlements audit. The built app must be sandboxed with empty entitlements (only the sandbox flag). Verify with:
codesign -d --entitlements :- /Applications/CastLots.appThe output must contain com.apple.security.app-sandbox set to true and
nothing else. Any other key — network client, file access, anything —
means the binary is not a build of this source. Do not use it.
2. Run the reference selftest. reference/reference.py is the derivation
spec; the app must match it bit-for-bit. From the repo root:
cd reference && python3 reference.py selftestIt must end with SELFTEST OK.
To derive from your own throwaway rolls with the reference alone (it reads stdin, so the rolls stay out of your shell history):
cd reference && python3 reference.py bip39 --words 242b. Cross-validate against Trezor's implementation. The derivation is
also checked against python-mnemonic, an independent third implementation,
over every committed vector and 500 random roll strings:
python3 -m venv .venv && .venv/bin/pip install mnemonic==0.21
.venv/bin/python scripts/crossvalidate_trezor.py --cases 500It must end with CROSSVALIDATE OK. This is a test-harness dependency only —
the app itself has zero third-party dependencies.
3. Device cross-check. From reference/test_vectors.json (notes): enter
the T3 rolls through Coldcard's dedicated dice-only flow—or another signer
documented to use the same direct SHA256-of-ASCII-rolls method—in 12-word
mode, and enter the T4/T5 rolls in 24-word mode. The words must match exactly.
This empirically confirms that the tested signer matches the documented direct
derivation, including the 12-word truncation. Every BIP-39 vector is also
cross-validated against python-mnemonic (the Trezor reference implementation).
4. Verify with throwaway rolls only. Never type real rolls into a shell
(--rolls is TEST ONLY — command lines leak into shell history and process
listings; the reference reads stdin by default for this reason). The vectors
in reference/ are public test data — never fund any of these seeds.
Both wordlists are compile-time pinned. The build fails if the files don't match these hashes, and the app re-verifies its embedded copies at every launch and refuses to run on mismatch.
| List | SHA-256 |
|---|---|
| BIP-39 English (2048 words) | 2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda |
| EFF large wordlist (7776 words) | addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e |
The EFF pin was confirmed on 2026-08-05 by downloading
https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt over TLS and
hashing it: the official file is byte-identical to reference/eff_large_wordlist.txt.
Building from source is the recommended path, and for a tool like this it is the better one. You compile code you can read, on your own machine, and never have to trust a binary someone else produced. It needs only Xcode and the macOS SDK — there are no dependencies to fetch.
git clone https://github.com/DigiMonk73/CastLots.gitcd CastLots && xcodebuild build -project app/CastLots.xcodeproj -scheme CastLots -configuration Release -derivedDataPath build/ddThe app lands in build/dd/Build/Products/Release/CastLots.app. Drag it to
Applications. A locally built app carries no quarantine flag and opens
normally. Run the test suite first if you want (-scheme CastLots-ReleaseTests
with xcodebuild test).
If you have only the Command Line Tools and no Xcode, scripts/build_app_clt.sh
produces the same app with swiftc.
Releases also carry a CastLots-v*.dmg that CI builds only after the full
Release test suite, the banned-API grep gate, and the entitlements audit all
pass. It is ad-hoc signed and not notarized by Apple. That has a real
consequence: macOS will refuse to open it on first launch, and getting past
that means approving it under System Settings → Privacy & Security → "Open
Anyway", or stripping the quarantine flag yourself.
Be honest with yourself about that step. Routinely bypassing Gatekeeper for downloaded software is a bad habit generally, and a particularly bad one around seed tools, because a trojaned clone of this app would ask you to do exactly the same thing. If that trade bothers you, build from source — that is precisely why the source path is listed first.
If you do use the DMG, verify it before opening it:
shasum -a 256 ~/Downloads/CastLots-*.dmgCompare against SHA256SUMS.txt on the release page. That file is signed
with the maintainer's SSH key, and the signature is verifiable against the
key GitHub publishes:
curl -s https://api.github.com/users/DigiMonk73/ssh_signing_keys | grep -o 'ssh-ed25519 [A-Za-z0-9+/=]*' | sed 's/^/DigiMonk73 /' > /tmp/castlots_allowed_signersssh-keygen -Y verify -f /tmp/castlots_allowed_signers -I DigiMonk73 -n file -s SHA256SUMS.txt.sig < SHA256SUMS.txtRelease tags are signed too — GitHub shows a "Verified" badge, and
git tag -v v1.0.5 checks it locally. None of this substitutes for Apple
notarization; it proves the artifact came from this maintainer, not that
Apple scanned it.
SECURITY_SPEC.md v1.1 is hash-pinned and now reflects the approved
implementation, including the address-aware offline gate, the explicit
Test Mode override, the Coldcard-compatible and same-method signer scope, and
the public-release status. Any future behavior that departs from the
specification must update both the specification and its workflow hash pins.
Completed Release Gate evidence includes the app vectors, property and fuzz
tests against reference.py and python-mnemonic, the committed entitlements
audit, the fs_usage zero-writes run recorded in
VERIFICATION.md, the repository-history secret scan, and
signed release tags and checksums.
Still outstanding: independent review by an outside party, Apple Developer ID signing, and notarization. Prefer building from source until those items are complete.
A flawless app cannot defend against a compromised platform: a keylogger or screen recorder on the host defeats everything. The app can be made unable to leak; the OS cannot be made unable to look. That is why the Offline Procedure in SECURITY_SPEC.md exists and why a hardware signer remains the generator of record for real funds.
SecretBuffer and direct glyph rendering reduce secret exposure, but they
cannot prove that the compiler, CryptoKit, Core Graphics, or the macOS display
pipeline creates no temporary copies. Any such copy may be outside the app's
locked buffers and may not be explicitly wiped. Use a clean offline host,
prevent screenshots and screen recording, quit the app, and reboot after
handling a real seed.
The lot is cast into the lap; but the whole disposing thereof is of the LORD. — Proverbs 16:33