Skip to content

Commit

Permalink
Merge pull request #4 from eyelight/concurrency
Browse files Browse the repository at this point in the history
Concurrency
  • Loading branch information
eyelight committed Mar 18, 2023
2 parents e976d60 + 8ce2345 commit 0c069d5
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 51 deletions.
16 changes: 8 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ clean:
build:
mkdir build

linux-amd64:
linux-amd64: clean build
mkdir build/linuxamd64
env GOOS=linux GOARCH=amd64 go build -o build/linuxamd64/glasnostr main.go
tar -czf build/linuxamd64/glasnostr-linux-amd64.tar.gz build/linuxamd64/glasnostr
linux-arm64:
cd build/linuxamd64; tar -czf glasnostr-linux-amd64.tar.gz glasnostr
linux-arm64: clean build
mkdir build/linuxarm64
env GOOS=linux GOARCH=arm64 go build -o build/linuxarm64/glasnostr main.go
tar -czf build/linuxarm64/glasnostr-linux-arm64.tar.gz build/linuxarm64/glasnostr
mac-amd64:
cd build/linuxarm64; tar -czf glasnostr-linux-arm64.tar.gz glasnostr
mac-amd64: clean build
mkdir build/darwinamd64
env GOOS=darwin GOARCH=amd64 go build -o build/darwinamd64/glasnostr main.go
tar -czf build/darwinamd64/glasnostr-mac-amd64.tar.gz build/darwinamd64/glasnostr
mac-arm64:
cd build/darwinamd64; tar -czf glasnostr-mac-amd64.tar.gz glasnostr
mac-arm64: clean build
mkdir build/darwinarm64
env GOOS=darwin GOARCH=amd64 go build -o build/darwinarm64/glasnostr main.go
tar -czf build/darwinarm64/glasnostr-mac-arm64.tar.gz build/darwinarm64/glasnostr
cd build/darwinarm64; tar -czf glasnostr-mac-arm64.tar.gz glasnostr
local: clean build
go build -o build/glasnostr main.go
local-install: clean build
Expand Down
17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/eyelight/glasnostr

go 1.18

require github.com/nbd-wtf/go-nostr v0.14.1

require (
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20230201052002-6c5833b989be // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
golang.org/x/net v0.8.0 // indirect
)
23 changes: 23 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20230201052002-6c5833b989be h1:ZUMGZpetBeapAS/oOlffnBL6aSG6WwXSWfNXeadAzXE=
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20230201052002-6c5833b989be/go.mod h1:ihkm1viTbO/LOsgdGoFPBSvzqvx7ibvkMzYp3CgtHik=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/nbd-wtf/go-nostr v0.14.1 h1:OBpT+fnVqg2mcaWZKgUmLMAzQT8QdhbYGTU/uagpySA=
github.com/nbd-wtf/go-nostr v0.14.1/go.mod h1:qFFTIxh15H5GGN0WsBI/P73DteqsevnhSEW/yk8nEf4=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
175 changes: 132 additions & 43 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
package main

import (
"errors"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"

"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)

const (
version string = "v0.0.2"
npubPrefix string = "npub1"
bech32charset string = "023456789acdefghjklmnpqrstuvwxyz"
version string = "v0.0.3"
npubPrefix string = "npub1"
bech32charset string = "023456789acdefghjklmnpqrstuvwxyz"
ERR_SEARCH_FAILED string = "did not find prefix"
)

var (
misses uint64
target string
limit uint64
startTime time.Time
unbounded bool
)

type keypair struct {
npub string
nsec string
pub string
sec string
attempts uint64
dur time.Duration
}

func isBech32(s string) bool {
for _, c := range s {
if !strings.Contains(bech32charset, string(c)) {
Expand All @@ -26,80 +48,147 @@ func isBech32(s string) bool {
}

func main() {
var target string
var tries string
var limit int
var hits uint
// var tries string
mult := time.Second

// whats up
fmt.Printf("Glasnostr (%s)\nMine a vanity prefix for your Nostr npub\nhttps://github.com/eyelight/glasnostr\n\n", version)

// handle no target
if len(os.Args) < 2 {
fmt.Printf("Please supply Glasnostr with a target and optionally an upper limit for the number of guesses (default: 21 million guesses)\n example: $ glasnostr foo 50000\n\nAlso, private keys will be sent to the screen, so you may want to redirect the output\n example: $ glasnostr foo 50000 > glasnostr.txt\n")
fmt.Printf("Please supply Glasnostr with a target and optionally an upper limit for the number of guesses (default: 21 million guesses, or use '0' for infinity)\n example: $ glasnostr foo 50000\n\nAlso, private keys will be sent to the screen, so you may want to redirect the output\n example: $ glasnostr foo 50000 > glasnostr.txt\n")
os.Exit(1)
}

// validate target
target = os.Args[1]
if !isBech32(target) {
fmt.Printf("Error: target '%s' is invalid\nThe valid character set for an encoded npub is bech32. Try again with only the following characters: \n\n %s\n\n", target, bech32charset)
fmt.Printf("Target '%s' is invalid\nThe valid character set for an encoded npub is bech32. Try again with only the following characters: \n\n %s\n\n", target, bech32charset)
os.Exit(1)
}

// set limit
if len(os.Args) < 3 {
limit = 21000000
} else {
tries = os.Args[2]
tries := os.Args[2]
l, e := strconv.ParseUint(tries, 10, 64)
if e != nil {
fmt.Printf("Cannot parse limit (stay positive): \n%s\n\n", e.Error())
os.Exit(1)
}
limit = int(l)
limit = l
if limit == 0 {
unbounded = true
}
}

fmt.Printf("Starting %d attempts for prefix '%s'\n\n", limit, target)

// find target
for i := 0; i < limit; i++ {
// generate keys
sk := nostr.GeneratePrivateKey()
pk, pkerr := nostr.GetPublicKey(sk)
if pkerr != nil {
fmt.Printf("Error getting public key: %s\n", pkerr.Error())
continue
}
numWorkers := runtime.GOMAXPROCS(runtime.NumCPU())
success := make(chan bool, 1)
cancelAll := make(chan struct{}, numWorkers)
var wg sync.WaitGroup
startTime = time.Now()
if !unbounded {
fmt.Printf("(%d attempts): ", limit)
}
fmt.Printf("Starting %d threads looking for prefix '%s' at %s\n\n", numWorkers, target, startTime.Local().Format(time.RFC822))

// make the npub from the pubkey
npub, nerr := nip19.EncodePublicKey(pk)
if nerr != nil {
fmt.Printf("Error encoding NIP-19 npub from public key: %s\n", nerr.Error())
continue
}
// set up workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go work(i, &wg, success, cancelAll)
}

// evaluate the target against the npub & report if found
result := evaluate(npub, target)
if !result {
continue
} else {
hits++
nsec, err := nip19.EncodePrivateKey(sk)
if err != nil {
fmt.Printf("Error encoding NIP-19 nsec from secret key: %s\n", err.Error())
Work:
for {
select {
case <-success:
// stop
for i := 0; i < numWorkers; i++ {
cancelAll <- struct{}{}
}
break Work
default:
// report progress
if !unbounded {
if misses >= limit {
fmt.Printf(\\_(ツ)_/¯ Tried %d attempts.\n", misses)
for i := 0; i < numWorkers; i++ {
cancelAll <- struct{}{}
}
break Work
}
}
if time.Since(startTime) > mult && misses%uint64(100000) == 0 {
fmt.Printf("%d tries after %s...\n", misses, time.Since(startTime).Round(time.Second))
mult *= 2
}
fmt.Printf("Glasnostr found '%s' after %d tries:\n (pub) %s\n (sec) %s\n (npub) %s\n (nsec) %s\n\n", target, i, pk, sk, npub, nsec)
}
}
if hits == 0 {
fmt.Printf(\\_(ツ)_/¯\n")
} else {
fmt.Printf("Done hogging your CPU. Thanks for using Glasnostr.\n")
}
wg.Wait()
fmt.Printf("Done hogging your CPU. Thanks for using Glasnostr.\n")
}

// evaluate returns true if `target` immediately follows 'npub1' in the key
func evaluate(key, target string) bool {
target = npubPrefix + target
return strings.HasPrefix(key, target)
}

// mine returns a keypair if a generated npub matches the target, or an error
func mine() (keypair, error) {
// generate keys
sk := nostr.GeneratePrivateKey()
pk, pkerr := nostr.GetPublicKey(sk)
if pkerr != nil {
fmt.Printf("error getting public key: %s\n", pkerr.Error())
return keypair{}, pkerr
}

// make the npub from the pubkey
npub, nerr := nip19.EncodePublicKey(pk)
if nerr != nil {
fmt.Printf("error encoding NIP-19 npub from public key: %s\n", nerr.Error())
return keypair{}, nerr
}

// evaluate the target against the npub; finalize & return keypair if found
result := evaluate(npub, target)
if !result {
return keypair{}, errors.New(ERR_SEARCH_FAILED)
} else {
nsec, err := nip19.EncodePrivateKey(sk)
if err != nil {
fmt.Printf("error encoding NIP-19 nsec from secret key: %s\n", err.Error())
return keypair{}, err
}
kp := keypair{
npub: npub,
nsec: nsec,
sec: sk,
pub: pk,
attempts: misses,
dur: time.Since(startTime),
}
return kp, nil
}
}

// work mines for keys and reports upon success, until receiving a cancelAll channel
func work(id int, wg *sync.WaitGroup, success chan<- bool, cancelAll <-chan struct{}) {
defer wg.Done()

for {
select {
case <-cancelAll:
return
default:
kp, err := mine()
if err == nil {
fmt.Printf("Glasnostr found '%s' after %d tries (%s):\n (pub) %s\n (sec) %s\n (npub) %s\n (nsec) %s\n\n", target, kp.attempts, kp.dur, kp.pub, kp.sec, kp.npub, kp.nsec)
success <- true
}
misses++
}
}
}

0 comments on commit 0c069d5

Please sign in to comment.