├── .gitignore ├── README.md ├── go.mod ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/go 3 | # Edit at https://www.gitignore.io/?templates=go 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | # End of https://www.gitignore.io/api/go 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oniongen-go 2 | 3 | v3 .onion address vanity URL generator written in Go. 4 | 5 | This implementation generates random ed25519 keys across all CPU cores. The ed25519 public key is converted to a Tor v3 .onion address which is then compared to a user supplied regex to find a vanity URL. If the regex for the .onion address matches, the secret key is expanded for use by Tor and the public key, secret key, and hostname are written to file in a new directory named for the .onion address. The program terminates when the user supplied number of addresses have been generated. 6 | 7 | ## Usage 8 | 9 | ``` 10 | go run main.go 11 | 12 | regex regex pattern addresses should match, consisiting of: A-Z, 2-7 13 | number number of matching addresses to generate before exiting 14 | ``` 15 | 16 | ## Example 17 | 18 | ``` 19 | go run main.go "^test" 5 20 | 21 | generate 5 onion addresses starting with "test" 22 | ``` 23 | 24 | ## References 25 | 26 | - public key -> onion: https://github.com/torproject/torspec/blob/12271f0e6db00dee9600425b2de063e02f19c1ee/rend-spec-v3.txt#L2136-L2158 27 | - secret key expansion: 28 | - implementation in mkp224o: https://github.com/cathugger/mkp224o/blob/af5a7cfe122ba62e819b92c8b5a662151a284c69/ed25519/ed25519.h#L153-L161 29 | - possibly related: https://github.com/torproject/torspec/blob/12271f0e6db00dee9600425b2de063e02f19c1ee/rend-spec-v3.txt#L2268-L2327 ?? 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rdkr/oniongen-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 7 | golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= 3 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 4 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 5 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 6 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 7 | golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= 8 | golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 9 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 10 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 11 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "crypto/sha512" 7 | "encoding/base32" 8 | "fmt" 9 | "io/ioutil" 10 | "os" 11 | "regexp" 12 | "runtime" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | 17 | "golang.org/x/crypto/sha3" 18 | ) 19 | 20 | func generate(wg *sync.WaitGroup, re *regexp.Regexp) { 21 | 22 | for { 23 | 24 | publicKey, secretKey, err := ed25519.GenerateKey(nil) 25 | checkErr(err) 26 | 27 | onionAddress := encodePublicKey(publicKey) 28 | 29 | // If a matching address is found, save key and notify wait group 30 | if re.MatchString(onionAddress) == true { 31 | fmt.Println(onionAddress) 32 | save(onionAddress, publicKey, expandSecretKey(secretKey)) 33 | wg.Done() 34 | } 35 | } 36 | } 37 | 38 | func expandSecretKey(secretKey ed25519.PrivateKey) [64]byte { 39 | 40 | hash := sha512.Sum512(secretKey[:32]) 41 | hash[0] &= 248 42 | hash[31] &= 127 43 | hash[31] |= 64 44 | return hash 45 | 46 | } 47 | 48 | func encodePublicKey(publicKey ed25519.PublicKey) string { 49 | 50 | // checksum = H(".onion checksum" || pubkey || version) 51 | var checksumBytes bytes.Buffer 52 | checksumBytes.Write([]byte(".onion checksum")) 53 | checksumBytes.Write([]byte(publicKey)) 54 | checksumBytes.Write([]byte{0x03}) 55 | checksum := sha3.Sum256(checksumBytes.Bytes()) 56 | 57 | // onion_address = base32(pubkey || checksum || version) 58 | var onionAddressBytes bytes.Buffer 59 | onionAddressBytes.Write([]byte(publicKey)) 60 | onionAddressBytes.Write([]byte(checksum[:2])) 61 | onionAddressBytes.Write([]byte{0x03}) 62 | onionAddress := base32.StdEncoding.EncodeToString(onionAddressBytes.Bytes()) 63 | 64 | return strings.ToLower(onionAddress) 65 | 66 | } 67 | 68 | func save(onionAddress string, publicKey ed25519.PublicKey, secretKey [64]byte) { 69 | os.MkdirAll(onionAddress, 0700) 70 | 71 | secretKeyFile := append([]byte("== ed25519v1-secret: type0 ==\x00\x00\x00"), secretKey[:]...) 72 | checkErr(ioutil.WriteFile(onionAddress+"/hs_ed25519_secret_key", secretKeyFile, 0600)) 73 | 74 | publicKeyFile := append([]byte("== ed25519v1-public: type0 ==\x00\x00\x00"), publicKey...) 75 | checkErr(ioutil.WriteFile(onionAddress+"/hs_ed25519_public_key", publicKeyFile, 0600)) 76 | 77 | checkErr(ioutil.WriteFile(onionAddress+"/hostname", []byte(onionAddress+".onion\n"), 0600)) 78 | } 79 | 80 | func checkErr(err error) { 81 | if err != nil { 82 | panic(err) 83 | } 84 | } 85 | 86 | func main() { 87 | 88 | // Set runtime to use all available CPUs. 89 | runtime.GOMAXPROCS(runtime.NumCPU()) 90 | 91 | // Compile regex from first argument. 92 | re, _ := regexp.Compile(os.Args[1]) 93 | 94 | // Get the number of desired addreses from second argument. 95 | numAddresses, _ := strconv.Atoi(os.Args[2]) 96 | 97 | // WaitGroup of size equal to desired number of addresses 98 | var wg sync.WaitGroup 99 | wg.Add(numAddresses) 100 | 101 | // For each CPU, run a generate goroutine 102 | for i := 0; i < runtime.NumCPU(); i++ { 103 | go generate(&wg, re) 104 | } 105 | 106 | // Exit after the desired number of addresses have been found. 107 | wg.Wait() 108 | 109 | } 110 | --------------------------------------------------------------------------------