keyRX
KEYRX, five exact letters, 1 in 656,356,768. solana-keygen grind at its 13,600/sec:
a 13-hour median. keyRX: 13 minutes, a match. Same machine, same odds - measured, not
estimated. (A five-letter grind the old way ran 50 hours here and found nothing.)
Solana and EVM BIP39 vanity address grinder. Standalone terminal tool — no daemon,
no service, no network. Replaces solana-keygen grind --use-mnemonic; on EVM it
does the same thing for Ethereum, Base, Arbitrum, Optimism, Polygon, BNB, Robinhood Chain
and every chain that shares the key format (one key is every one of them).
Why it is fast: one mnemonic yields unlimited addresses. Derive to m/44'/501' once, then walk the account index; each extra candidate costs two HMAC-SHA512 ops and one Ed25519 scalar mult instead of 2048 rounds of PBKDF2. Suffix matching needs only the last N base58 characters.
cargo install keyrx && clear && keyrx # from crates.io · Rust 1.85 or newer · lands on the start screen
RUSTFLAGS="-C target-cpu=native" cargo install --path . # from a clone, tuned to this CPU
keyrx # start screen: every command and flag, explained
keyrx verify # run first, always
keyrx bench --indices 128 # measures AND saves the rate estimate uses
keyrx estimate --ends-with KEYRX # measured; what --ignore-case and --indices 128 buy
keyrx grind --ends-with KEYRX --words 12 --indices 8 --out mint.txt # Phantom
keyrx grind --ends-with KEYRX --indices 128 --out mint.txt # Solflare
keyrx show KEYRX --keys # the private key, for Phantom 'Import Private Key'
keyrx estimate --chain evm --ends-with dead # EVM: hex, any case by default
keyrx grind --chain evm --ends-with dead # 0x...dead; the key imports into MetaMask/Rabby
keyrx grind --chain evm --starts-with 0xc0ffee --checksum # the letters in EIP-55 case as typed, too
keyrx show evm/dead --keys # the 0x hex private key
While grinding, one line rewrites in place every 2s: candidates tried, rate, elapsed, time to the 50% and 90% marks. Every match prints its address, path and the exact wallet import steps for that path style and index.
Security, and how to check a release
keyrx verify first, on your own machine. Then, for the release itself: the crate is published by
trusted publishing (no token anywhere), every published .crate carries a signed build-provenance
attestation (gh attestation verify --owner keyrx keyrx-<version>.crate), a fresh build from the
tag is compared with the registry's copy file for file on every release (the reproducible badge), the
dependency tree is audited against RustSec on every change and every week (the audit badge), every
GitHub Release attaches a CycloneDX SBOM, and OpenSSF Scorecard reads the repository's practice
continuously. What to report, and how (security@keyrx.tech): SECURITY.md.
The site
site/index.html is keyrx.tech - one self-contained file (font subset
embedded, zero external requests) that renders the CLI's own panels and runs a
real toy grind in a Web Worker: real random keys, real base58, the address
shown and nothing kept. Two grids (78 desktop / 42 phone); every framed line
is measured by node tests/site_harness.js site/index.html.
The look
Double-line frames with title tabs, an ink palette (healthy is hueless
grey; amber at 70%, rose at 90%), gauge bars in █▌░. Every
framed line is measured in tests - the width invariant is asserted, never
eyeballed. Colour drops out entirely when stdout is not a terminal.
Verified on 2026-08-16
keyrx verify: 50,000 pubkeys × 10 suffix lengths OK; derivation deterministic.- solana-keygen cross-check: identical at both path styles for the
[7u8; 32]test entropy —8zzKEAB4Vqn…UQPh1j(m/44'/501'/0'/0') and2Ju5fiKYKf4…NjAnKo(m/44'/501'/0'). Both are pinned as#[test]s. - A ground hit imports:
grind --ends-with ab --indices 8found a match in 0.15s andsolana-keygen pubkey "prompt://?full-path=m/44'/501'/6'/0'"with that mnemonic returned the identical address. - Bench (28 threads): 265,863/sec at 64 indices (19.5× the 13,600 baseline), 331,793/sec at 128 (24.4×), 437,811/sec at 256 (32.2×). Per core: 9,495–15,636 vs 284 baseline (33–55×).
cargo clippy --all-targetsclean; 10 tests green.
Verified on 2026-08-20 (EVM)
- The "abandon … about" mnemonic derives its published first account
0x9858EfFD…EcaEda94and key atm/44'/60'/0'/0/0; EIP-55's four specification examples round-trip; private key 1 is0x7E5F4552…9395Bdf. All pinned as#[test]s. - Independent cross-check: the same
[7u8; 32]test seed at indices 0, 1, 2 and 7, and with the passphrase "correct horse", derived by a separate implementation (node crypto +@noble/curves+@noble/hashes, a different language and libraries) gives the identical addresses and keys; pinned. - Two ground hits re-derive:
grind --chain evm --ends-with dead --count 2found two matches in about a second (317,800 candidates); both re-derived, address and key, by that independent implementation from the match file alone. - Bench (28 threads, 64 indices): 333,501/sec, 11,911/sec/thread; the Solana loop on the same box and settings: 498,030/sec, 17,787/sec/thread.
cargo clippy --all-targetsclean; 41 tests green; every framed line measured.
Secrets
Seed phrases never reach stdout, logs or panic output by default. Matches
go to a mode-0600 file (--out). --show-seed is opt-in. If the match
file cannot be written, the seed goes to <out>.recovered (also 0600); if
that fails too the grind STOPS and prints address+path only — never the
seed. Entropy, seed and derived key material are zeroized.
Test only with 2-character targets. No mnemonic in any fixture, snapshot or committed file: the two pinned addresses derive from public constant entropy and are worthless by construction.
--passphrase grinds with a BIP39 passphrase (the "25th word"): prompted
on the terminal, hidden, typed twice; never read from a flag, a file or the
environment, never stored, never printed. The match file records only that
one was used — the seed alone will then NOT reach the address; the privkey
and keypair lines will, standalone. Most browser wallets have no passphrase
field on seed import, so a passphrase address is imported by KEY. keyrx verify checks the passphrase path against the BIP39 specification's own
test vector ("TREZOR").
Which wallet, which flags
Every match writes five lines: address, path, seed (12 or 24 words -
restores the whole tree), privkey (the base58 64-byte keypair - what
Phantom "Import Private Key" pastes) and keypair (the same 64 bytes as a
JSON array [1,2,...] - what Solflare and solana-keygen import). A key
import lands on the exact address in one paste as a standalone account - the
index never matters, so grind wide (--indices 128). One thing every wallet
hides: a fresh Phantom or MetaMask insists on a seed phrase first and only then
offers an import; the wallet must exist (any seed, it never sees this key), and
the key import ADDS an account to it. Standalone means a seed
will not recover it: the match file is the backup. Verified both ways: a
ground base58 key converted to bytes, and the JSON line copied verbatim, each
fed to solana-keygen pubkey print the identical address.
Importing the seed instead puts the address inside a recoverable HD wallet.
Solflare takes the exact path the match printed. Phantom does not take a path -
it reaches account N by clicking "add account" N times - so a seed-into-Phantom
grind should use --indices 8. --words defaults to 12 (what Phantom
generates); every major wallet imports 12 or 24.
EVM (--chain evm, 0.4.0)
Same idea, other curve. One mnemonic pays PBKDF2 once; the BIP44 tree
m/44'/60'/0'/0/N is walked at one HMAC-SHA512 plus one secp256k1 scalar
multiplication per candidate, and the address is the last twenty bytes of
keccak-256 over the public key, written in EIP-55 case. Measured on the
development machine: about 11,900 candidates/sec per thread at 64 indices,
two thirds of the Ed25519 rate; a six-hex-digit suffix is under a minute on
a desktop, eight is hours. keyrx bench --chain evm measures yours and
estimate --chain evm reads it (its own file: the two loops cost nothing
alike).
Patterns are hex, 0-9 a-f, matched in any case by default because hex
has no case of its own; 0x may lead a prefix. --checksum asks for more:
the letters must also come out in EIP-55 case exactly as typed, a coin flip
per letter, and estimate prints both numbers.
A match writes four lines under matches/evm/<pattern>.txt: address
(EIP-55), path, seed, and privkey as the 0x hex every EVM wallet
imports. MetaMask / Rabby: a wallet must exist first (any seed; it never
sees this key); then account menu → Import account → Private key, paste it: the
exact address, standalone, on every EVM chain, including any network you add to
the wallet. Or import THIS seed as the wallet and "add account" N times
(account N+1 is the one). keyrx show lists EVM files as
evm/<pattern>; keyrx show evm/dead --keys reads one. --passphrase,
--count, --out, --words work the same on both chains; --path is
Solana-only.
keyrx networks prints the add-a-network steps for MetaMask and Rabby and the
values for EVM chains a wallet does not list by default, bare for pasting: today
Robinhood Chain (Ethereum L2, mainnet): RPC https://rpc.mainnet.chain.robinhood.com,
chain ID 4663, currency ETH, explorer https://explorer.mainnet.chain.robinhood.com
(Blockscout); the chain id was checked against that RPC on 2026-08-21. The same address
and key work there; only the selected network differs.
keyrx verify checks the EVM path against the "abandon … about" mnemonic's
published first account and key, this tool's own public test seed at four
indices against an independent implementation (node crypto + noble), the
four EIP-55 specification examples, and private key 1; and prints the manual
cross-check (cast wallet address --mnemonic … --mnemonic-index 0, or a
throwaway MetaMask).
Where matches go
~/.local/share/keyrx/matches/ (or $XDG_DATA_HOME/keyrx/matches/), a
mode-0700 directory of its own - never the current directory. Each file is
mode 0600 and named after the pattern: --ends-with KEYRX -> KEYRX.txt,
--ignore-case -> KEYRX.ic.txt, several patterns join with +. EVM files
sit under matches/evm/ (dead.txt, --checksum -> DeAd.cs.txt). --out
overrides. keyrx show lists the files; keyrx show KEYRX reads one
(--seeds / --keys reveal the secrets).
Known behaviour (unchanged from the reference, by instruction)
Superseded releases are yanked on crates.io when a new one publishes (never deleted; an
existing install keeps working). keyrx --update keeps you on the newest.
--count N may return slightly more than N matches: several threads can
hit before the stop flag propagates. Every extra match is written and
valid. Phantom walks account indices sequentially when adding accounts, so
keep --indices low if Phantom is the target; Solflare takes custom paths.
The mark
The mark is a record, sealed on chain: sixty-four hex digits of a hash on an 8×8 grid, a cell lit
where the digit is 8 or above; the upper half blue, the lower half amber. The CLI prints it above the
start screen. assets/logo.svg is the source (assets/make_mark.py draws it from the hash) and every
raster is cut from that file. What the record is will be said when the time comes.
Changelog
CHANGELOG.md — one section per version on crates.io.
Licence and name
MIT — use it, fork it, ship it, sell it. The code is yours under that licence, and
with it the look: anyone may build a grinder that frames its panels the same way.
The name, the mark, and the files under assets/ are not part of
the grant — see TRADEMARK.md and assets/LICENSE. Forks rebrand.