Build a high-performance CLI tool that generates Git commit hashes matching user-defined patterns (vanity hashes). The tool mutates only invisible metadata (a binary nonce in the commit header) so the commit message, tree, parent, author, and committer remain byte-for-byte identical.
A Git commit hash is SHA-1(commit <len>\0<header>\n<message>). Any byte change
in the commit object produces a completely different hash. By appending a hidden
x-nonce field to the commit header and brute-forcing its value, we can search
for a hash that matches a desired pattern without altering any user-visible data.
Git resolves git vanity to the binary git-vanity on $PATH, making this a
seamless Git subcommand.
- Generate valid Git commits whose hash matches a user-specified pattern
- Preserve commit message integrity (byte-for-byte)
- Produce no visible difference in
git logoutput - Achieve high throughput via multi-threaded brute-force (Rust)
- Cross-platform: macOS (Intel + Apple Silicon), Linux, Windows
- Simple CLI:
git vanity <pattern> - Optional npm wrapper for easy distribution
- Modifying commits beyond HEAD (no automated rebase)
- Supporting non-Git VCS
- Guaranteeing cryptographic uniqueness
- SHA-256 support in v1 (abstracted for future)
git vanity <pattern> [options]
| Type | Syntax | Example | Match Rule |
|---|---|---|---|
| Prefix | <hex> |
git vanity cafe |
hash.startsWith("cafe") |
| Repeat | repeat:<n> |
git vanity repeat:3 |
Any run of n identical hex chars (e.g. aaa, 111) |
| Structured | <prefix>x{n} |
git vanity 1997xxx |
Prefix 1997 followed by 3 identical chars |
| Pair | xx |
git vanity xx |
Any position has 2 identical adjacent chars |
| Regex | /<regex>/ |
git vanity "/1997(.)\1/" |
Full regex against hex hash string |
Pattern validation rules:
- Prefix patterns must be valid hex (
[0-9a-f]) xin structured patterns is a wildcard for "any identical char"- Regex patterns are delimited by
/ - Invalid patterns produce a clear error and exit code 1
| Option | Short | Default | Description |
|---|---|---|---|
--amend |
true |
Rewrite HEAD commit (default behavior) | |
--message <msg> |
-m |
HEAD's message | Override commit message |
--timeout <ms> |
-t |
30000 |
Abort after N milliseconds |
--max-attempts <n> |
∞ |
Abort after N hash attempts | |
--dry-run |
-n |
false |
Print matching hash without writing |
--debug |
-d |
false |
Show throughput metrics |
--no-repeat |
false |
Disable auto-repeat pattern detection | |
--threads <n> |
-j |
num_cpus |
Number of worker threads |
| Code | Meaning |
|---|---|
| 0 | Success — matching commit written (or printed with --dry-run) |
| 1 | Invalid arguments / pattern |
| 2 | Timeout or max-attempts reached without match |
| 3 | Git error (not a repo, no HEAD, write failure) |
git cat-file commit HEAD
Parse into:
- Header fields:
tree,parent(0..n),author,committer,gpgsig(optional), other extra headers - Message: everything after the first blank line
Append to the header (after committer, before message):
x-nonce <binary-payload>
Nonce requirements:
- 8–16 bytes of random binary data per attempt
- Non-printable bytes (outside ASCII 0x20–0x7E) to avoid
git logdisplay - Monotonically incrementing counter per thread + random base (avoids collisions across threads)
- Must not contain
\n(0x0A) as that terminates the header line — use byte range0x80–0xFF
Reconstruct the full commit object:
commit <content-length>\0<tree ...>\n<parent ...>\n<author ...>\n<committer ...>\nx-nonce <binary>\n\n<message>
Compute: SHA-1(object)
Evaluate the hex-encoded hash against the active pattern engine.
Optimization: for prefix patterns, compare raw bytes (not hex strings) to avoid hex-encoding overhead on every attempt.
# Write the object to Git's object store
echo -n "$content" | git hash-object -t commit -w --stdin
# Update HEAD to point to the new commit
git update-ref HEAD <new-hash>Important: Use git update-ref instead of git reset --hard to avoid
touching the working tree or index unnecessarily. The tree hash is identical,
so the working directory is already correct.
If the original commit has a gpgsig header:
- Default: Strip the signature and warn the user (the nonce invalidates it)
--keep-sig(future): Attempt to re-sign if GPG key is available
| Pattern | Target (release build) |
|---|---|
| 4-char prefix (16^4 = 65K space) | < 1 second |
| 6-char prefix (16^6 = 16M space) | < 5 seconds |
| 8-char prefix (16^8 = 4B space) | < 60 seconds |
Expected throughput: 50–150M hashes/sec on modern hardware (8+ cores).
- Must never corrupt the Git repository
- Must produce valid Git objects (
git fsckclean) - Must handle concurrent Git operations gracefully (lock file awareness)
- Atomic: either writes the new commit or leaves HEAD unchanged
- macOS: Intel + Apple Silicon
- Linux: x86_64 + aarch64
- Windows: x86_64
src/
main.rs — CLI entry, arg parsing (clap)
pattern.rs — Pattern parsing and matching engine
commit.rs — Git commit parsing and reconstruction
hasher.rs — SHA-1 hashing with nonce injection
worker.rs — Multi-threaded brute-force coordinator
nonce.rs — Nonce generation strategy
git.rs — Git plumbing operations (read/write objects)
- Spawn
Nworker threads (default:num_cpus) - Each thread gets a unique nonce range (base + thread_id offset)
- Threads increment their nonce counter independently
- First match signals all threads to stop via
AtomicBool - Main thread collects result and writes commit
{
"name": "git-vanity",
"bin": { "git-vanity": "bin/index.js" },
"optionalDependencies": {
"git-vanity-darwin-arm64": "*",
"git-vanity-darwin-x64": "*",
"git-vanity-linux-x64": "*",
"git-vanity-win32-x64": "*"
}
}bin/index.js selects the correct binary for the current platform and executes it.
✓ Found matching hash: cafebabe1234... (1,234,567 attempts, 0.82s)
[vanity] threads: 8
[vanity] pattern: prefix("cafe")
[vanity] attempt: 1,000,000 | speed: 120M hash/sec | elapsed: 0.42s
[vanity] match: cafebabe1234... | nonce: 0x8a9b3c... | attempts: 1,234,567
When attached to a TTY, show a live progress line on stderr:
⠋ Searching... 1.2M attempts | 98M hash/sec
| Case | Behavior |
|---|---|
| No HEAD (empty repo) | Error with exit code 3 and helpful message |
| Detached HEAD | Works normally — updates HEAD ref |
| Merge commit (multiple parents) | Preserves all parent refs |
| GPG-signed commit | Strip signature + warn (nonce invalidates it) |
| Large commit message | No impact (message is unchanged, only header grows) |
.git/index.lock exists |
Error with message to retry |
SHA-256 repo (extensions.objectFormat = sha256) |
v1: error. Future: abstract hash function |
| Channel | Artifact |
|---|---|
| GitHub Releases | git-vanity-{os}-{arch} binaries |
| Homebrew | brew install git-vanity (future) |
| npm | npx git-vanity (optional wrapper) |
| Cargo | cargo install git-vanity |
| Risk | Mitigation |
|---|---|
| Slow brute-force for long patterns | Multi-threading + raw byte comparison |
| History confusion after rewrite | Clear output showing old → new hash |
| Invalid commit format | Strict parser + git fsck validation in tests |
| Git version incompatibility | Use only stable plumbing commands |
| GPG signature invalidation | Warn user, strip by default |
-
git vanity cafeproduces a commit starting withcafe -
git logshows identical message, author, date -
git fsckreports no errors - 4-char prefix completes in < 1 second
- Works on macOS, Linux, Windows
-
--dry-runprints hash without modifying repo
- Regex pattern engine (full PCRE support)
- GPU acceleration (SHA-1 on compute shaders)
- Pattern presets (
--preset cool→ curated patterns) - Distributed brute-force across machines
git vanity log— show vanity stats for repo- Commit signature re-signing after nonce injection
- Branch name vanity (
git vanity-branch)
git vanity = brute-force commit nonce → match hash pattern → write invisible mutation → beautiful commit hash.