Skip to content

Commit 3a8d95c

Browse files
committed
Add vanitygen command
1 parent 7df9222 commit 3a8d95c

File tree

10 files changed

+325
-19
lines changed

10 files changed

+325
-19
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
+ [showrootkey](#showrootkey)
2020
+ [summary](#summary)
2121
+ [sweeptimelock](#sweeptimelock)
22+
+ [vanitygen](#vanitygen)
2223
+ [walletinfo](#walletinfo)
2324

2425
This tool provides helper functions that can be used to rescue funds locked in
@@ -219,6 +220,7 @@ Available commands:
219220
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
220221
summary Compile a summary about the current state of channels.
221222
sweeptimelock Sweep the force-closed state after the time lock has expired.
223+
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given p
222224
walletinfo Shows relevant information about an lnd wallet.db file and optionally extracts the BIP32 HD root key.
223225
```
224226

@@ -541,6 +543,36 @@ chantools --fromsummary results/forceclose-xxxx-yyyy.json \
541543
--sweepaddr bc1q.....
542544
```
543545

546+
### vanitygen
547+
548+
```
549+
Usage:
550+
chantools [OPTIONS] vanitygen [vanitygen-OPTIONS]
551+
552+
[vanitygen command options]
553+
--prefix= Hex encoded prefix to find in node public key.
554+
--threads= Number of parallel threads. (default: 4)
555+
```
556+
557+
Try random lnd compatible seeds until one is found that produces a node identity
558+
public key that starts with the given prefix.
559+
560+
Example command:
561+
562+
```bash
563+
chantools vanitygen --prefix 022222 --threads 8
564+
```
565+
566+
Example output:
567+
568+
```text
569+
Running vanitygen on 8 threads. Prefix bit length is 17, expecting to approach
570+
probability p=1.0 after 131,072 seeds.
571+
Tested 185k seeds, p=1.41296, speed=14k/s, elapsed=13s
572+
Looking for 022222, found pubkey: 022222f015540ddde9bdf7c95b24f1d44f7ea6ab69bec83d6fbe622296d64b51d6
573+
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]
574+
```
575+
544576
### walletinfo
545577

546578
```text

bip39/bip39.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
6161
for i, v := range English {
6262
wordMap[v] = i
6363
}
64-
64+
6565
// Decode the words into a big.Int.
6666
b := big.NewInt(0)
6767
for _, v := range mnemonicSlice {
6868
index, found := wordMap[v]
6969
if found == false {
70-
return nil, fmt.Errorf("word `%v` not found in " +
70+
return nil, fmt.Errorf("word `%v` not found in "+
7171
"reverse map", v)
7272
}
7373
var wordBytes [2]byte

bip39/wordlist_english.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2070,4 +2070,4 @@ zebra
20702070
zero
20712071
zone
20722072
zoo
2073-
`
2073+
`

btc/fasthd/extendedkey.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) 2014-2016 The btcsuite developers
2+
// Use of this source code is governed by an ISC
3+
// license that can be found in the LICENSE file.
4+
5+
package fasthd
6+
7+
import (
8+
"crypto/hmac"
9+
"crypto/sha512"
10+
"encoding/binary"
11+
"errors"
12+
"math/big"
13+
14+
"github.com/btcsuite/btcd/btcec"
15+
"github.com/btcsuite/btcd/chaincfg"
16+
)
17+
18+
const (
19+
HardenedKeyStart = 0x80000000 // 2^31
20+
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
21+
keyLen = 33
22+
)
23+
24+
var (
25+
ErrInvalidChild = errors.New("the extended key at this index is invalid")
26+
ErrUnusableSeed = errors.New("unusable seed")
27+
masterKey = []byte("Bitcoin seed")
28+
)
29+
30+
type FastDerivation struct {
31+
key []byte
32+
chainCode []byte
33+
version []byte
34+
scratch [keyLen + 4]byte
35+
}
36+
37+
func (k *FastDerivation) PubKeyBytes() []byte {
38+
pkx, pky := btcec.S256().ScalarBaseMult(k.key)
39+
pubKey := btcec.PublicKey{Curve: btcec.S256(), X: pkx, Y: pky}
40+
return pubKey.SerializeCompressed()
41+
}
42+
43+
func (k *FastDerivation) Child(i uint32) error {
44+
isChildHardened := i >= HardenedKeyStart
45+
if isChildHardened {
46+
copy(k.scratch[1:], k.key)
47+
} else {
48+
copy(k.scratch[:], k.PubKeyBytes())
49+
}
50+
binary.BigEndian.PutUint32(k.scratch[keyLen:], i)
51+
52+
hmac512 := hmac.New(sha512.New, k.chainCode)
53+
hmac512.Write(k.scratch[:])
54+
ilr := hmac512.Sum(nil)
55+
56+
il := ilr[:len(ilr)/2]
57+
childChainCode := ilr[len(ilr)/2:]
58+
59+
ilNum := new(big.Int).SetBytes(il)
60+
if ilNum.Cmp(btcec.S256().N) >= 0 || ilNum.Sign() == 0 {
61+
return ErrInvalidChild
62+
}
63+
64+
keyNum := new(big.Int).SetBytes(k.key)
65+
ilNum.Add(ilNum, keyNum)
66+
ilNum.Mod(ilNum, btcec.S256().N)
67+
68+
k.key = ilNum.Bytes()
69+
k.chainCode = childChainCode
70+
71+
return nil
72+
}
73+
74+
func (k *FastDerivation) ChildPath(path []uint32) error {
75+
for _, pathPart := range path {
76+
if err := k.Child(pathPart); err != nil {
77+
return err
78+
}
79+
}
80+
return nil
81+
}
82+
83+
func NewFastDerivation(seed []byte, net *chaincfg.Params) (*FastDerivation, error) {
84+
// First take the HMAC-SHA512 of the master key and the seed data:
85+
// I = HMAC-SHA512(Key = "Bitcoin seed", Data = S)
86+
hmac512 := hmac.New(sha512.New, masterKey)
87+
hmac512.Write(seed)
88+
lr := hmac512.Sum(nil)
89+
90+
// Split "I" into two 32-byte sequences Il and Ir where:
91+
// Il = master secret key
92+
// Ir = master chain code
93+
secretKey := lr[:len(lr)/2]
94+
chainCode := lr[len(lr)/2:]
95+
96+
// Ensure the key in usable.
97+
secretKeyNum := new(big.Int).SetBytes(secretKey)
98+
if secretKeyNum.Cmp(btcec.S256().N) >= 0 || secretKeyNum.Sign() == 0 {
99+
return nil, ErrUnusableSeed
100+
}
101+
102+
return &FastDerivation{
103+
key: secretKey,
104+
chainCode: chainCode,
105+
version: net.HDPrivateKeyID[:],
106+
}, nil
107+
}

cmd/chantools/derivekey.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (c *deriveKeyCommand) Execute(_ []string) error {
2828
switch {
2929
case c.BIP39:
3030
extendedKey, err = btc.ReadMnemonicFromTerminal(chainParams)
31-
31+
3232
case c.RootKey != "":
3333
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
3434

cmd/chantools/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func runCommandParser() error {
6767

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

cmd/chantools/vanitygen.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"crypto/rand"
6+
"encoding/hex"
7+
"fmt"
8+
"math"
9+
"runtime"
10+
"strconv"
11+
"sync"
12+
"time"
13+
14+
"github.com/guggero/chantools/btc/fasthd"
15+
"github.com/guggero/chantools/lnd"
16+
"github.com/lightningnetwork/lnd/aezeed"
17+
"github.com/lightningnetwork/lnd/keychain"
18+
)
19+
20+
var (
21+
nodeKeyDerivationPath = "m/1017'/%d'/%d'/0/0"
22+
)
23+
24+
type vanityGenCommand struct {
25+
Prefix string `long:"prefix" description:"Hex encoded prefix to find in node public key."`
26+
Threads int `long:"threads" description:"Number of parallel threads." default:"4"`
27+
}
28+
29+
func (c *vanityGenCommand) Execute(_ []string) error {
30+
setupChainParams(cfg)
31+
32+
prefixBytes, err := hex.DecodeString(c.Prefix)
33+
if err != nil {
34+
return fmt.Errorf("hex decoding of prefix failed: %v", err)
35+
}
36+
37+
if len(prefixBytes) < 2 {
38+
return fmt.Errorf("prefix must be at least 2 bytes")
39+
}
40+
if !(prefixBytes[0] == 0x02 || prefixBytes[0] == 0x03) {
41+
return fmt.Errorf("prefix must start with 02 or 03 because " +
42+
"it's an EC public key")
43+
}
44+
45+
path, err := lnd.ParsePath(fmt.Sprintf(
46+
nodeKeyDerivationPath, chainParams.HDCoinType,
47+
keychain.KeyFamilyNodeKey,
48+
))
49+
if err != nil {
50+
return err
51+
}
52+
53+
numBits := ((len(prefixBytes) - 1) * 8) + 1
54+
numTries := math.Pow(2, float64(numBits))
55+
fmt.Printf("Running vanitygen on %d threads. Prefix bit length is %d, "+
56+
"expecting to approach\nprobability p=1.0 after %s seeds.\n",
57+
c.Threads, numBits, format(int64(numTries)))
58+
runtime.GOMAXPROCS(c.Threads)
59+
var (
60+
mtx sync.Mutex
61+
globalCount uint64
62+
abort = make(chan struct{})
63+
start = time.Now()
64+
)
65+
66+
for i := 0; i < c.Threads; i++ {
67+
go func() {
68+
var (
69+
entropy [16]byte
70+
count uint64
71+
)
72+
for {
73+
select {
74+
case <-abort:
75+
return
76+
default:
77+
}
78+
79+
if _, err := rand.Read(entropy[:]); err != nil {
80+
log.Error(err)
81+
}
82+
rootKey, err := fasthd.NewFastDerivation(
83+
entropy[:], chainParams,
84+
)
85+
if err != nil {
86+
log.Error(err)
87+
}
88+
err = rootKey.ChildPath(path)
89+
if err != nil {
90+
log.Error(err)
91+
}
92+
pubKeyBytes := rootKey.PubKeyBytes()
93+
94+
if bytes.HasPrefix(
95+
pubKeyBytes, prefixBytes,
96+
) {
97+
seed, err := aezeed.New(
98+
aezeed.CipherSeedVersion,
99+
&entropy, time.Now(),
100+
)
101+
if err != nil {
102+
log.Error(err)
103+
}
104+
mnemonic, err := seed.ToMnemonic(nil)
105+
if err != nil {
106+
log.Error(err)
107+
}
108+
fmt.Printf("\nLooking for %x, found "+
109+
"pubkey: %x\nwith seed: %v\n",
110+
prefixBytes, pubKeyBytes,
111+
mnemonic)
112+
113+
close(abort)
114+
return
115+
}
116+
117+
if count > 0 && count%100 == 0 {
118+
mtx.Lock()
119+
globalCount += count
120+
count = 0
121+
mtx.Unlock()
122+
}
123+
count++
124+
}
125+
}()
126+
}
127+
128+
lastCount := uint64(0)
129+
for {
130+
select {
131+
case <-abort:
132+
return nil
133+
case <-time.After(1 * time.Second):
134+
mtx.Lock()
135+
currentCount := globalCount
136+
mtx.Unlock()
137+
138+
msg := fmt.Sprintf("Tested %sk seeds, p=%.5f, "+
139+
"speed=%dk/s, elapsed=%v",
140+
format(int64(currentCount/1000)),
141+
float64(currentCount)/numTries,
142+
(currentCount-lastCount)/1000,
143+
time.Since(start).Truncate(time.Second),
144+
)
145+
fmt.Printf("\r%-80s", msg)
146+
147+
lastCount = currentCount
148+
}
149+
}
150+
}
151+
152+
func format(n int64) string {
153+
in := strconv.FormatInt(n, 10)
154+
numOfDigits := len(in)
155+
if n < 0 {
156+
numOfDigits-- // First character is the - sign (not a digit)
157+
}
158+
numOfCommas := (numOfDigits - 1) / 3
159+
160+
out := make([]byte, len(in)+numOfCommas)
161+
if n < 0 {
162+
in, out[0] = in[1:], '-'
163+
}
164+
165+
for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
166+
out[j] = in[i]
167+
if i == 0 {
168+
return string(out)
169+
}
170+
if k++; k == 3 {
171+
j, k = j-1, 0
172+
out[j] = ','
173+
}
174+
}
175+
}

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ require (
1515
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191224233846-f289a39c1a00
1616
github.com/ltcsuite/ltcd v0.0.0-20191228044241-92166e412499 // indirect
1717
github.com/miekg/dns v1.1.26 // indirect
18-
github.com/prometheus/common v0.4.0
1918
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
2019
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
2120
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 // indirect

0 commit comments

Comments
 (0)