main.go raw

   1  // Package main is a simple nostr key miner that uses the fast bitcoin secp256k1
   2  // C library to derive npubs with specified prefix/infix/suffix strings present.
   3  package main
   4  
   5  import (
   6  	"bytes"
   7  	"encoding/hex"
   8  	"fmt"
   9  	"os"
  10  	"runtime"
  11  	"strings"
  12  	"sync"
  13  	"sync/atomic"
  14  	"time"
  15  
  16  	"next.orly.dev/pkg/lol/chk"
  17  	"next.orly.dev/pkg/lol/log"
  18  
  19  	"next.orly.dev/pkg/nostr/crypto/ec/bech32"
  20  	"next.orly.dev/pkg/nostr/encoders/bech32encoding"
  21  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  22  
  23  	"github.com/alexflint/go-arg"
  24  )
  25  
  26  var prefix = append(bech32encoding.PubHRP, '1')
  27  
  28  const (
  29  	PositionBeginning = iota
  30  	PositionContains
  31  	PositionEnding
  32  )
  33  
  34  type Result struct {
  35  	sec  []byte
  36  	npub []byte
  37  	pub  []byte
  38  }
  39  
  40  var args struct {
  41  	String   string `arg:"positional" help:"the string you want to appear in the npub"`
  42  	Position string `arg:"positional" default:"end" help:"[begin|contain|end] default: end"`
  43  	Threads  int    `help:"number of threads to mine with - defaults to using all CPU threads available"`
  44  }
  45  
  46  func main() {
  47  	arg.MustParse(&args)
  48  	if args.String == "" {
  49  		_, _ = fmt.Fprintln(
  50  			os.Stderr,
  51  			`Usage: vainstr [--threads THREADS] [STRING [POSITION]]
  52  
  53  Positional arguments:
  54    STRING                 the string you want to appear in the npub
  55    POSITION               [begin|contain|end] default: end
  56  
  57  Options:
  58    --threads THREADS      number of threads to mine with - defaults to using all CPU threads available
  59    --help, -h             display this help and exit`,
  60  		)
  61  		os.Exit(0)
  62  	}
  63  	var where int
  64  	canonical := strings.ToLower(args.Position)
  65  	switch {
  66  	case strings.HasPrefix(canonical, "begin"):
  67  		where = PositionBeginning
  68  	case strings.Contains(canonical, "contain"):
  69  		where = PositionContains
  70  	case strings.HasSuffix(canonical, "end"):
  71  		where = PositionEnding
  72  	}
  73  	if args.Threads == 0 {
  74  		args.Threads = runtime.NumCPU()
  75  	}
  76  	if err := Vanity(args.String, where, args.Threads); chk.E(err) {
  77  		log.F.F("error: %s", err)
  78  	}
  79  }
  80  
  81  func Vanity(str string, where int, threads int) (err error) {
  82  	// check the string has valid bech32 ciphers
  83  	for i := range str {
  84  		wrong := true
  85  		for j := range bech32.Charset {
  86  			if str[i] == bech32.Charset[j] {
  87  				wrong = false
  88  				break
  89  			}
  90  		}
  91  		if wrong {
  92  			return fmt.Errorf(
  93  				"found invalid character '%c' only ones from '%s' allowed\n",
  94  				str[i], bech32.Charset,
  95  			)
  96  		}
  97  	}
  98  	started := time.Now()
  99  	quit := make(chan struct{})
 100  	resC := make(chan Result)
 101  
 102  	// Handle interrupt
 103  	go func() {
 104  		c := make(chan os.Signal, 1)
 105  		<-c
 106  		close(quit)
 107  		log.I.Ln("\rinterrupt signal received")
 108  		os.Exit(0)
 109  	}()
 110  
 111  	var wg sync.WaitGroup
 112  	var counter int64
 113  	for i := 0; i < threads; i++ {
 114  		log.D.F("starting up worker %d", i)
 115  		go mine(str, where, quit, resC, &wg, &counter)
 116  	}
 117  	tick := time.NewTicker(time.Second * 5)
 118  	var res Result
 119  out:
 120  	for {
 121  		select {
 122  		case <-tick.C:
 123  			workingFor := time.Now().Sub(started)
 124  			wm := workingFor % time.Second
 125  			workingFor -= wm
 126  			fmt.Printf(
 127  				" working for %v, attempts %d",
 128  				workingFor, atomic.LoadInt64(&counter),
 129  			)
 130  		case r := <-resC:
 131  			// one of the workers found the solution
 132  			res = r
 133  			// tell the others to stop
 134  			close(quit)
 135  			break out
 136  		}
 137  	}
 138  
 139  	// wait for all workers to stop
 140  	wg.Wait()
 141  
 142  	fmt.Printf(
 143  		"\r# generated in %d attempts using %d threads, taking %v                                                 ",
 144  		atomic.LoadInt64(&counter), args.Threads, time.Now().Sub(started),
 145  	)
 146  	fmt.Printf(
 147  		"\nHSEC = %s\nHPUB = %s\n",
 148  		hex.EncodeToString(res.sec),
 149  		hex.EncodeToString(res.pub),
 150  	)
 151  	nsec, _ := bech32encoding.BinToNsec(res.sec)
 152  	fmt.Printf("NSEC = %s\nNPUB = %s\n", nsec, res.npub)
 153  	return
 154  }
 155  
 156  func mine(
 157  	str string, where int, quit <-chan struct{}, resC chan Result, wg *sync.WaitGroup,
 158  	counter *int64,
 159  ) {
 160  	wg.Add(1)
 161  	defer wg.Done()
 162  
 163  	var r Result
 164  	var e error
 165  	found := false
 166  out:
 167  	for {
 168  		select {
 169  		case <-quit:
 170  			if found {
 171  				// send back the result
 172  				log.D.Ln("sending back result")
 173  				resC <- r
 174  				log.D.Ln("sent")
 175  			} else {
 176  				log.D.Ln("other thread found it")
 177  			}
 178  			break out
 179  		default:
 180  		}
 181  		atomic.AddInt64(counter, 1)
 182  		r.sec, r.pub, e = Gen()
 183  		if e != nil {
 184  			log.E.Ln("error generating key: '%v' worker stopping", e)
 185  			break out
 186  		}
 187  		if r.npub, e = bech32encoding.BinToNpub(r.pub); e != nil {
 188  			log.E.Ln("fatal error generating npub: %s", e)
 189  			break out
 190  		}
 191  		fmt.Printf("\rgenerating key: %s", r.npub)
 192  		switch where {
 193  		case PositionBeginning:
 194  			if bytes.HasPrefix(r.npub, append(prefix, []byte(str)...)) {
 195  				found = true
 196  				// Signal quit by sending result
 197  				select {
 198  				case resC <- r:
 199  				default:
 200  				}
 201  				return
 202  			}
 203  		case PositionEnding:
 204  			if bytes.HasSuffix(r.npub, []byte(str)) {
 205  				found = true
 206  				select {
 207  				case resC <- r:
 208  				default:
 209  				}
 210  				return
 211  			}
 212  		case PositionContains:
 213  			if bytes.Contains(r.npub, []byte(str)) {
 214  				found = true
 215  				select {
 216  				case resC <- r:
 217  				default:
 218  				}
 219  				return
 220  			}
 221  		}
 222  	}
 223  }
 224  
 225  func Gen() (skb, pkb []byte, err error) {
 226  	sign, err := p8k.New()
 227  	if err != nil {
 228  		return nil, nil, err
 229  	}
 230  	if err = sign.Generate(); chk.E(err) {
 231  		return
 232  	}
 233  	skb, pkb = sign.Sec(), sign.Pub()
 234  	return
 235  }
 236