Finds a vanity SSH ed25519 keypair on your GPU, using OpenCL. If you want your
public key to contain something like pCaRR1er, run with:
$ vanity-keygen '[pP][cC][aA][rR][rR][iI1][eE][rR]'
and wait for the program to terminate, depending on your luck
after tens of billions (>1e10) of generations in this instance,
fewer than a hundred millions (<1e8) for [Pp]ierre,
millions (1e7) for love$,
fewer than a million (<1e6) for love.
Assuming you saved the private key in vanity, remember to set a passphrase with:
$ ssh-keygen -p -f vanity
Alternatively, use the comment (third) field in your .pub and authorized_keys. But where's the fun in that?
With Nix (flakes enabled):
$ nix build # binary in ./result/bin/vanity-keygen
$ nix develop # dev shell: cargo, OpenCL headers/ICD loader, pocl (CPU OpenCL)
$ cargo build --release
At runtime the program needs an OpenCL ICD for your device:
- NVIDIA: the proprietary driver (on NixOS,
hardware.nvidiaprovides/run/opengl-driver/etc/OpenCL/vendors/nvidia.icd). - AMD: ROCm (
rocmPackages.clr) or rusticl (Mesa). - Intel:
intel-compute-runtimeor rusticl (Mesa). - CPU:
pocl(included in the dev shell; much slower than any GPU).
Check with clinfo. Use -device N to pick a device (default: first
GPU-type device), -batch N for candidates per kernel launch
(default: 1048576). On servers, tmux is your friend.
Each OpenCL work item derives a batch of candidate keypairs from scratch:
SHA-512 of the seed, clamping, ed25519 scalar multiplication against the
basepoint (ref10-style field arithmetic mod 2^255-19, fixed-window
precomputed table with mixed twisted-Edwards additions, one field inversion
shared per batch), and base64 encoding of the SSH blob — all in
kernels/vanity.cl.
A GPU can't run an arbitrary regex, so the host compiles your pattern into a
necessary-condition prefilter: a short sequence of per-position bitmasks over
the base64 alphabet, matched on-device against the candidate
authorized_keys lines. Only prefilter survivors are read back, re-derived
with ed25519-dalek, and verified with the real regex, so a kernel filter
bug can cost speed, never correctness.
Consequently, patterns must be a concatenation of literals and character
classes over the base64 alphabet ([A-Za-z0-9+/]), optionally anchored with
^ and/or $. That covers typical vanity patterns (love$, pCaRR1er,
[pP][cC][aA][rR][rR][iI1][eE][rR]). Alternation of multi-character
sequences, repetitions, ., negated classes, etc. are rejected with an
error. Matching happens over the base64 pubkey portion of the
authorized_keys line.
The heavy lifting is the ed25519 arithmetic, and it shows in the numbers:
- NVIDIA RTX 4090: ~300M keypairs/s.
- pocl on a 32-core Ryzen 7950X3D: ~3M keypairs/s.
The Go version this was ported from managed ~250k keypairs/s on the same 32 cores. Use a real GPU.
The speed comes from classic tricks, all in the kernel: field arithmetic in
radix 2^51 (5×64-bit limbs, 128-bit products via mul_hi), a fixed-window
basepoint table generated on-device at startup (16 signed 16-bit windows,
32769 precomputed points per window in the (y+x, y-x, 2d·xy) format,
cutting scalar multiplication to 16 lookups and mixed additions),
addition-chain inversion, and Montgomery's batch inversion shared across
the 32 keys each work item derives.
cargo test runs unit tests for the encodings and the pattern compiler, and
— when an OpenCL device is available (pocl from the dev shell works) —
integration tests comparing the kernel against ed25519-dalek/sha2
(pubkeys, scalars, scalar multiplications incl. edge cases, and the matcher).
This is reminiscent of bitcoin mining. Finding a SHA-256 hash with a lot of zeroes through trial and error doesn't make it any more prone to collisions. We are similarly using brute force to find a public key of a particular shape, which shouldn't make the private key any more discoverable nor its security any weaker.
Do worry about a potential capture of the program's output, and cleartext storage of the private key.
As such, don't run this on a multi-user system for a personal keypair.
At the risk of repeating myself, do not use persistent storage that's not encrypted or physically secured throughout its lifetime to buffer the program's output or store the plaintext private key (however temporarily).
Of course, don't blindly trust random strangers on the Internet.
The interesting parts are small and should make for a quick read:
kernels/vanity.cl and src/.
Cargo.lock pins every dependency hash for inspection.
Not much else for me to say about your Rust standard library, toolchain, OpenCL runtime, OS, and hardware. Open is better. Security is hard.
$ vanity-keygen 'zz$'
2026/07/18 14:54:41Z Looking for a public key matching zz$ on NVIDIA GeForce RTX 4090
2026/07/18 14:54:41Z Public key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICYa3IvvAmleOZndPRGUmZGHM6gE3G1LtMd+0zeB45zz
2026/07/18 14:54:41Z Private key:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAmGtyL7wJpXjmZ3T0RlJmRhzOoBNxtS7THftM3geOc8wAAAIhnGzwOZxs8
DgAAAAtzc2gtZWQyNTUxOQAAACAmGtyL7wJpXjmZ3T0RlJmRhzOoBNxtS7THftM3geOc8w
AAAEAA+T0oFoYH7ZALcy6O85/8jIYSFBvWOmBtDOjlSU/D6yYa3IvvAmleOZndPRGUmZGH
M6gE3G1LtMd+0zeB45zzAAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----