Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
innix committed Oct 24, 2021
0 parents commit c01d47b
Show file tree
Hide file tree
Showing 15 changed files with 926 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
root = true

[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4

[*.md]
indent_size = 4
trim_trailing_whitespace = false

[Dockerfile]
indent_size = 4
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# IDE directories
.idea/
.vscode/

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Outputs of the go coverage tool, specifically when used with LiteIDE
*.out
test-coverage.html

# Dependency directories
vendor/

# Release archives
*.tar.gz
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 innix

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
116 changes: 116 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<div align="center">

# Shrek <img src="./assets/onion-icon.png" alt=":onion:" title=":onion:" class="emoji" height="20">

Shrek is a vanity `.onion` address generator written in Go.

![Shrek running from CLI](/assets/shrek-zsh.png)

</div>

# Usage (CLI)

Shrek compiles to a single binary that can be used on the CLI. To install:

```bash
# The shrek binary will be installed to $GOPATH/bin
go install github.com/innix/shrek/cmd/shrek
```

The program takes 1 or more filters as arguments. It only generates v3 addresses.

```bash
# Generate an address that starts with "food":
shrek food

# Generate an address that starts with "food" and ends with "xid":
shrek food:xid

# Generate an address that starts with "food" and ends with "bid", or starts with "barn":
shrek food:bid barn

# Shrek can search for the start of an onion address much faster than the end
# of the address. Therefore, it is recommended that the filters you provide have a
# bigger start filter and a smaller (or zero) end filter.

# Generate an address that ends with "2ayd".
shrek :2ayd
```

To see full usage, use the `-h` flag:

```bash
shrek -h
```

# Usage (library)

You can use Shrek as a library in your Go code. Add it to your `go.mod` file:

```bash
# Run in your project root, where your go.mod file is.
go get github.com/innix/shrek
```

Here's an example of finding an address using a start-end pattern and saving it to disk:

```go
package main

import (
"context"
"fmt"

"github.com/innix/shrek"
)

func main() {
// Brute-force find a hostname that starts with "foo" and ends with "id".
addr, err := shrek.MineOnionHostName(context.Background(), nil, shrek.StartEndMatcher{
Start: []byte("foo"),
End: []byte("id"),
})
if err != nil {
panic(err)
}

// Save hostname and the public/secret keys to disk.
fmt.Printf("Onion address %q found, saving to file system.\n", addr.HostNameString())
err = shrek.SaveOnionAddress("output_dir", addr)
if err != nil {
panic(err)
}
}
```

# Performance

Shrek is the fastest `.onion` vanity address generator written in Go (at time of writing), but it's
still very slow compared to native C programs like [`mkp224o`](https://github.com/cathugger/mkp224o).
There are likely optimizations that could be made by someone smarter than me. If you fancy the
challenge, feel free to make a pull request with your optimizations.

The primary goal of Shrek is to be an easy to use Go program/library for Go developers, not to be the
fastest program out there. It should be able to run on every platform that Go supports. Use of `cgo`
or other complicated build processes should be avoided.

You can find the slow parts of the codebase by running the benchmarks:

```bash
go test -bench=.
```

# FAQ

## Are v2 addresses supported?

No. They were already deprecated at the time Shrek was written, so there didn't seem any point
supporting them. There are no plans to add it as a feature.

## Why "Shrek"?

Onions have layers, ogres have layers.

# License

Shrek is distributed under the terms of the MIT License (see [LICENSE](LICENSE)).
Binary file added assets/onion-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/shrek-zsh.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions cmd/shrek/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"fmt"
"os"
)

var LogVerboseEnabled = false

func LogError(format string, a ...interface{}) {
_, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf(format, a...))
}

func LogInfo(format string, a ...interface{}) {
_, _ = fmt.Fprintln(os.Stdout, fmt.Sprintf(format, a...))
}

func LogVerbose(format string, a ...interface{}) {
if LogVerboseEnabled {
_, _ = fmt.Fprintln(os.Stdout, fmt.Sprintf(format, a...))
}
}
157 changes: 157 additions & 0 deletions cmd/shrek/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import (
"context"
"errors"
"flag"
"fmt"
"os"
"runtime"
"strings"

"github.com/innix/shrek"
)

type appOptions struct {
Verbose bool
SaveDirectory string
NumAddresses int
NumThreads int
Patterns []string
}

func main() {
opts := buildAppOptions()
runtime.GOMAXPROCS(opts.NumThreads + 1) // +1 for main proc.

if opts.Verbose {
LogVerboseEnabled = true
}

m, err := buildMatcher(opts.Patterns)
if err != nil {
LogError("ERROR: invalid args: %v", err)
os.Exit(2)
}

LogInfo("Searching for %d addresses, using %d threads, with %d filters.",
opts.NumAddresses, opts.NumThreads, len(m.Inner))
LogInfo("")

// Channel to receive onion addresses from miners.
addrs := make(chan *shrek.OnionAddress, opts.NumAddresses)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Spin up the miners.
for i := opts.NumThreads; i > 0; i-- {
go func() {
if err := mineHostNames(ctx, addrs, m); err != nil && !errors.Is(err, ctx.Err()) {
LogError("ERROR: %v", err)
}
}()
}

// Loop until the requested number of addresses have been mined.
mineForever := opts.NumAddresses == 0
for i := 0; i < opts.NumAddresses || mineForever; i++ {
addr := <-addrs
hostname := addr.HostNameString()

LogInfo(hostname)
if err := shrek.SaveOnionAddress(opts.SaveDirectory, addr); err != nil {
LogError("ERROR: Found .onion but could not save it to file system: %v", err)
}
}
}

func buildAppOptions() appOptions {
var opts appOptions
flag.BoolVar(&opts.Verbose, "V", false, "Verbose logging (default = false)")
flag.StringVar(&opts.SaveDirectory, "d", "", "The directory to save keys in (default = current directory)")
flag.IntVar(&opts.NumAddresses, "n", 0, "Number of onion addresses to generate (0/default = unlimited)")
flag.IntVar(&opts.NumThreads, "t", 0, "Number of threads to use (0/default = all CPU cores)")
flag.Parse()

if flag.NArg() < 1 {
LogError("Usage: %s [COMMAND OPTIONS] <pattern1> [pattern2...]", os.Args[0])
os.Exit(2)
}

// Set runtime to use number of threads requested.
if opts.NumThreads <= 0 {
opts.NumThreads = runtime.NumCPU()
}

// Set to default if negative number given for some reason.
if opts.NumAddresses < 0 {
opts.NumAddresses = 0
}

// Non-flag args are patterns.
opts.Patterns = flag.Args()

return opts
}

func buildMatcher(args []string) (shrek.MultiMatcher, error) {
var mm shrek.MultiMatcher

for _, pattern := range args {
parts := strings.Split(pattern, ":")

switch len(parts) {
case 1:
if !isValidMatcherPattern(parts[0]) {
return mm, fmt.Errorf("pattern contains invalid chars: %q", parts[0])
}

mm.Inner = append(mm.Inner, shrek.StartEndMatcher{
Start: []byte(parts[0]),
End: nil,
})

LogVerbose("Found filter: starts_with='%s'", parts[0])
case 2:
if !isValidMatcherPattern(parts[0]) {
return mm, fmt.Errorf("pattern contains invalid chars: %q", parts[0])
}
if !isValidMatcherPattern(parts[1]) {
return mm, fmt.Errorf("pattern contains invalid chars: %q", parts[1])
}

mm.Inner = append(mm.Inner, shrek.StartEndMatcher{
Start: []byte(parts[0]),
End: []byte(parts[1]),
})

LogVerbose("Found filter: starts_with='%s', ends_with='%s'", parts[0], parts[1])
default:
return mm, fmt.Errorf("invalid pattern: %q", pattern)
}
}

return mm, nil
}

func isValidMatcherPattern(v string) bool {
return strings.Trim(v, "abcdefghijklmnopqrstuvwxyz234567") == ""
}

func mineHostNames(ctx context.Context, ch chan<- *shrek.OnionAddress, m shrek.Matcher) error {
for ctx.Err() == nil {
addr, err := shrek.MineOnionHostName(ctx, nil, m)
if err != nil {
return err
}

select {
case ch <- addr:
default:
return nil
}
}

return ctx.Err()
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/innix/shrek

go 1.17

require (
github.com/oasisprotocol/curve25519-voi v0.0.0-20210908142542-2a44edfcaeb0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
)

require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
github.com/oasisprotocol/curve25519-voi v0.0.0-20210908142542-2a44edfcaeb0 h1:mhi9nviuY1liEwHRApVyaY/5B7JQMU2/vpm/qHk7pdc=
github.com/oasisprotocol/curve25519-voi v0.0.0-20210908142542-2a44edfcaeb0/go.mod h1:WUcXjUd98qaCVFb6j8Xc87MsKeMCXDu9Nk8JRJ9SeC8=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Loading

0 comments on commit c01d47b

Please sign in to comment.