You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
5.0 KiB
235 lines
5.0 KiB
// 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" |
|
"sync/atomic" |
|
"time" |
|
|
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/log" |
|
|
|
"git.mleku.dev/mleku/nostr/crypto/ec/bech32" |
|
"git.mleku.dev/mleku/nostr/encoders/bech32encoding" |
|
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k" |
|
|
|
"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() { |
|
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.E(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 := make(chan struct{}) |
|
resC := make(chan Result) |
|
|
|
// Handle interrupt |
|
go func() { |
|
c := make(chan os.Signal, 1) |
|
<-c |
|
close(quit) |
|
log.I.Ln("\rinterrupt signal received") |
|
os.Exit(0) |
|
}() |
|
|
|
var wg sync.WaitGroup |
|
var counter int64 |
|
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, atomic.LoadInt64(&counter), |
|
) |
|
case r := <-resC: |
|
// one of the workers found the solution |
|
res = r |
|
// tell the others to stop |
|
close(quit) |
|
break out |
|
} |
|
} |
|
|
|
// wait for all workers to stop |
|
wg.Wait() |
|
|
|
fmt.Printf( |
|
"\r# generated in %d attempts using %d threads, taking %v ", |
|
atomic.LoadInt64(&counter), 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 <-chan struct{}, resC chan Result, wg *sync.WaitGroup, |
|
counter *int64, |
|
) { |
|
wg.Add(1) |
|
defer wg.Done() |
|
|
|
var r Result |
|
var e error |
|
found := false |
|
out: |
|
for { |
|
select { |
|
case <-quit: |
|
if found { |
|
// send back the result |
|
log.D.Ln("sending back result") |
|
resC <- r |
|
log.D.Ln("sent") |
|
} else { |
|
log.D.Ln("other thread found it") |
|
} |
|
break out |
|
default: |
|
} |
|
atomic.AddInt64(counter, 1) |
|
r.sec, r.pub, e = Gen() |
|
if e != nil { |
|
log.E.Ln("error generating key: '%v' worker stopping", e) |
|
break out |
|
} |
|
if r.npub, e = bech32encoding.BinToNpub(r.pub); e != nil { |
|
log.E.Ln("fatal error generating npub: %s", 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 |
|
// Signal quit by sending result |
|
select { |
|
case resC <- r: |
|
default: |
|
} |
|
return |
|
} |
|
case PositionEnding: |
|
if bytes.HasSuffix(r.npub, []byte(str)) { |
|
found = true |
|
select { |
|
case resC <- r: |
|
default: |
|
} |
|
return |
|
} |
|
case PositionContains: |
|
if bytes.Contains(r.npub, []byte(str)) { |
|
found = true |
|
select { |
|
case resC <- r: |
|
default: |
|
} |
|
return |
|
} |
|
} |
|
} |
|
} |
|
|
|
func Gen() (skb, pkb []byte, err error) { |
|
sign, err := p8k.New() |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
if err = sign.Generate(); chk.E(err) { |
|
return |
|
} |
|
skb, pkb = sign.Sec(), sign.Pub() |
|
return |
|
}
|
|
|