Skip to content

Match by a prefix instead of regexp #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
oniongen

# Created by https://www.gitignore.io/api/go
# Edit at https://www.gitignore.io/?templates=go
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build:
./make.sh build
update_libs:
./make.sh update_libs
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@

v3 .onion address vanity URL generator written in Go.

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.
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 prefix to find a vanity URL.
If the prefix 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.

## Usage

```
go run main.go <regex> <number>
go run main.go <prefix> <count>

regex regex pattern addresses should match, consisiting of: A-Z, 2-7
number number of matching addresses to generate before exiting
prefix addresses should start from the prefix, consisiting of: a-z, 2-7
count number of matching addresses to generate before exiting
```

## Example

```
go run main.go "^test" 5
go run main.go test 5

generate 5 onion addresses starting with "test"
```

## References

- Onion Addresses are defined in [Tor Rendezvous Specification - Version 3](https://github.com/torproject/torspec/blob/main/rend-spec-v3.txt)
- public key -> onion: https://github.com/torproject/torspec/blob/12271f0e6db00dee9600425b2de063e02f19c1ee/rend-spec-v3.txt#L2136-L2158
- secret key expansion:
- implementation in mkp224o: https://github.com/cathugger/mkp224o/blob/af5a7cfe122ba62e819b92c8b5a662151a284c69/ed25519/ed25519.h#L153-L161
- possibly related: https://github.com/torproject/torspec/blob/12271f0e6db00dee9600425b2de063e02f19c1ee/rend-spec-v3.txt#L2268-L2327 ??
- [specification](https://github.com/torproject/torspec/blob/12271f0e6db00dee9600425b2de063e02f19c1ee/rend-spec-v3.txt#L2268-L2327)
- [should the secret key be expanded?](https://tor.stackexchange.com/questions/23849/tor-onion-domain-should-the-secret-key-be-expanded)
- [implementation in tor](https://gitlab.torproject.org/tpo/core/tor/-/blob/main/src/ext/ed25519/donna/ed25519_tor.c#L65)
- [implementation in mkp224o](https://github.com/cathugger/mkp224o/blob/af5a7cfe122ba62e819b92c8b5a662151a284c69/ed25519/ed25519.h#L153-L161)
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/rdkr/oniongen-go

go 1.14
go 1.18

require (
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
)
14 changes: 4 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
65 changes: 40 additions & 25 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"crypto/sha512"
"encoding/base32"
"fmt"
"io/ioutil"
"os"
"regexp"
"runtime"
Expand All @@ -17,17 +16,21 @@ import (
"golang.org/x/crypto/sha3"
)

func generate(wg *sync.WaitGroup, re *regexp.Regexp) {
const b32Lower = "abcdefghijklmnopqrstuvwxyz234567"

for {
var b32Enc = base32.NewEncoding(b32Lower).WithPadding(base32.NoPadding)

func generate(wg *sync.WaitGroup, prefix string, prefixDecodedLen int) {
for {
publicKey, secretKey, err := ed25519.GenerateKey(nil)
checkErr(err)

onionAddress := encodePublicKey(publicKey)

// Match the public key with prefix.
// No need to encode all PK to B32 if prefix shorter
publicKeyB32 := b32Enc.EncodeToString(publicKey[0:prefixDecodedLen])
// If a matching address is found, save key and notify wait group
if re.MatchString(onionAddress) == true {
if strings.HasPrefix(publicKeyB32, prefix) {
onionAddress := encodePublicKey(publicKey)
fmt.Println(onionAddress)
save(onionAddress, publicKey, expandSecretKey(secretKey))
wg.Done()
Expand All @@ -36,45 +39,42 @@ func generate(wg *sync.WaitGroup, re *regexp.Regexp) {
}

func expandSecretKey(secretKey ed25519.PrivateKey) [64]byte {

hash := sha512.Sum512(secretKey[:32])
// clamp the blinding factor 'h' according to the ed25519 spec
hash[0] &= 248
hash[31] &= 127
hash[31] |= 64
return hash

}

func encodePublicKey(publicKey ed25519.PublicKey) string {

// checksum = H(".onion checksum" || pubkey || version)
var checksumBytes bytes.Buffer
checksumBytes.Write([]byte(".onion checksum"))
checksumBytes.Write([]byte(publicKey))
checksumBytes.Write(publicKey)
checksumBytes.Write([]byte{0x03})
checksum := sha3.Sum256(checksumBytes.Bytes())

// onion_address = base32(pubkey || checksum || version)
var onionAddressBytes bytes.Buffer
onionAddressBytes.Write([]byte(publicKey))
onionAddressBytes.Write([]byte(checksum[:2]))
onionAddressBytes.Write(publicKey)
onionAddressBytes.Write(checksum[:2])
onionAddressBytes.Write([]byte{0x03})
onionAddress := base32.StdEncoding.EncodeToString(onionAddressBytes.Bytes())

return strings.ToLower(onionAddress)
onionAddress := b32Enc.EncodeToString(onionAddressBytes.Bytes())

return onionAddress
}

func save(onionAddress string, publicKey ed25519.PublicKey, secretKey [64]byte) {
os.MkdirAll(onionAddress, 0700)
checkErr(os.MkdirAll(onionAddress, 0700))

secretKeyFile := append([]byte("== ed25519v1-secret: type0 ==\x00\x00\x00"), secretKey[:]...)
checkErr(ioutil.WriteFile(onionAddress+"/hs_ed25519_secret_key", secretKeyFile, 0600))
checkErr(os.WriteFile(onionAddress+"/hs_ed25519_secret_key", secretKeyFile, 0600))

publicKeyFile := append([]byte("== ed25519v1-public: type0 ==\x00\x00\x00"), publicKey...)
checkErr(ioutil.WriteFile(onionAddress+"/hs_ed25519_public_key", publicKeyFile, 0600))
checkErr(os.WriteFile(onionAddress+"/hs_ed25519_public_key", publicKeyFile, 0600))

checkErr(ioutil.WriteFile(onionAddress+"/hostname", []byte(onionAddress+".onion\n"), 0600))
checkErr(os.WriteFile(onionAddress+"/hostname", []byte(onionAddress+".onion\n"), 0600))
}

func checkErr(err error) {
Expand All @@ -84,14 +84,30 @@ func checkErr(err error) {
}

func main() {

// Set runtime to use all available CPUs.
runtime.GOMAXPROCS(runtime.NumCPU())

// Compile regex from first argument.
re, _ := regexp.Compile(os.Args[1])
prefix := os.Args[1]
validPrefix, _ := regexp.MatchString(`^[a-z2-9]*$`, prefix)
if !validPrefix {
fmt.Fprintf(os.Stderr, "Invalid prefix: onion addresses can't contain 0 or 1 to avoid confusion with o and l\n")
os.Exit(1)
}

prefixLen := len(prefix)
prefixDecodedLen := b32Enc.DecodedLen(prefixLen)
// Same DecodedLen(prefix) but without rounding to floor
prefixDecodedLenRatio := float64(prefixLen) * 5.0 / 8.0
// if there is some division remainder
if prefixDecodedLenRatio > float64(prefixDecodedLen) {
// 8 bytes encoded into 5 chars. We must add them all because don't know which byte changes a char
prefixDecodedLen += 8
}
if prefixDecodedLen > 32 {
prefixDecodedLen = 32
}

// Get the number of desired addreses from second argument.
// Get the number of desired addresses from second argument.
numAddresses, _ := strconv.Atoi(os.Args[2])

// WaitGroup of size equal to desired number of addresses
Expand All @@ -100,10 +116,9 @@ func main() {

// For each CPU, run a generate goroutine
for i := 0; i < runtime.NumCPU(); i++ {
go generate(&wg, re)
go generate(&wg, prefix, prefixDecodedLen)
}

// Exit after the desired number of addresses have been found.
wg.Wait()

}
32 changes: 32 additions & 0 deletions make.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
build() {
go mod download
GOAMD64=v3 go build -tags release -o ./oniongen
}

update_libs() {
go get -u
go mod tidy
}

help() {
go get -u
echo "build update_libs"
}

progname=$(basename $0)
subcommand=$1
case $subcommand in
"" | "-h" | "--help")
help
;;
*)
shift
echo "Executing: $subcommand"
${subcommand} "$@"
if [ $? = 127 ]; then
echo "Error: '$subcommand' is not a known subcommand." >&2
help
exit 1
fi
;;
esac