243 lines
5.4 KiB
Go
243 lines
5.4 KiB
Go
// Package main is a simple nostr key miner that uses the fast bitcoin secp256k1
|
|
// C library to derive npubs with specified prefix/infix/suffix strings present.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"orly.dev/pkg/crypto/ec/bech32"
|
|
"orly.dev/pkg/crypto/ec/secp256k1"
|
|
"orly.dev/pkg/crypto/p256k"
|
|
"orly.dev/pkg/encoders/bech32encoding"
|
|
"orly.dev/pkg/utils/atomic"
|
|
"orly.dev/pkg/utils/chk"
|
|
"orly.dev/pkg/utils/interrupt"
|
|
"orly.dev/pkg/utils/log"
|
|
"orly.dev/pkg/utils/lol"
|
|
"orly.dev/pkg/utils/qu"
|
|
|
|
"github.com/alexflint/go-arg"
|
|
)
|
|
|
|
var prefix = append(bech32encoding.PubHRP, '1')
|
|
|
|
const (
|
|
PositionBeginning = iota
|
|
PositionContains
|
|
PositionEnding
|
|
)
|
|
|
|
type Result struct {
|
|
sec []byte
|
|
npub []byte
|
|
pub []byte
|
|
}
|
|
|
|
var args struct {
|
|
String string `arg:"positional" help:"the string you want to appear in the npub"`
|
|
Position string `arg:"positional" default:"end" help:"[begin|contain|end] default: end"`
|
|
Threads int `help:"number of threads to mine with - defaults to using all CPU threads available"`
|
|
}
|
|
|
|
func main() {
|
|
lol.SetLogLevel("info")
|
|
arg.MustParse(&args)
|
|
if args.String == "" {
|
|
_, _ = fmt.Fprintln(
|
|
os.Stderr,
|
|
`Usage: vainstr [--threads THREADS] [STRING [POSITION]]
|
|
|
|
Positional arguments:
|
|
STRING the string you want to appear in the npub
|
|
POSITION [begin|contain|end] default: end
|
|
|
|
Options:
|
|
--threads THREADS number of threads to mine with - defaults to using all CPU threads available
|
|
--help, -h display this help and exit`,
|
|
)
|
|
os.Exit(0)
|
|
}
|
|
var where int
|
|
canonical := strings.ToLower(args.Position)
|
|
switch {
|
|
case strings.HasPrefix(canonical, "begin"):
|
|
where = PositionBeginning
|
|
case strings.Contains(canonical, "contain"):
|
|
where = PositionContains
|
|
case strings.HasSuffix(canonical, "end"):
|
|
where = PositionEnding
|
|
}
|
|
if args.Threads == 0 {
|
|
args.Threads = runtime.NumCPU()
|
|
}
|
|
if err := Vanity(args.String, where, args.Threads); chk.T(err) {
|
|
log.F.F("error: %s", err)
|
|
}
|
|
}
|
|
|
|
func Vanity(str string, where int, threads int) (err error) {
|
|
|
|
// check the string has valid bech32 ciphers
|
|
for i := range str {
|
|
wrong := true
|
|
for j := range bech32.Charset {
|
|
if str[i] == bech32.Charset[j] {
|
|
wrong = false
|
|
break
|
|
}
|
|
}
|
|
if wrong {
|
|
return fmt.Errorf(
|
|
"found invalid character '%c' only ones from '%s' allowed\n",
|
|
str[i], bech32.Charset,
|
|
)
|
|
}
|
|
}
|
|
started := time.Now()
|
|
quit, shutdown := qu.T(), qu.T()
|
|
resC := make(chan Result)
|
|
interrupt.AddHandler(
|
|
func() {
|
|
// this will stop work if CTRL-C or Interrupt signal from OS.
|
|
shutdown.Q()
|
|
},
|
|
)
|
|
var wg sync.WaitGroup
|
|
counter := atomic.NewInt64(0)
|
|
for i := 0; i < threads; i++ {
|
|
log.D.F("starting up worker %d", i)
|
|
go mine(str, where, quit, resC, &wg, counter)
|
|
}
|
|
tick := time.NewTicker(time.Second * 5)
|
|
var res Result
|
|
out:
|
|
for {
|
|
select {
|
|
case <-tick.C:
|
|
workingFor := time.Now().Sub(started)
|
|
wm := workingFor % time.Second
|
|
workingFor -= wm
|
|
fmt.Printf(
|
|
" working for %v, attempts %d",
|
|
workingFor, counter.Load(),
|
|
)
|
|
case r := <-resC:
|
|
// one of the workers found the solution
|
|
res = r
|
|
// tell the others to stop
|
|
quit.Q()
|
|
break out
|
|
case <-shutdown.Wait():
|
|
quit.Q()
|
|
log.I.Ln("\rinterrupt signal received")
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
// wait for all workers to stop
|
|
wg.Wait()
|
|
|
|
fmt.Printf(
|
|
"\r# generated in %d attempts using %d threads, taking %v ",
|
|
counter.Load(), args.Threads, time.Now().Sub(started),
|
|
)
|
|
fmt.Printf(
|
|
"\nHSEC = %s\nHPUB = %s\n",
|
|
hex.EncodeToString(res.sec),
|
|
hex.EncodeToString(res.pub),
|
|
)
|
|
nsec, _ := bech32encoding.BinToNsec(res.sec)
|
|
fmt.Printf("NSEC = %s\nNPUB = %s\n", nsec, res.npub)
|
|
return
|
|
}
|
|
|
|
func mine(
|
|
str string, where int, quit qu.C, resC chan Result, wg *sync.WaitGroup,
|
|
counter *atomic.Int64,
|
|
) {
|
|
|
|
wg.Add(1)
|
|
var r Result
|
|
var e error
|
|
found := false
|
|
out:
|
|
for {
|
|
select {
|
|
case <-quit:
|
|
wg.Done()
|
|
if found {
|
|
// send back the result
|
|
log.D.Ln("sending back result\n")
|
|
resC <- r
|
|
log.D.Ln("sent\n")
|
|
} else {
|
|
log.D.Ln("other thread found it\n")
|
|
}
|
|
break out
|
|
default:
|
|
}
|
|
counter.Inc()
|
|
// r.sec, r.pub, e = GenKeyPair()
|
|
r.sec, r.pub, e = Gen()
|
|
if e != nil {
|
|
log.E.Ln("error generating key: '%v' worker stopping", e)
|
|
break out
|
|
}
|
|
// r.npub, e = bech32encoding.PublicKeyToNpub(r.pub)
|
|
if r.npub, e = bech32encoding.BinToNpub(r.pub); e != nil {
|
|
log.E.Ln("fatal error generating npub: %s\n", e)
|
|
break out
|
|
}
|
|
fmt.Printf("\rgenerating key: %s", r.npub)
|
|
switch where {
|
|
case PositionBeginning:
|
|
if bytes.HasPrefix(r.npub, append(prefix, []byte(str)...)) {
|
|
found = true
|
|
quit.Q()
|
|
}
|
|
case PositionEnding:
|
|
if bytes.HasSuffix(r.npub, []byte(str)) {
|
|
found = true
|
|
quit.Q()
|
|
}
|
|
case PositionContains:
|
|
if bytes.Contains(r.npub, []byte(str)) {
|
|
found = true
|
|
quit.Q()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Gen() (skb, pkb []byte, err error) {
|
|
sign := p256k.Signer{}
|
|
if err = sign.Generate(); chk.E(err) {
|
|
return
|
|
}
|
|
skb, pkb = sign.Sec(), sign.Pub()
|
|
return
|
|
}
|
|
|
|
// GenKeyPair creates a fresh new key pair using the entropy source used by
|
|
// crypto/rand (ie, /dev/random on posix systems).
|
|
func GenKeyPair() (
|
|
sec *secp256k1.SecretKey,
|
|
pub *secp256k1.PublicKey, err error,
|
|
) {
|
|
|
|
sec, err = secp256k1.GenerateSecretKey()
|
|
if err != nil {
|
|
err = fmt.Errorf("error generating key: %s", err)
|
|
return
|
|
}
|
|
pub = sec.PubKey()
|
|
return
|
|
}
|