Skip to content
DigiMonk73Public

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

23 Commits

Folders and files

Repository files navigation

CastLots

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.

What CastLots will NEVER do

  • 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.

Primary use: verify that your hardware signer really uses your dice

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.

Why CastLots exists

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.

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:

  1. Roll dice and write the sequence down: at least 50 rolls for a 12-word seed, 99 for 24 words.
  2. On Coldcard, use the dedicated Import Existing → Dice Rolls flow. For another signer, first confirm that its documentation specifies the same direct derivation: SHA256 of the ASCII dice-roll sequence. Select the same word count, enter the exact sequence, and let the signer show you the resulting words.
  3. Enter the exact same sequence into CastLots, in the same word count.
  4. 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.

What a match does and does not prove

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.

Role doctrine

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.

Verifying the app

1. Entitlements audit. The built app must be sandboxed with empty entitlements (only the sandbox flag). Verify with:

codesign -d --entitlements :- /Applications/CastLots.app

The 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 selftest

It 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 24

2b. 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 500

It 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.

Wordlist pins (SHA-256)

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.

Installing

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.git
cd CastLots && xcodebuild build -project app/CastLots.xcodeproj -scheme CastLots -configuration Release -derivedDataPath build/dd

The 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.

The prebuilt DMG, and its limitation

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-*.dmg

Compare 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_signers
ssh-keygen -Y verify -f /tmp/castlots_allowed_signers -I DigiMonk73 -n file -s SHA256SUMS.txt.sig < SHA256SUMS.txt

Release 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 specification status

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.

Known residual risk

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

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages