The fastest Solana vanity address grinder on Apple Silicon. No CUDA required.
solgrind is an ed25519 vanity grinder that runs on the Apple GPU (Metal). It searches
for Solana addresses (base58-encoded ed25519 public keys) that match a prefix or
suffix, and writes the matching keypair to a standard Solana JSON file you can drop
straight into ~/.config/solana/id.json.
On an Apple M4 Max it sustains 13.2 million attempts/sec end-to-end, which is
10.3× faster than the official solana-keygen grind on the same machine.
git clone <repo> && cd solgrind
# Find an address starting with "So" using the GPU (Metal). Base58 excludes 0/O/I/l.
cargo run --release --features metal-gpu --bin solgrind -- grind --prefix So --gpu
# CPU fallback (no --gpu flag, no metal-gpu feature needed)
cargo run --release --bin solgrind -- grind --prefix So
# Throughput check
cargo run --release --features metal-gpu --bin eval-perfThe grinder runs until a match is found, then writes vanity.json (override with
-o). On a 4-character prefix you should expect a hit in a couple of seconds on GPU
or ~20 seconds on CPU.
Sustained attempts/sec for the full pipeline (RNG → SHA-512 → ed25519 scalar mult → point compress → base58 prefix check). All "real" numbers are measured locally.
| Tool | Backend | Rate | × vs solana-keygen |
Source |
|---|---|---|---|---|
solana-keygen grind |
CPU, 16 thr | 1.28 M/s | 1.00× | measured |
solgrind (CPU) |
CPU, 16 thr | 1.59 M/s | 1.24× | measured |
solgrind (Metal GPU) |
M4 Max iGPU | 13.23 M/s | 10.33× | measured |
For absolute context only. CUDA grinders do not run on Apple Silicon.
| Tool | Backend | Rate | Notes |
|---|---|---|---|
| Solanity | CUDA, RTX 3080 | ~40–70 M/s | Per project README and community reports; ~3–5× solgrind. |
| CUDA grinders | RTX 4090 (est.) | ~100–150 M/s | Linear scaling estimate from FP32 throughput; ~8–11× solgrind. |
Caveats: the NVIDIA rows are pulled from public project claims and FP32-scaling
estimates, not numbers we re-measured. The RTX 4090 has roughly 8× the FP32
throughput of the M4 Max GPU, so a well-tuned CUDA grinder will outpace solgrind
on dedicated NVIDIA hardware, but on a Mac solgrind is the option that runs.
| You have… | Best choice |
|---|---|
| MacBook / Mac Studio / Mac mini | solgrind --gpu (10× faster than the official tool) |
| Linux + NVIDIA GPU | Solanity or another mature CUDA grinder |
| Linux without a GPU | solgrind CPU build (Rayon-parallel, ~1.6 M/s) |
The hot path is seed → SHA-512 → clamp scalar → scalar · BasePoint → compress → check.
On a 16-core M4 Max CPU this tops out at ~1.6 M attempts/sec because each thread runs
dalek's constant-time scalar multiplication serially. The GPU breaks this ceiling by
running thousands of derivations in parallel and applying four stacked algorithmic
wins to each one.
The naive scalar multiplication loops 256 times over the bits of the scalar, doubling
the accumulator each step and adding the basepoint on set bits. We precompute a table
of d · 256^w · B for every byte position w ∈ 0..32 and every byte value
d ∈ 1..255 (32 windows × 255 entries × 160 bytes ≈ 1.27 MB, generated once at
startup, lives in device memory). Each thread now loops 32 times instead of 256,
doing a single table lookup plus point addition per iteration. That alone is the
~4× speedup on the kernel.
The standard extended-twisted-Edwards point addition takes 10 field multiplications.
With precomputed points (the basepoint multiples are known up front) we can store
each table entry as (y − x, y + x, 2·d·x·y) instead of (X, Y, Z, T). The mixed
addition then takes 7 muls instead of 10, a 30% reduction in the inner loop's
arithmetic, and the table also shrinks by 25% (3 field elements per entry vs 4).
This is the same trick dalek uses for its AffineNielsPoint representation, ported
into the MSL kernel. Stacked on top of windowing it's another ~1.22×.
The final point_compress step requires inverting the Z coordinate via Fermat's
little theorem (z^(p-2) mod p), which costs ~254 field multiplications per thread.
With windowing the scalar mult is only ~224 muls per thread, so the per-thread
inversion was eating ~50% of the work.
We share one inversion across 64 threads using a Hillis–Steele prefix scan in threadgroup memory: collect every thread's Z into shared memory, parallel-scan to get running products, invert the final product once on thread 0, then walk back to recover each thread's individual inverse. Per-thread invert cost drops from ~254 muls to ~5 muls (one shared invert plus a per-thread combine), giving another ~1.17×.
The host-side grind loop used to allocate Metal buffers on every iteration, memcpy
seeds from a Vec into device memory, and read pubkeys back. That ate ~40% of wall
time at the kernel's measured 13 M/s.
MetalBackend::make_grinder(batch_size) now pre-allocates the input/output buffers
once. The grind loop writes seeds straight into the shared input buffer via
grinder.seeds_mut() (no memcpy), dispatches, then reads pubkeys directly from the
output buffer. RNG fill and matcher checks run in parallel across CPU cores via
Rayon. End-to-end throughput now matches the kernel-isolated bench: ~7.5 M/s →
13.23 M/s, a 1.76× host-side win that closes the gap entirely.
Starting from the per-bit double-and-add MSL kernel (2.4 M/s on M4 Max), the optimizations stack as:
2.40 M/s per-bit table baseline (1.5× CPU)
6.77 M/s + 4-bit window (4.2× CPU)
10.02 M/s + 8-bit window (6.3× CPU)
11.19 M/s + threadgroup batch invert (7.0× CPU)
13.04 M/s + Niels precomputed points (8.2× CPU, kernel only)
13.23 M/s + persistent grinder ctx, end-to-end (8.3× CPU baseline / 10.3× official)
The textbook advice for 32-bit GPUs is to use ref10's field26 representation
(10 × 26-bit signed limbs) instead of dalek's field51 (5 × 51-bit unsigned limbs).
On Apple GPU this lost by 28×. Why: Metal's MSL compiler emulates i64 × i64
as a multi-instruction sequence regardless of operand bit-width, so ref10's 100
schoolbook multiplications never beat field51's 25 mulhi/mullo pairs, which the
compiler maps cleanly to the GPU's native 32×32→64 multiply instruction.
The kernel is kept (gated as ed25519_derive_table_26) as a documented dead-end.
See docs/CHANGELOG.md for the measurements.
- What ships: end-to-end Solana vanity grinding on Metal GPU, byte-exact vs
dalek across 100 000+ random scalars (
eval-correctnesstest suite). 117 unit tests pass, 0 ignored. CLI matchessolana-keygen grind's output format. - Practical ceiling on M4 Max: ~13–14 M/s. The remaining ~17% of per-thread work is the in-kernel SHA-512, which uses MSL's emulated u64 path. A u32-pair vectorized SHA-512 rewrite could push this closer to 16 M/s but is left for v0.6.
- Constant-time? No. This grinder is deliberately not constant-time: it
runs
if (digit != 0) addover each scalar byte, branching on the secret. This is fine for vanity grinding (the scalar is random and immediately discarded if it doesn't match), but do not reuse this kernel for signing.
solgrind/
├── src/
│ ├── cpu/ reference ed25519 implementation (correctness oracle)
│ ├── metal/ GPU host code + MetalGrinder API (gated by metal-gpu feature)
│ ├── matcher/ prefix/suffix + byte-range pruning
│ ├── grind.rs CPU rayon + GPU grind loops
│ └── bin/ solgrind CLI + eval-correctness + eval-perf
├── kernels/ MSL kernel source (point arithmetic, batch invert, derive)
├── benches/ criterion benchmarks (CPU + GPU)
├── eval/ correctness and perf eval harness
└── docs/ CHANGELOG.md (per-version optimization notes), findings.md
# Tier 1: unit tests (~3 s)
cargo test --release --features metal-gpu
# Tier 2: correctness eval (~3 s; 115k+ comparisons vs dalek)
cargo run --release --features metal-gpu --bin eval-correctness
# Tier 3: throughput (CPU + GPU side-by-side, ~30 s)
cargo run --release --features metal-gpu --bin eval-perf
# Criterion micro-benches for the GPU derive variants
cargo bench --features metal-gpu --bench ed25519_ops -- metal_derivesolana-keygen grind comparison:
# 50 matches of a 3-char case-sensitive prefix (~9.76M expected attempts).
# Time it and divide.
time solana-keygen grind --starts-with sun:50 --num-threads 16 --no-bip39-passphrase
# Our measurement on M4 Max: 173M / 135s = 1.28 M/sMIT
curve25519-dalek: the correctness oracle and the reference implementation forfield51, HWCD point arithmetic, and the Niels precomputed-point form.- Bernstein, Lange et al.: the underlying ed25519 algorithms (RFC 8032, HWCD 2008).