From 3a8d95c4bace285fca58da457b14e99578c4575c Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 8 Aug 2020 12:17:01 +0200 Subject: [PATCH] Add vanitygen command --- README.md | 32 +++++++ bip39/bip39.go | 4 +- bip39/wordlist_english.go | 2 +- btc/fasthd/extendedkey.go | 107 +++++++++++++++++++++++ cmd/chantools/derivekey.go | 2 +- cmd/chantools/main.go | 7 +- cmd/chantools/vanitygen.go | 175 +++++++++++++++++++++++++++++++++++++ go.mod | 1 - go.sum | 12 --- lnd/aezeed.go | 2 +- 10 files changed, 325 insertions(+), 19 deletions(-) create mode 100644 btc/fasthd/extendedkey.go create mode 100644 cmd/chantools/vanitygen.go diff --git a/README.md b/README.md index 6aa9fbe..64fcd1d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bip39/bip39.go b/bip39/bip39.go index 480c5e8..e268803 100644 --- a/bip39/bip39.go +++ b/bip39/bip39.go @@ -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 diff --git a/bip39/wordlist_english.go b/bip39/wordlist_english.go index c40a6e6..e0eb0c5 100644 --- a/bip39/wordlist_english.go +++ b/bip39/wordlist_english.go @@ -2070,4 +2070,4 @@ zebra zero zone zoo -` \ No newline at end of file +` diff --git a/btc/fasthd/extendedkey.go b/btc/fasthd/extendedkey.go new file mode 100644 index 0000000..b157407 --- /dev/null +++ b/btc/fasthd/extendedkey.go @@ -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 +} diff --git a/cmd/chantools/derivekey.go b/cmd/chantools/derivekey.go index 5ae2eea..8fe54e0 100644 --- a/cmd/chantools/derivekey.go +++ b/cmd/chantools/derivekey.go @@ -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) diff --git a/cmd/chantools/main.go b/cmd/chantools/main.go index 9b44b12..bb6f48a 100644 --- a/cmd/chantools/main.go +++ b/cmd/chantools/main.go @@ -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 "+ diff --git a/cmd/chantools/vanitygen.go b/cmd/chantools/vanitygen.go new file mode 100644 index 0000000..3a350db --- /dev/null +++ b/cmd/chantools/vanitygen.go @@ -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] = ',' + } + } +} diff --git a/go.mod b/go.mod index c3bdcf9..0d7099c 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index f190eb7..11e7024 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/lnd/aezeed.go b/lnd/aezeed.go index 67ea390..9ac9119 100644 --- a/lnd/aezeed.go +++ b/lnd/aezeed.go @@ -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()