Add vanitygen command

pull/17/head
Oliver Gugger 4 years ago
parent 7df9222d0c
commit 3a8d95c4ba
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -19,6 +19,7 @@
+ [showrootkey](#showrootkey)
+ [summary](#summary)
+ [sweeptimelock](#sweeptimelock)
+ [vanitygen](#vanitygen)
+ [walletinfo](#walletinfo)
This tool provides helper functions that can be used to rescue funds locked in
@ -219,6 +220,7 @@ Available commands:
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
summary Compile a summary about the current state of channels.
sweeptimelock Sweep the force-closed state after the time lock has expired.
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given p
walletinfo Shows relevant information about an lnd wallet.db file and optionally extracts the BIP32 HD root key.
```
@ -541,6 +543,36 @@ chantools --fromsummary results/forceclose-xxxx-yyyy.json \
--sweepaddr bc1q.....
```
### vanitygen
```
Usage:
chantools [OPTIONS] vanitygen [vanitygen-OPTIONS]
[vanitygen command options]
--prefix= Hex encoded prefix to find in node public key.
--threads= Number of parallel threads. (default: 4)
```
Try random lnd compatible seeds until one is found that produces a node identity
public key that starts with the given prefix.
Example command:
```bash
chantools vanitygen --prefix 022222 --threads 8
```
Example output:
```text
Running vanitygen on 8 threads. Prefix bit length is 17, expecting to approach
probability p=1.0 after 131,072 seeds.
Tested 185k seeds, p=1.41296, speed=14k/s, elapsed=13s
Looking for 022222, found pubkey: 022222f015540ddde9bdf7c95b24f1d44f7ea6ab69bec83d6fbe622296d64b51d6
with seed: [ability roast pear stomach wink cable tube trumpet shy caught hunt someone border organ spoon only prepare calm silent million tobacco chaos normal phone]
```
### walletinfo
```text

@ -61,13 +61,13 @@ func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
for i, v := range English {
wordMap[v] = i
}
// Decode the words into a big.Int.
b := big.NewInt(0)
for _, v := range mnemonicSlice {
index, found := wordMap[v]
if found == false {
return nil, fmt.Errorf("word `%v` not found in " +
return nil, fmt.Errorf("word `%v` not found in "+
"reverse map", v)
}
var wordBytes [2]byte

@ -2070,4 +2070,4 @@ zebra
zero
zone
zoo
`
`

@ -0,0 +1,107 @@
// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package fasthd
import (
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"errors"
"math/big"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
)
const (
HardenedKeyStart = 0x80000000 // 2^31
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
keyLen = 33
)
var (
ErrInvalidChild = errors.New("the extended key at this index is invalid")
ErrUnusableSeed = errors.New("unusable seed")
masterKey = []byte("Bitcoin seed")
)
type FastDerivation struct {
key []byte
chainCode []byte
version []byte
scratch [keyLen + 4]byte
}
func (k *FastDerivation) PubKeyBytes() []byte {
pkx, pky := btcec.S256().ScalarBaseMult(k.key)
pubKey := btcec.PublicKey{Curve: btcec.S256(), X: pkx, Y: pky}
return pubKey.SerializeCompressed()
}
func (k *FastDerivation) Child(i uint32) error {
isChildHardened := i >= HardenedKeyStart
if isChildHardened {
copy(k.scratch[1:], k.key)
} else {
copy(k.scratch[:], k.PubKeyBytes())
}
binary.BigEndian.PutUint32(k.scratch[keyLen:], i)
hmac512 := hmac.New(sha512.New, k.chainCode)
hmac512.Write(k.scratch[:])
ilr := hmac512.Sum(nil)
il := ilr[:len(ilr)/2]
childChainCode := ilr[len(ilr)/2:]
ilNum := new(big.Int).SetBytes(il)
if ilNum.Cmp(btcec.S256().N) >= 0 || ilNum.Sign() == 0 {
return ErrInvalidChild
}
keyNum := new(big.Int).SetBytes(k.key)
ilNum.Add(ilNum, keyNum)
ilNum.Mod(ilNum, btcec.S256().N)
k.key = ilNum.Bytes()
k.chainCode = childChainCode
return nil
}
func (k *FastDerivation) ChildPath(path []uint32) error {
for _, pathPart := range path {
if err := k.Child(pathPart); err != nil {
return err
}
}
return nil
}
func NewFastDerivation(seed []byte, net *chaincfg.Params) (*FastDerivation, error) {
// First take the HMAC-SHA512 of the master key and the seed data:
// I = HMAC-SHA512(Key = "Bitcoin seed", Data = S)
hmac512 := hmac.New(sha512.New, masterKey)
hmac512.Write(seed)
lr := hmac512.Sum(nil)
// Split "I" into two 32-byte sequences Il and Ir where:
// Il = master secret key
// Ir = master chain code
secretKey := lr[:len(lr)/2]
chainCode := lr[len(lr)/2:]
// Ensure the key in usable.
secretKeyNum := new(big.Int).SetBytes(secretKey)
if secretKeyNum.Cmp(btcec.S256().N) >= 0 || secretKeyNum.Sign() == 0 {
return nil, ErrUnusableSeed
}
return &FastDerivation{
key: secretKey,
chainCode: chainCode,
version: net.HDPrivateKeyID[:],
}, nil
}

@ -28,7 +28,7 @@ func (c *deriveKeyCommand) Execute(_ []string) error {
switch {
case c.BIP39:
extendedKey, err = btc.ReadMnemonicFromTerminal(chainParams)
case c.RootKey != "":
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)

@ -67,7 +67,7 @@ func runCommandParser() error {
// Parse command line.
parser := flags.NewParser(cfg, flags.Default)
log.Infof("chantools version v%s commit %s", version, Commit)
_, _ = parser.AddCommand(
"summary", "Compile a summary about the current state of "+
@ -132,6 +132,11 @@ func runCommandParser() error {
"compacting it in the process.", "",
&compactDBCommand{},
)
_, _ = parser.AddCommand(
"vanitygen", "Generate a seed with a custom lnd node identity "+
"public key that starts with the given prefix.", "",
&vanityGenCommand{},
)
// TODO: uncomment when command is fully implemented.
//_, _ = parser.AddCommand(
// "rescuefunding", "Rescue funds locked in a funding multisig "+

@ -0,0 +1,175 @@
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"math"
"runtime"
"strconv"
"sync"
"time"
"github.com/guggero/chantools/btc/fasthd"
"github.com/guggero/chantools/lnd"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/keychain"
)
var (
nodeKeyDerivationPath = "m/1017'/%d'/%d'/0/0"
)
type vanityGenCommand struct {
Prefix string `long:"prefix" description:"Hex encoded prefix to find in node public key."`
Threads int `long:"threads" description:"Number of parallel threads." default:"4"`
}
func (c *vanityGenCommand) Execute(_ []string) error {
setupChainParams(cfg)
prefixBytes, err := hex.DecodeString(c.Prefix)
if err != nil {
return fmt.Errorf("hex decoding of prefix failed: %v", err)
}
if len(prefixBytes) < 2 {
return fmt.Errorf("prefix must be at least 2 bytes")
}
if !(prefixBytes[0] == 0x02 || prefixBytes[0] == 0x03) {
return fmt.Errorf("prefix must start with 02 or 03 because " +
"it's an EC public key")
}
path, err := lnd.ParsePath(fmt.Sprintf(
nodeKeyDerivationPath, chainParams.HDCoinType,
keychain.KeyFamilyNodeKey,
))
if err != nil {
return err
}
numBits := ((len(prefixBytes) - 1) * 8) + 1
numTries := math.Pow(2, float64(numBits))
fmt.Printf("Running vanitygen on %d threads. Prefix bit length is %d, "+
"expecting to approach\nprobability p=1.0 after %s seeds.\n",
c.Threads, numBits, format(int64(numTries)))
runtime.GOMAXPROCS(c.Threads)
var (
mtx sync.Mutex
globalCount uint64
abort = make(chan struct{})
start = time.Now()
)
for i := 0; i < c.Threads; i++ {
go func() {
var (
entropy [16]byte
count uint64
)
for {
select {
case <-abort:
return
default:
}
if _, err := rand.Read(entropy[:]); err != nil {
log.Error(err)
}
rootKey, err := fasthd.NewFastDerivation(
entropy[:], chainParams,
)
if err != nil {
log.Error(err)
}
err = rootKey.ChildPath(path)
if err != nil {
log.Error(err)
}
pubKeyBytes := rootKey.PubKeyBytes()
if bytes.HasPrefix(
pubKeyBytes, prefixBytes,
) {
seed, err := aezeed.New(
aezeed.CipherSeedVersion,
&entropy, time.Now(),
)
if err != nil {
log.Error(err)
}
mnemonic, err := seed.ToMnemonic(nil)
if err != nil {
log.Error(err)
}
fmt.Printf("\nLooking for %x, found "+
"pubkey: %x\nwith seed: %v\n",
prefixBytes, pubKeyBytes,
mnemonic)
close(abort)
return
}
if count > 0 && count%100 == 0 {
mtx.Lock()
globalCount += count
count = 0
mtx.Unlock()
}
count++
}
}()
}
lastCount := uint64(0)
for {
select {
case <-abort:
return nil
case <-time.After(1 * time.Second):
mtx.Lock()
currentCount := globalCount
mtx.Unlock()
msg := fmt.Sprintf("Tested %sk seeds, p=%.5f, "+
"speed=%dk/s, elapsed=%v",
format(int64(currentCount/1000)),
float64(currentCount)/numTries,
(currentCount-lastCount)/1000,
time.Since(start).Truncate(time.Second),
)
fmt.Printf("\r%-80s", msg)
lastCount = currentCount
}
}
}
func format(n int64) string {
in := strconv.FormatInt(n, 10)
numOfDigits := len(in)
if n < 0 {
numOfDigits-- // First character is the - sign (not a digit)
}
numOfCommas := (numOfDigits - 1) / 3
out := make([]byte, len(in)+numOfCommas)
if n < 0 {
in, out[0] = in[1:], '-'
}
for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
out[j] = in[i]
if i == 0 {
return string(out)
}
if k++; k == 3 {
j, k = j-1, 0
out[j] = ','
}
}
}

@ -15,7 +15,6 @@ require (
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191224233846-f289a39c1a00
github.com/ltcsuite/ltcd v0.0.0-20191228044241-92166e412499 // indirect
github.com/miekg/dns v1.1.26 // indirect
github.com/prometheus/common v0.4.0
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 // indirect

@ -34,8 +34,6 @@ github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422 h1:EqnrgSSg0SFWRlEZLExgjtuUR/IPnuQ6qw6nwRda4Uk=
github.com/btcsuite/btcutil v0.0.0-20191219182022-e17c9730c422/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.11.0 h1:XhwqdhEchy5a0q6R+y3F82roD2hYycPCHovgNyJS08w=
github.com/btcsuite/btcwallet v0.11.0/go.mod h1:qtPAohN1ioo0pvJt/j7bZM8ANBWlYWVCVFL0kkijs7s=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623 h1:ZuJRjucNsTmlrbZncsqzD0z3EaXrOobCx2I4lc12R4g=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623/go.mod h1:1O1uRHMPXHdwA4/od8nqYqrgclVKp+wtfXUAqHmeRvE=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
@ -45,8 +43,6 @@ github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZw
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf40SmmMkSQkoE8vCT+s=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.1.0 h1:JHAL7wZ8pX4SULabeAv/wPO9sseRWMGzE80lfVmRw6Y=
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.2.0 h1:E0+M4jHOToAvGWZ27ew5AaDAHDi6fUiXkjUJUnoEOD0=
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
@ -101,10 +97,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.6 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc=
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/guggero/lnd v0.9.0-beta-rc1.0.20200307100932-44bc5edfb7ff h1:IkvTEtay7QShH/g4XHMKMHMXFVdOAs39UjoGxDV/q10=
github.com/guggero/lnd v0.9.0-beta-rc1.0.20200307100932-44bc5edfb7ff/go.mod h1:bMBXSbO0hwk9HmZSbI04SDTHxTK/iEOvzfzQkNSIJmU=
github.com/guggero/lnd v0.9.0-beta-rc1.0.20200307101515-9e007e24ff31 h1:ohlZ5DgcJIMwQ/7grewh9lVb8zDIjUz4ctBHz61LARg=
github.com/guggero/lnd v0.9.0-beta-rc1.0.20200307101515-9e007e24ff31/go.mod h1:bMBXSbO0hwk9HmZSbI04SDTHxTK/iEOvzfzQkNSIJmU=
github.com/guggero/lnd v0.9.0-beta-rc1.0.20200307101759-2650bff06031 h1:G7UpjWLXdmFi1gYVidq6c/EJaH/eX0HixAOVxxAT/K0=
github.com/guggero/lnd v0.9.0-beta-rc1.0.20200307101759-2650bff06031/go.mod h1:bMBXSbO0hwk9HmZSbI04SDTHxTK/iEOvzfzQkNSIJmU=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@ -145,12 +137,8 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQ
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d h1:U50MHOOeL6gR3Ee/l0eMvZMpmRo+ydzmlQuIruCyCsA=
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191224233846-f289a39c1a00 h1:kncvRXZdtDGQfSJcPfFngiFiwPXfbVLor82KzlMel/s=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191224233846-f289a39c1a00/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4=
github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw=
github.com/lightningnetwork/lnd/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=

@ -42,7 +42,7 @@ func ReadAezeedFromTerminal(params *chaincfg.Params) (*hdkeychain.ExtendedKey,
mnemonicStr = numberDotsRegex.ReplaceAllString(mnemonicStr, "")
mnemonicStr = multipleSpaces.ReplaceAllString(mnemonicStr, " ")
mnemonicStr = strings.TrimSpace(mnemonicStr)
cipherSeedMnemonic := strings.Split(mnemonicStr, " ")
fmt.Println()

Loading…
Cancel
Save