diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d995617 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +bin/ +.git/ +dist/ +packaging/ +docs/*.gif diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c854ce..78c9a37 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,3 +26,4 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1c3d37c..43fc271 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -48,6 +48,26 @@ nfpms: - deb - rpm +# Publishes to github.com/bytestrix/homebrew-tap. Skipped until that repo +# exists and a HOMEBREW_TAP_TOKEN secret (a PAT with write access to it) is set. +homebrew_casks: + - name: vanityrig + ids: [vanityrig] + homepage: https://github.com/bytestrix/vanityrig + description: Vanity Tor v3 .onion address finder with prefix/suffix/anywhere matching. + license: MIT + repository: + owner: bytestrix + name: homebrew-tap + token: '{{ envOrDefault "HOMEBREW_TAP_TOKEN" "" }}' + skip_upload: '{{ if eq (envOrDefault "HOMEBREW_TAP_TOKEN" "") "" }}true{{ else }}false{{ end }}' + hooks: + post: + install: | + if OS.mac? + system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/vanityrig"] + end + release: github: owner: bytestrix diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ee9af30 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:1.26-alpine AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /vanityrig ./cmd/vanityrig + +FROM alpine:3 +COPY --from=build /vanityrig /usr/local/bin/vanityrig +ENTRYPOINT ["vanityrig"] diff --git a/Makefile b/Makefile index 5fe9e28..a0d8fe8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test lint +.PHONY: build test lint demo build: go build -o bin/vanityrig ./cmd/vanityrig @@ -8,3 +8,7 @@ test: lint: go vet ./... + +# Regenerates docs/demo.gif — needs vhs (https://github.com/charmbracelet/vhs). +demo: + vhs docs/demo.tape diff --git a/README.md b/README.md index 5358b5a..7c1c788 100644 --- a/README.md +++ b/README.md @@ -13,39 +13,41 @@ Quick start · Usage · Why VanityRig · + Comparison · Contributing · Security

--- -## What is it? +**Find a Tor v3 `.onion` address containing your word — at the start, the +end, or anywhere — and know exactly how long it will take before you spend +any CPU on it.** -A Tor v3 `.onion` address is 56 random-looking characters. VanityRig finds -one containing a word you choose — at the start, at the end, or anywhere in -the middle — and tells you honestly what that will cost **before** it spends -any CPU time on it. +

+ VanityRig dashboard searching for "cafe" and finding matches in seconds +

-It's built on [`mkp224o`](https://github.com/cathugger/mkp224o), the -standard generator for this, and adds the three things that tool doesn't -have: an accurate up-front time/cost estimate, a live progress dashboard, and -the ability to match a word anywhere in the address rather than only at the -start — which is often dramatically faster than people expect. +```sh +curl -fsSL https://raw.githubusercontent.com/bytestrix/vanityrig/main/install.sh | bash +vanityrig +``` --- ## Quick start -**1. Install** +**1. Install** (Linux/macOS) ```sh curl -fsSL https://raw.githubusercontent.com/bytestrix/vanityrig/main/install.sh | bash ``` -Downloads the right prebuilt binary for your OS/architecture from the -[latest release](https://github.com/bytestrix/vanityrig/releases/latest) — -no Go toolchain required. Linux and macOS only; Windows users, grab the -`.zip` from the releases page directly. +**Windows** (PowerShell): + +```powershell +irm https://raw.githubusercontent.com/bytestrix/vanityrig/main/install.ps1 | iex +``` **2. Run it** @@ -53,25 +55,30 @@ no Go toolchain required. Linux and macOS only; Windows users, grab the vanityrig ``` -That's it — no flags to learn. A real dashboard opens, with everything on one -screen at once: **Configuration** (word, save location, match mode, CPU -share), **Statistics** (live prefix/suffix/anywhere cost comparison, -including difficulty as 2^N bits, until you start), **Progress**, -**Resources** (CPU share and, where the kernel exposes one, real CPU -temperature), **GPU Status** (honestly "coming soon" — there is no GPU engine -here, so nothing here is a fabricated number), and a scrolling **Logs** panel -with a cyclable level filter (`v`). Tab between fields, arrow keys to change a -selection, `enter` to edit a field or start, `s` to start/stop/restart a -search without quitting, `q` to quit — Configuration freezes in place while -running and the rest switch to live search data, without ever leaving this -screen. - -Already know what you want? Skip the prompts with `vanityrig [flags]` -— see [Usage](#usage) below. +No flags to learn. Type a word, press `s`, and watch it search. + +| Key | Action | +|---|---| +| `tab` / `↑↓` | move between fields | +| `←→` | change a selection | +| `enter` | edit a field | +| `s` | start / stop the search | +| `v` | cycle the log level filter | +| `q` | quit | + +Already know what you want? `vanityrig [flags]` — see [Usage](#usage). + +Found an address? See [Using your address with Tor](docs/using-your-address.md).
Other ways to install +**Homebrew (macOS/Linux):** + +```sh +brew install --cask bytestrix/tap/vanityrig +``` + **Debian/Ubuntu (.deb) or Fedora/RHEL (.rpm):** download the package for your architecture from the [latest release](https://github.com/bytestrix/vanityrig/releases/latest) @@ -88,6 +95,13 @@ sudo rpm -i vanityrig_*_linux_amd64.rpm # Fedora/RHEL go install github.com/bytestrix/vanityrig/cmd/vanityrig@latest ``` +**Docker:** + +```sh +docker build -t vanityrig https://github.com/bytestrix/vanityrig.git +docker run --rm -it -v "$PWD/keys:/keys" vanityrig cafe -out /keys +``` + **Build from source:** ```sh @@ -102,7 +116,7 @@ Optional, for any install method: put uses it automatically for prefix searches — it's roughly 8x faster per core than VanityRig's own engine for that mode (measured, not estimated — see `internal/engine/bench_test.go`). Suffix and anywhere searches always run on -VanityRig's built-in engine, since `mkp224o` can't do those at all. +VanityRig's built-in engine.
@@ -161,8 +175,7 @@ input. - **Matches anywhere in the address, not just the start.** A word has up to 47 possible positions in a 56-character address — matching any of them is - often 10-50x faster than pinning it to the front. No other generator - supports this. + often 10-50x faster than pinning it to the front. - **Compares where the word can go, not just how long it takes.** Prefix, suffix, and anywhere have very different costs for the same word, so VanityRig checks all three and tells you which is achievable — instead of @@ -186,6 +199,25 @@ input. --- +## Comparison + +| | VanityRig | mkp224o | +|---|---|---| +| Prefix match | ✅ (uses mkp224o if installed) | ✅ | +| Suffix match | ✅ | ❌ | +| Match anywhere | ✅ | only via a custom `--enable-regex` build | +| Time estimate before searching | ✅ | ❌ | +| Detects impossible patterns | ✅ | ❌ | +| Live dashboard | ✅ | ❌ | +| Resume after stop | ✅ | ❌ | +| Prefix speed per core | ~8x slower (built-in engine) | fastest | +| GPU | not yet ([#2](https://github.com/bytestrix/vanityrig/issues/2)) | ❌ | + +VanityRig isn't trying to replace `mkp224o` — it drives it for prefix +searches when it's on your `PATH`, and fills in what it doesn't do. + +--- + ## Contributing Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) and the diff --git a/docs/demo.gif b/docs/demo.gif new file mode 100644 index 0000000..63a827e Binary files /dev/null and b/docs/demo.gif differ diff --git a/docs/demo.tape b/docs/demo.tape new file mode 100644 index 0000000..2f1d862 --- /dev/null +++ b/docs/demo.tape @@ -0,0 +1,32 @@ +# Regenerate the README demo with: vhs docs/demo.tape +Output docs/demo.gif + +Set Shell "bash" +Set FontSize 14 +Set Width 1400 +Set Height 800 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 80ms + +Hide +Type "make build >/dev/null && export PATH=$PWD/bin:$PATH && clear" +Enter +Show + +Type "vanityrig" +Sleep 400ms +Enter +Sleep 1.5s + +# Edit the word field and type a word +Enter +Type "cafe" +Enter +Sleep 2s + +# Start the search and let it find matches +Type "s" +Sleep 8s + +Type "q" +Sleep 500ms diff --git a/docs/using-your-address.md b/docs/using-your-address.md new file mode 100644 index 0000000..a544404 --- /dev/null +++ b/docs/using-your-address.md @@ -0,0 +1,53 @@ +# Using your address with Tor + +Every match VanityRig finds is saved as its own directory inside the output +folder (default `~/.vanityrig/keys`), named after the address: + +``` +~/.vanityrig/keys/cafexyz…abcd.onion/ +├── hostname # the full .onion address +├── hs_ed25519_public_key +└── hs_ed25519_secret_key # keep this private — whoever has it owns the address +``` + +These are exactly the files Tor expects in a hidden service directory. + +## 1. Copy the directory to where Tor can read it + +```sh +sudo mkdir -p /var/lib/tor/my_service +sudo cp ~/.vanityrig/keys/.onion/* /var/lib/tor/my_service/ +sudo chown -R debian-tor:debian-tor /var/lib/tor/my_service # user is "tor" on Arch/Fedora +sudo chmod 700 /var/lib/tor/my_service +sudo chmod 600 /var/lib/tor/my_service/* +``` + +Tor refuses to start if the directory permissions are too open. + +## 2. Point Tor at it + +Add to `/etc/tor/torrc`: + +``` +HiddenServiceDir /var/lib/tor/my_service/ +HiddenServicePort 80 127.0.0.1:8080 +``` + +This forwards port 80 of your `.onion` to a web server on local port 8080. + +## 3. Restart Tor and check + +```sh +sudo systemctl restart tor +sudo cat /var/lib/tor/my_service/hostname +``` + +The hostname printed must be the address VanityRig found. Open it in Tor +Browser to confirm the service is reachable. + +## Keep the secret key safe + +- Back up `hs_ed25519_secret_key` somewhere offline — if you lose it, the + address is gone for good. +- Anyone with a copy can impersonate your service. Delete the matches you + aren't going to use. diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..d60d95e --- /dev/null +++ b/install.ps1 @@ -0,0 +1,41 @@ +# Installs the latest vanityrig release binary on Windows. +# Usage: irm https://raw.githubusercontent.com/bytestrix/vanityrig/main/install.ps1 | iex +$ErrorActionPreference = 'Stop' + +$repo = 'bytestrix/vanityrig' +$binDir = if ($env:VANITYRIG_INSTALL_DIR) { $env:VANITYRIG_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA 'vanityrig' } + +$arch = switch ($env:PROCESSOR_ARCHITECTURE) { + 'AMD64' { 'amd64' } + 'ARM64' { 'arm64' } + default { throw "unsupported architecture: $env:PROCESSOR_ARCHITECTURE" } +} + +$version = (Invoke-RestMethod "https://api.github.com/repos/$repo/releases/latest").tag_name +if (-not $version) { throw "could not determine the latest release of $repo" } + +$archive = "vanityrig_windows_$arch.zip" +$url = "https://github.com/$repo/releases/download/$version/$archive" + +$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid()) +New-Item -ItemType Directory -Path $tmp | Out-Null +try { + Write-Host "Downloading vanityrig $version for windows/$arch..." + Invoke-WebRequest $url -OutFile (Join-Path $tmp $archive) + Expand-Archive (Join-Path $tmp $archive) -DestinationPath $tmp + + New-Item -ItemType Directory -Force -Path $binDir | Out-Null + Move-Item -Force (Join-Path $tmp 'vanityrig.exe') (Join-Path $binDir 'vanityrig.exe') +} finally { + Remove-Item -Recurse -Force $tmp +} + +Write-Host "Installed vanityrig $version to $binDir\vanityrig.exe" + +$userPath = [Environment]::GetEnvironmentVariable('Path', 'User') +if (($userPath -split ';') -notcontains $binDir) { + [Environment]::SetEnvironmentVariable('Path', "$userPath;$binDir", 'User') + Write-Host "Added $binDir to your PATH. Open a new terminal, then run: vanityrig" +} else { + Write-Host 'Run it: vanityrig' +} diff --git a/install.sh b/install.sh index e482313..b0bedfe 100755 --- a/install.sh +++ b/install.sh @@ -19,7 +19,7 @@ case "$os" in linux|darwin) ;; *) echo "error: this script supports Linux and macOS only." >&2 - echo "Windows: download a .zip from https://github.com/$REPO/releases/latest" >&2 + echo "Windows: irm https://raw.githubusercontent.com/$REPO/main/install.ps1 | iex" >&2 exit 1 ;; esac diff --git a/internal/tui/setup_test.go b/internal/tui/setup_test.go index 9ead1dd..4fe96f5 100644 --- a/internal/tui/setup_test.go +++ b/internal/tui/setup_test.go @@ -30,6 +30,18 @@ func key(s string) tea.KeyMsg { } } +// stopAndWait cancels a running search and waits for the runner to finish +// writing its state, so t.TempDir cleanup never races a final save. +func stopAndWait(t *testing.T, s *Setup) { + t.Helper() + s.cancel() + select { + case <-s.run.Done(): + case <-time.After(10 * time.Second): + t.Fatal("runner did not stop within 10s of cancel") + } +} + func press(s *Setup, keys ...string) { for _, k := range keys { s.Update(key(k)) @@ -266,7 +278,7 @@ func TestNoLineExceedsTerminalWidth(t *testing.T) { } } if running { - s.cancel() + stopAndWait(t, s) } } } @@ -382,7 +394,7 @@ func TestMouseIgnoredOnceRunning(t *testing.T) { if s.mode != before { t.Error("clicking after the search has started must not change settings") } - s.cancel() + stopAndWait(t, s) } // The default log level (info) hides routine STATS throughput lines but @@ -680,5 +692,5 @@ func TestDashboardNeverShowsFabricatedData(t *testing.T) { t.Errorf("dashboard must never show fabricated data %q:\n%s", f, out) } } - s.cancel() + stopAndWait(t, s) }