This tool searches for a WireGuard Curve25519 keypair with a base64-encoded public key that has a specified prefix.
Compared to similar tools, it uses the fastest search algorithm ๐
Install the tool locally and run:
$ go install github.com/AlexanderYastrebov/wireguard-vanity-key@latest
$ wireguard-vanity-key -prefix=2025
private public attempts duration attempts/s
WHakaGFouuy2AxMmOdSTf2L2KWsI6a3s+gvAOKuKtH0= 2025sb38RUVI+GJg5Uk2RRPuJfhZyg4uSxfV2WDn1g8= 47423039 2s 19926032
$ # verify
$ echo WHakaGFouuy2AxMmOdSTf2L2KWsI6a3s+gvAOKuKtH0= | wg pubkey
2025sb38RUVI+GJg5Uk2RRPuJfhZyg4uSxfV2WDn1g8=
or run the tool from the source repository:
$ go run . -prefix=2025
or use the Docker image:
$ docker pull ghcr.io/alexanderyastrebov/wireguard-vanity-key:latest
$ docker run ghcr.io/alexanderyastrebov/wireguard-vanity-key:latest -prefix=2025
The tool checks ~29'000'000 keys per second on a test machine:
$ go run . -prefix=GoodLuckWithThisPrefix -timeout=20s
private public attempts duration attempts/s
- GoodLuckWithThisPrefix... 583920640 20s 29194831
In practice, it finds a 4-character prefix in a second and a 5-character prefix in a minute:
$ while go run . -prefix=AYAYA ; do : ; done
private public attempts duration attempts/s
OM9WvIxO90NRnHpMLBYbKCwRxj1KcwWVfo5EO1vftls= AYAYAgcnXbdsMwLB+nR0kkWDpIkMr+3thhfGnBEvTmM= 515859548 23s 22328225
private public attempts duration attempts/s
eEbiqUhcUrH6Uj1p7cycgTOspY6fMxxImSNNr1YvaEg= AYAYA4yow92Ks1wnbQeceKEWIYHhaRyezomUz9SQJic= 350598404 19s 18060407
private public attempts duration attempts/s
OOkjEu4elrWJ4MD+OxB2kvUcKdyo482E3G3Y/tLBsmI= AYAYAW4yGEUVT/IkX3T6ZZTnz3yPS1lPxiRe0yhOCCs= 260273230 14s 17972036
private public attempts duration attempts/s
+BWkcGvbkXFxNgxIrAYyJoMF1R6R3eguv5NyMsdlaEA= AYAYAQEsY0gagwZ5lGLRQYfxQ+5rl83LOPmaASvASFQ= 1094012149 56s 19446702
private public attempts duration attempts/s
aG7Rakjbn1kpc2HN7fUz1u/ZrTcYziXg7OJq2EcMWFU= AYAYAe9QZdXn36CrkOK8aoD8h92mbEHCQt1QdTBARjY= 1088287697 56s 19483959
Each additional character increases search time by a factor of 64.
The tool supports blind search, i.e., when the worker does not know the private key. See demo-blind.sh.
You can run the tool in a distributed manner in Kubernetes cluster using the demo-k8s.yaml manifest to search for a vanity key without exposing the private key:
$ # Generate secure starting key pair
$ wg genkey | tee /dev/stderr | wg pubkey
YI5+UcKmyLdeRDqU8l3k53wrUZO9Mw23NpvB8tDtvWU=
startkQgqI9Gv1IX7eNa2qeFhpYBRDwpz40JIAAYOSk=
$ # Edit demo-k8s.yaml to configure prefix, starting public key, parallelism, and resource limits ๐ธ
$ # Create search job
$ kubectl apply -f demo-k8s.yaml
job.batch/wvk created
$ # Check job
$ kubectl get job wvk
NAME STATUS COMPLETIONS DURATION AGE
wvk Running 0/10 2m53s 2m53s
$ # Check pods
$ kubectl get pods --selector=batch.kubernetes.io/job-name=wvk
NAME READY STATUS RESTARTS AGE
wvk-0-8tdz5 1/1 Running 0 3m8s
wvk-1-pmnkn 1/1 Running 0 3m8s
wvk-2-2ls7m 1/1 Running 0 3m8s
wvk-3-rd7gx 1/1 Running 0 3m8s
wvk-4-jqksz 1/1 Running 0 3m8s
wvk-5-vj6gd 1/1 Running 0 3m8s
wvk-6-vhgmc 1/1 Running 0 3m8s
wvk-7-drr98 1/1 Running 0 3m8s
wvk-8-tmb6c 1/1 Running 0 3m8s
wvk-9-gxlp2 1/1 Running 0 3m8s
$ # Check resource usage
$ kubectl top pods --selector=batch.kubernetes.io/job-name=wvk
$ # Wait for the job to complete
$ kubectl wait --for=condition=complete job/wvk --timeout=1h
job.batch/wvk condition met
$ # Job is complete
$ kubectl get job wvk
NAME STATUS COMPLETIONS DURATION AGE
wvk Complete 1/999999 34m 37m
$ # Get found offset from the logs
$ kubectl logs jobs/wvk
7538451707115552752
$ # Generate new private vanity key by offsetting the starting private key
$ echo YI5+UcKmyLdeRDqU8l3k53wrUZO9Mw23NpvB8tDtvWU= | wireguard-vanity-key add --offset=7538451707115552752 --prefix=wvk+k8s
4I4EWan32HJbRDqU8l3k53wrUZO9Mw23NpvB8tDtvWU=
$ # Get the vanity public key
$ echo 4I4EWan32HJbRDqU8l3k53wrUZO9Mw23NpvB8tDtvWU= | wg pubkey
wvk+k8shgsJcW5EKet2AkViKc7a/0Ud8/EDOy91aCQg=
$ # Delete the job
$ kubectl delete job wvk
job.batch "wvk" deleted
A WireGuard key pair consists of a 256-bit random private key and a public key derived by scalar multiplication on Curve25519 involving arithmetic operations (additions, multiplications) in a finite field.
The performance of any brute-force key search algorithm ultimately depends on the number of finite field multiplications per candidate key - the most expensive field operation.
All available WireGuard vanity key search tools use the straightforward approach: multiply the base point by a random candidate private key and check the resulting public key:
public_key = private_key ร base_point
For the WireGuard key format, this basic algorithm requires 2561 field multiplications (using square-and-multiply) or 743 field multiplications (using Twisted Edwards curve) per candidate key.
This tool uses only 5 (five) field multiplications per candidate key and other optimizations, which makes it the fastest ๐
Inspired by wireguard-vanity-address "faster algorithm", instead of doing full scalar multiplication for each candidate, this tool applies a point increment technique that reduces the number of multiplications:
public_key0 = private_key0 ร base_point
public_key1 = (private_key0 + const_offset) ร base_point
= private_key0 ร base_point + const_offset ร base_point
= public_key0 + const_offset ร base_point
= public_key0 + const_point_offset
i.e., the candidate public key is obtained by point addition instead of base point multiplication, which requires fewer field multiplications (275 vs. 743 or ~60% faster). See initial commit.
Getting the public key in WireGuard format (Montgomery form) requires a field inversion, which uses 265 field multiplications. This tool uses the Montgomery trick to implement batch inversion, so for a batch of N candidates, only 1 (one) inversion is needed, resulting in a huge speedup. See #3.
The algorithm uses affine coordinates and exploits symmetries in precomputed point offsets, saving even more field multiplications. See #10, #12, and #14.
Other tools encode the full public key to base64 and compare the prefix. This tool decodes the base64 prefix and compares it to public key bytes directly. See #5.
For raw speed, the C worker (wvk
) uses awslabs/s2n-bignum -
a highly optimized field arithmetic library written in assembly.
The worker supports prefix lengths up to 10 base64 characters, so the prefix check becomes a single masked integer comparison.
These two optimizations make wvk
~2 times faster than the Go implementation.
See #15.