├── README.md
├── accounts
├── accounts.go
├── errors.go
├── hd.go
├── keystore
│ ├── account_cache.go
│ ├── account_cache_test.go
│ ├── cracker.go
│ ├── key.go
│ ├── keystore.go
│ ├── keystore_passphrase.go
│ ├── keystore_passphrase_test.go
│ ├── keystore_plain.go
│ ├── keystore_test.go
│ ├── plain_test.go
│ ├── presale.go
│ ├── testdata
│ │ ├── dupes
│ │ │ ├── 1
│ │ │ ├── 2
│ │ │ └── foo
│ │ ├── keystore
│ │ │ ├── .hiddenfile
│ │ │ ├── README
│ │ │ ├── UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
│ │ │ ├── aaa
│ │ │ ├── empty
│ │ │ ├── foo
│ │ │ │ └── fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e
│ │ │ ├── garbage
│ │ │ ├── no-address
│ │ │ ├── zero
│ │ │ └── zzz
│ │ ├── v1
│ │ │ └── cb61d5a9c4896fb9658090b597ef0e7be6f7b67e
│ │ │ │ └── cb61d5a9c4896fb9658090b597ef0e7be6f7b67e
│ │ ├── v1_test_vector.json
│ │ ├── v3_test_vector.json
│ │ └── very-light-scrypt.json
│ ├── wallet.go
│ ├── watch.go
│ └── watch_fallback.go
├── manager.go
└── url.go
├── build.bash
├── dmg_pass.bash
├── ethcracker-linux
├── ethcracker-macos
├── ethcracker-macos-arm64
├── ethcracker-windows.exe
├── go.mod
├── go.sum
├── main.go
├── run.bash
└── tmp.txt
/README.md:
--------------------------------------------------------------------------------
1 | # Ethereum password cracker
2 |
3 | If you forgot your password for Ethereum private key or presale file, but think you still remember
4 | the list of possible substrings from which you constructed the password, then try this program to
5 | routinly go through all the possible combinations and find the working password.
6 |
7 | PLEASE DO NOT TRUST ANYONE TO COMPILE THE PROGRAM FOR YOU. ALWAYS USE THE SOURCE CODE DOWNLOADED FROM
8 | THE GITHUB. THIS WAY YOU CAN BE SURE, THE PROGRAM DOES NOT HAVE ANY MALICIOUS CODE !!!
9 |
10 | # Gitter Channel
11 |
12 | https://gitter.im/lexansoft/ethcracker
13 |
14 | # Usage
15 |
16 | ethcracker -pk ~/test/pk.txt -t ~/test/templates.txt
17 |
18 | -pk path to the private key file
19 | -t path to the template file
20 | -l path to the file with all the possible variants (every line has one variant) If -l is specified, -t is ignored
21 | -presale for cracking presale JSON file
22 | -threads Number of threads
23 | -v Verbosity ( 0, 1, 2 )
24 | -start_from Skip first N combinations ( you can specify N as percentage. F.e. : 30% )
25 | -keep_order Keep the order of the lines ( no permutations )
26 | -re Report every N-th combination
27 | -dump path Just dump all the variants into text file
28 | -min_len Minimum password length
29 | -max_len Maximum password length
30 |
31 |
32 | # Template file format
33 |
34 | Every line contains the possible variants of the substring. For example file:
35 |
36 | a1 a2 a3
37 | b
38 |
39 | will generate all those combinations
40 |
41 | a1
42 | a2
43 | a3
44 | b
45 | a1b
46 | ba1
47 | a2b
48 | ba2
49 | a3b
50 | ba3
51 |
52 | Note: you can use \s to specify white space. ( "a\sb" means "a b" )
53 |
54 |
55 | # Template line flags
56 |
57 | You can also specify some keys for every line.
58 |
59 | ~a always use some value from this string
60 | ~c Try both: capitalized and not-capitalized versions of all words.
61 |
62 | For example the template file
63 |
64 | a
65 | ~ac test
66 |
67 | will generate all those combinations
68 |
69 | test
70 | Test
71 | atest
72 | aTest
73 | testa
74 | Testa
75 |
76 |
77 | # Installing
78 |
79 | Install Go Language
80 |
81 |
82 | git clone https://github.com/lexansoft/ethcracker
83 | cd ethcracker
84 |
85 | go get github.com/ethereum/go-ethereum
86 | go get github.com/pborman/uuid
87 | go get golang.org/x/crypto/pbkdf2
88 | go get golang.org/x/crypto/ripemd160
89 | go get golang.org/x/crypto/scrypt
90 | go get github.com/rjeczalik/notify
91 |
92 |
93 | # To update all previously installed packages use this:
94 |
95 | go get -u all
96 |
97 | # Run the cracker
98 |
99 | go run src/ethcracker.go -pk PATH_TO_FILE -t PATH_TO_TEMPLATE_FILE -threads 4
100 |
101 | # Installing on Windows
102 |
103 | On windows you need to install the Chocolatey: https://chocolatey.org
104 |
105 | Then install git, golang and mingw
106 |
107 | choco install git
108 | choco install golang
109 | choco install mingw
110 |
111 | After that make all the steps from the Installing section.
112 |
113 | # Cracking your Mac DMG file password
114 | If you stored your keys in the encrypted mac DMG image and forgot the password, do this:
115 |
116 | 1. dump all the possible variants of your password into a file
117 |
118 | go run src/ethcracker.go ... -dump ~/v.txt
119 |
120 | 2. Use dmg_pass.bash script to try all the variants form v.txt
121 |
122 | ./dmg_pass.bash v.txt your.dmg
123 |
124 | or
125 |
126 | ./dmg_pass.bash v.txt your.dmg N -- to skip first N lines
127 |
128 | # Donation
129 |
130 | If this program helped you to restore the password, please donate some ETH to the address:
131 |
132 | 0x281694Fabfdd9735e01bB59942B18c469b6e3df6
133 |
134 | Thank you
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/accounts/accounts.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | // Package accounts implements high level Ethereum account management.
18 | package accounts
19 |
20 | import (
21 | "math/big"
22 |
23 | ethereum "github.com/ethereum/go-ethereum"
24 | "github.com/ethereum/go-ethereum/common"
25 | "github.com/ethereum/go-ethereum/core/types"
26 | "github.com/ethereum/go-ethereum/event"
27 | )
28 |
29 | // Account represents an Ethereum account located at a specific location defined
30 | // by the optional URL field.
31 | type Account struct {
32 | Address common.Address `json:"address"` // Ethereum account address derived from the key
33 | URL URL `json:"url"` // Optional resource locator within a backend
34 | }
35 |
36 | // Wallet represents a software or hardware wallet that might contain one or more
37 | // accounts (derived from the same seed).
38 | type Wallet interface {
39 | // URL retrieves the canonical path under which this wallet is reachable. It is
40 | // user by upper layers to define a sorting order over all wallets from multiple
41 | // backends.
42 | URL() URL
43 |
44 | // Status returns a textual status to aid the user in the current state of the
45 | // wallet.
46 | Status() string
47 |
48 | // Open initializes access to a wallet instance. It is not meant to unlock or
49 | // decrypt account keys, rather simply to establish a connection to hardware
50 | // wallets and/or to access derivation seeds.
51 | //
52 | // The passphrase parameter may or may not be used by the implementation of a
53 | // particular wallet instance. The reason there is no passwordless open method
54 | // is to strive towards a uniform wallet handling, oblivious to the different
55 | // backend providers.
56 | //
57 | // Please note, if you open a wallet, you must close it to release any allocated
58 | // resources (especially important when working with hardware wallets).
59 | Open(passphrase string) error
60 |
61 | // Close releases any resources held by an open wallet instance.
62 | Close() error
63 |
64 | // Accounts retrieves the list of signing accounts the wallet is currently aware
65 | // of. For hierarchical deterministic wallets, the list will not be exhaustive,
66 | // rather only contain the accounts explicitly pinned during account derivation.
67 | Accounts() []Account
68 |
69 | // Contains returns whether an account is part of this particular wallet or not.
70 | Contains(account Account) bool
71 |
72 | // Derive attempts to explicitly derive a hierarchical deterministic account at
73 | // the specified derivation path. If requested, the derived account will be added
74 | // to the wallet's tracked account list.
75 | Derive(path DerivationPath, pin bool) (Account, error)
76 |
77 | // SelfDerive sets a base account derivation path from which the wallet attempts
78 | // to discover non zero accounts and automatically add them to list of tracked
79 | // accounts.
80 | //
81 | // Note, self derivaton will increment the last component of the specified path
82 | // opposed to descending into a child path to allow discovering accounts starting
83 | // from non zero components.
84 | //
85 | // You can disable automatic account discovery by calling SelfDerive with a nil
86 | // chain state reader.
87 | SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
88 |
89 | // SignHash requests the wallet to sign the given hash.
90 | //
91 | // It looks up the account specified either solely via its address contained within,
92 | // or optionally with the aid of any location metadata from the embedded URL field.
93 | //
94 | // If the wallet requires additional authentication to sign the request (e.g.
95 | // a password to decrypt the account, or a PIN code o verify the transaction),
96 | // an AuthNeededError instance will be returned, containing infos for the user
97 | // about which fields or actions are needed. The user may retry by providing
98 | // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
99 | // the account in a keystore).
100 | SignHash(account Account, hash []byte) ([]byte, error)
101 |
102 | // SignTx requests the wallet to sign the given transaction.
103 | //
104 | // It looks up the account specified either solely via its address contained within,
105 | // or optionally with the aid of any location metadata from the embedded URL field.
106 | //
107 | // If the wallet requires additional authentication to sign the request (e.g.
108 | // a password to decrypt the account, or a PIN code o verify the transaction),
109 | // an AuthNeededError instance will be returned, containing infos for the user
110 | // about which fields or actions are needed. The user may retry by providing
111 | // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
112 | // the account in a keystore).
113 | SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
114 |
115 | // SignHashWithPassphrase requests the wallet to sign the given hash with the
116 | // given passphrase as extra authentication information.
117 | //
118 | // It looks up the account specified either solely via its address contained within,
119 | // or optionally with the aid of any location metadata from the embedded URL field.
120 | SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
121 |
122 | // SignTxWithPassphrase requests the wallet to sign the given transaction, with the
123 | // given passphrase as extra authentication information.
124 | //
125 | // It looks up the account specified either solely via its address contained within,
126 | // or optionally with the aid of any location metadata from the embedded URL field.
127 | SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
128 | }
129 |
130 | // Backend is a "wallet provider" that may contain a batch of accounts they can
131 | // sign transactions with and upon request, do so.
132 | type Backend interface {
133 | // Wallets retrieves the list of wallets the backend is currently aware of.
134 | //
135 | // The returned wallets are not opened by default. For software HD wallets this
136 | // means that no base seeds are decrypted, and for hardware wallets that no actual
137 | // connection is established.
138 | //
139 | // The resulting wallet list will be sorted alphabetically based on its internal
140 | // URL assigned by the backend. Since wallets (especially hardware) may come and
141 | // go, the same wallet might appear at a different positions in the list during
142 | // subsequent retrievals.
143 | Wallets() []Wallet
144 |
145 | // Subscribe creates an async subscription to receive notifications when the
146 | // backend detects the arrival or departure of a wallet.
147 | Subscribe(sink chan<- WalletEvent) event.Subscription
148 | }
149 |
150 | // WalletEvent is an event fired by an account backend when a wallet arrival or
151 | // departure is detected.
152 | type WalletEvent struct {
153 | Wallet Wallet // Wallet instance arrived or departed
154 | Arrive bool // Whether the wallet was added or removed
155 | }
156 |
--------------------------------------------------------------------------------
/accounts/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package accounts
18 |
19 | import (
20 | "errors"
21 | "fmt"
22 | )
23 |
24 | // ErrUnknownAccount is returned for any requested operation for which no backend
25 | // provides the specified account.
26 | var ErrUnknownAccount = errors.New("unknown account")
27 |
28 | // ErrUnknownWallet is returned for any requested operation for which no backend
29 | // provides the specified wallet.
30 | var ErrUnknownWallet = errors.New("unknown wallet")
31 |
32 | // ErrNotSupported is returned when an operation is requested from an account
33 | // backend that it does not support.
34 | var ErrNotSupported = errors.New("not supported")
35 |
36 | // ErrInvalidPassphrase is returned when a decryption operation receives a bad
37 | // passphrase.
38 | var ErrInvalidPassphrase = errors.New("invalid passphrase")
39 |
40 | // ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
41 | // secodn time.
42 | var ErrWalletAlreadyOpen = errors.New("wallet already open")
43 |
44 | // ErrWalletClosed is returned if a wallet is attempted to be opened the
45 | // secodn time.
46 | var ErrWalletClosed = errors.New("wallet closed")
47 |
48 | // AuthNeededError is returned by backends for signing requests where the user
49 | // is required to provide further authentication before signing can succeed.
50 | //
51 | // This usually means either that a password needs to be supplied, or perhaps a
52 | // one time PIN code displayed by some hardware device.
53 | type AuthNeededError struct {
54 | Needed string // Extra authentication the user needs to provide
55 | }
56 |
57 | // NewAuthNeededError creates a new authentication error with the extra details
58 | // about the needed fields set.
59 | func NewAuthNeededError(needed string) error {
60 | return &AuthNeededError{
61 | Needed: needed,
62 | }
63 | }
64 |
65 | // Error implements the standard error interfacel.
66 | func (err *AuthNeededError) Error() string {
67 | return fmt.Sprintf("authentication needed: %s", err.Needed)
68 | }
69 |
--------------------------------------------------------------------------------
/accounts/hd.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package accounts
18 |
19 | import (
20 | "errors"
21 | "fmt"
22 | "math"
23 | "math/big"
24 | "strings"
25 | )
26 |
27 | // DefaultRootDerivationPath is the root path to which custom derivation endpoints
28 | // are appended. As such, the first account will be at m/44'/60'/0'/0, the second
29 | // at m/44'/60'/0'/1, etc.
30 | var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0}
31 |
32 | // DefaultBaseDerivationPath is the base path from which custom derivation endpoints
33 | // are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
34 | // at m/44'/60'/0'/1, etc.
35 | var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
36 |
37 | // DerivationPath represents the computer friendly version of a hierarchical
38 | // deterministic wallet account derivaion path.
39 | //
40 | // The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
41 | // defines derivation paths to be of the form:
42 | //
43 | // m / purpose' / coin_type' / account' / change / address_index
44 | //
45 | // The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
46 | // defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
47 | // SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
48 | // the `coin_type` 60' (or 0x8000003C) to Ethereum.
49 | //
50 | // The root path for Ethereum is m/44'/60'/0'/0 according to the specification
51 | // from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
52 | // yet whether accounts should increment the last component or the children of
53 | // that. We will go with the simpler approach of incrementing the last component.
54 | type DerivationPath []uint32
55 |
56 | // ParseDerivationPath converts a user specified derivation path string to the
57 | // internal binary representation.
58 | //
59 | // Full derivation paths need to start with the `m/` prefix, relative derivation
60 | // paths (which will get appended to the default root path) must not have prefixes
61 | // in front of the first element. Whitespace is ignored.
62 | func ParseDerivationPath(path string) (DerivationPath, error) {
63 | var result DerivationPath
64 |
65 | // Handle absolute or relative paths
66 | components := strings.Split(path, "/")
67 | switch {
68 | case len(components) == 0:
69 | return nil, errors.New("empty derivation path")
70 |
71 | case strings.TrimSpace(components[0]) == "":
72 | return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
73 |
74 | case strings.TrimSpace(components[0]) == "m":
75 | components = components[1:]
76 |
77 | default:
78 | result = append(result, DefaultRootDerivationPath...)
79 | }
80 | // All remaining components are relative, append one by one
81 | if len(components) == 0 {
82 | return nil, errors.New("empty derivation path") // Empty relative paths
83 | }
84 | for _, component := range components {
85 | // Ignore any user added whitespace
86 | component = strings.TrimSpace(component)
87 | var value uint32
88 |
89 | // Handle hardened paths
90 | if strings.HasSuffix(component, "'") {
91 | value = 0x80000000
92 | component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
93 | }
94 | // Handle the non hardened component
95 | bigval, ok := new(big.Int).SetString(component, 0)
96 | if !ok {
97 | return nil, fmt.Errorf("invalid component: %s", component)
98 | }
99 | max := math.MaxUint32 - value
100 | if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
101 | if value == 0 {
102 | return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
103 | }
104 | return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
105 | }
106 | value += uint32(bigval.Uint64())
107 |
108 | // Append and repeat
109 | result = append(result, value)
110 | }
111 | return result, nil
112 | }
113 |
114 | // String implements the stringer interface, converting a binary derivation path
115 | // to its canonical representation.
116 | func (path DerivationPath) String() string {
117 | result := "m"
118 | for _, component := range path {
119 | var hardened bool
120 | if component >= 0x80000000 {
121 | component -= 0x80000000
122 | hardened = true
123 | }
124 | result = fmt.Sprintf("%s/%d", result, component)
125 | if hardened {
126 | result += "'"
127 | }
128 | }
129 | return result
130 | }
131 |
--------------------------------------------------------------------------------
/accounts/keystore/account_cache.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "bufio"
21 | "encoding/json"
22 | "fmt"
23 | "io/ioutil"
24 | "os"
25 | "path/filepath"
26 | "sort"
27 | "strings"
28 | "sync"
29 | "time"
30 |
31 | "github.com/ethereum/go-ethereum/accounts"
32 | "github.com/ethereum/go-ethereum/common"
33 | "github.com/ethereum/go-ethereum/log"
34 | )
35 |
36 | // Minimum amount of time between cache reloads. This limit applies if the platform does
37 | // not support change notifications. It also applies if the keystore directory does not
38 | // exist yet, the code will attempt to create a watcher at most this often.
39 | const minReloadInterval = 2 * time.Second
40 |
41 | type accountsByURL []accounts.Account
42 |
43 | func (s accountsByURL) Len() int { return len(s) }
44 | func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
45 | func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
46 |
47 | // AmbiguousAddrError is returned when attempting to unlock
48 | // an address for which more than one file exists.
49 | type AmbiguousAddrError struct {
50 | Addr common.Address
51 | Matches []accounts.Account
52 | }
53 |
54 | func (err *AmbiguousAddrError) Error() string {
55 | files := ""
56 | for i, a := range err.Matches {
57 | files += a.URL.Path
58 | if i < len(err.Matches)-1 {
59 | files += ", "
60 | }
61 | }
62 | return fmt.Sprintf("multiple keys match address (%s)", files)
63 | }
64 |
65 | // accountCache is a live index of all accounts in the keystore.
66 | type accountCache struct {
67 | keydir string
68 | watcher *watcher
69 | mu sync.Mutex
70 | all accountsByURL
71 | byAddr map[common.Address][]accounts.Account
72 | throttle *time.Timer
73 | notify chan struct{}
74 | }
75 |
76 | func newAccountCache(keydir string) (*accountCache, chan struct{}) {
77 | ac := &accountCache{
78 | keydir: keydir,
79 | byAddr: make(map[common.Address][]accounts.Account),
80 | notify: make(chan struct{}, 1),
81 | }
82 | ac.watcher = newWatcher(ac)
83 | return ac, ac.notify
84 | }
85 |
86 | func (ac *accountCache) accounts() []accounts.Account {
87 | ac.maybeReload()
88 | ac.mu.Lock()
89 | defer ac.mu.Unlock()
90 | cpy := make([]accounts.Account, len(ac.all))
91 | copy(cpy, ac.all)
92 | return cpy
93 | }
94 |
95 | func (ac *accountCache) hasAddress(addr common.Address) bool {
96 | ac.maybeReload()
97 | ac.mu.Lock()
98 | defer ac.mu.Unlock()
99 | return len(ac.byAddr[addr]) > 0
100 | }
101 |
102 | func (ac *accountCache) add(newAccount accounts.Account) {
103 | ac.mu.Lock()
104 | defer ac.mu.Unlock()
105 |
106 | i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
107 | if i < len(ac.all) && ac.all[i] == newAccount {
108 | return
109 | }
110 | // newAccount is not in the cache.
111 | ac.all = append(ac.all, accounts.Account{})
112 | copy(ac.all[i+1:], ac.all[i:])
113 | ac.all[i] = newAccount
114 | ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
115 | }
116 |
117 | // note: removed needs to be unique here (i.e. both File and Address must be set).
118 | func (ac *accountCache) delete(removed accounts.Account) {
119 | ac.mu.Lock()
120 | defer ac.mu.Unlock()
121 |
122 | ac.all = removeAccount(ac.all, removed)
123 | if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
124 | delete(ac.byAddr, removed.Address)
125 | } else {
126 | ac.byAddr[removed.Address] = ba
127 | }
128 | }
129 |
130 | func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
131 | for i := range slice {
132 | if slice[i] == elem {
133 | return append(slice[:i], slice[i+1:]...)
134 | }
135 | }
136 | return slice
137 | }
138 |
139 | // find returns the cached account for address if there is a unique match.
140 | // The exact matching rules are explained by the documentation of accounts.Account.
141 | // Callers must hold ac.mu.
142 | func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
143 | // Limit search to address candidates if possible.
144 | matches := ac.all
145 | if (a.Address != common.Address{}) {
146 | matches = ac.byAddr[a.Address]
147 | }
148 | if a.URL.Path != "" {
149 | // If only the basename is specified, complete the path.
150 | if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
151 | a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
152 | }
153 | for i := range matches {
154 | if matches[i].URL == a.URL {
155 | return matches[i], nil
156 | }
157 | }
158 | if (a.Address == common.Address{}) {
159 | return accounts.Account{}, ErrNoMatch
160 | }
161 | }
162 | switch len(matches) {
163 | case 1:
164 | return matches[0], nil
165 | case 0:
166 | return accounts.Account{}, ErrNoMatch
167 | default:
168 | err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
169 | copy(err.Matches, matches)
170 | return accounts.Account{}, err
171 | }
172 | }
173 |
174 | func (ac *accountCache) maybeReload() {
175 | ac.mu.Lock()
176 | defer ac.mu.Unlock()
177 |
178 | if ac.watcher.running {
179 | return // A watcher is running and will keep the cache up-to-date.
180 | }
181 | if ac.throttle == nil {
182 | ac.throttle = time.NewTimer(0)
183 | } else {
184 | select {
185 | case <-ac.throttle.C:
186 | default:
187 | return // The cache was reloaded recently.
188 | }
189 | }
190 | ac.watcher.start()
191 | ac.reload()
192 | ac.throttle.Reset(minReloadInterval)
193 | }
194 |
195 | func (ac *accountCache) close() {
196 | ac.mu.Lock()
197 | ac.watcher.close()
198 | if ac.throttle != nil {
199 | ac.throttle.Stop()
200 | }
201 | if ac.notify != nil {
202 | close(ac.notify)
203 | ac.notify = nil
204 | }
205 | ac.mu.Unlock()
206 | }
207 |
208 | // reload caches addresses of existing accounts.
209 | // Callers must hold ac.mu.
210 | func (ac *accountCache) reload() {
211 | accounts, err := ac.scan()
212 | if err != nil {
213 | log.Debug("Failed to reload keystore contents", "err", err)
214 | }
215 | ac.all = accounts
216 | sort.Sort(ac.all)
217 | for k := range ac.byAddr {
218 | delete(ac.byAddr, k)
219 | }
220 | for _, a := range accounts {
221 | ac.byAddr[a.Address] = append(ac.byAddr[a.Address], a)
222 | }
223 | select {
224 | case ac.notify <- struct{}{}:
225 | default:
226 | }
227 | log.Debug("Reloaded keystore contents", "accounts", len(ac.all))
228 | }
229 |
230 | func (ac *accountCache) scan() ([]accounts.Account, error) {
231 | files, err := ioutil.ReadDir(ac.keydir)
232 | if err != nil {
233 | return nil, err
234 | }
235 |
236 | var (
237 | buf = new(bufio.Reader)
238 | addrs []accounts.Account
239 | keyJSON struct {
240 | Address string `json:"address"`
241 | }
242 | )
243 | for _, fi := range files {
244 | path := filepath.Join(ac.keydir, fi.Name())
245 | if skipKeyFile(fi) {
246 | log.Trace("Ignoring file on account scan", "path", path)
247 | continue
248 | }
249 | logger := log.New("path", path)
250 |
251 | fd, err := os.Open(path)
252 | if err != nil {
253 | logger.Trace("Failed to open keystore file", "err", err)
254 | continue
255 | }
256 | buf.Reset(fd)
257 | // Parse the address.
258 | keyJSON.Address = ""
259 | err = json.NewDecoder(buf).Decode(&keyJSON)
260 | addr := common.HexToAddress(keyJSON.Address)
261 | switch {
262 | case err != nil:
263 | logger.Debug("Failed to decode keystore key", "err", err)
264 | case (addr == common.Address{}):
265 | logger.Debug("Failed to decode keystore key", "err", "missing or zero address")
266 | default:
267 | addrs = append(addrs, accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}})
268 | }
269 | fd.Close()
270 | }
271 | return addrs, err
272 | }
273 |
274 | func skipKeyFile(fi os.FileInfo) bool {
275 | // Skip editor backups and UNIX-style hidden files.
276 | if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
277 | return true
278 | }
279 | // Skip misc special files, directories (yes, symlinks too).
280 | if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
281 | return true
282 | }
283 | return false
284 | }
285 |
--------------------------------------------------------------------------------
/accounts/keystore/account_cache_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "fmt"
21 | "math/rand"
22 | "os"
23 | "path/filepath"
24 | "reflect"
25 | "sort"
26 | "testing"
27 | "time"
28 |
29 | "github.com/cespare/cp"
30 | "github.com/davecgh/go-spew/spew"
31 | "github.com/ethereum/go-ethereum/accounts"
32 | "github.com/ethereum/go-ethereum/common"
33 | )
34 |
35 | var (
36 | cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
37 | cachetestAccounts = []accounts.Account{
38 | {
39 | Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
40 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
41 | },
42 | {
43 | Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
44 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
45 | },
46 | {
47 | Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
48 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
49 | },
50 | }
51 | )
52 |
53 | func TestWatchNewFile(t *testing.T) {
54 | t.Parallel()
55 |
56 | dir, ks := tmpKeyStore(t, false)
57 | defer os.RemoveAll(dir)
58 |
59 | // Ensure the watcher is started before adding any files.
60 | ks.Accounts()
61 | time.Sleep(200 * time.Millisecond)
62 |
63 | // Move in the files.
64 | wantAccounts := make([]accounts.Account, len(cachetestAccounts))
65 | for i := range cachetestAccounts {
66 | wantAccounts[i] = accounts.Account{
67 | Address: cachetestAccounts[i].Address,
68 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
69 | }
70 | if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
71 | t.Fatal(err)
72 | }
73 | }
74 |
75 | // ks should see the accounts.
76 | var list []accounts.Account
77 | for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
78 | list = ks.Accounts()
79 | if reflect.DeepEqual(list, wantAccounts) {
80 | // ks should have also received change notifications
81 | select {
82 | case <-ks.changes:
83 | default:
84 | t.Fatalf("wasn't notified of new accounts")
85 | }
86 | return
87 | }
88 | time.Sleep(d)
89 | }
90 | t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
91 | }
92 |
93 | func TestWatchNoDir(t *testing.T) {
94 | t.Parallel()
95 |
96 | // Create ks but not the directory that it watches.
97 | rand.Seed(time.Now().UnixNano())
98 | dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
99 | ks := NewKeyStore(dir, LightScryptN, LightScryptP)
100 |
101 | list := ks.Accounts()
102 | if len(list) > 0 {
103 | t.Error("initial account list not empty:", list)
104 | }
105 | time.Sleep(100 * time.Millisecond)
106 |
107 | // Create the directory and copy a key file into it.
108 | os.MkdirAll(dir, 0700)
109 | defer os.RemoveAll(dir)
110 | file := filepath.Join(dir, "aaa")
111 | if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
112 | t.Fatal(err)
113 | }
114 |
115 | // ks should see the account.
116 | wantAccounts := []accounts.Account{cachetestAccounts[0]}
117 | wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
118 | for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
119 | list = ks.Accounts()
120 | if reflect.DeepEqual(list, wantAccounts) {
121 | // ks should have also received change notifications
122 | select {
123 | case <-ks.changes:
124 | default:
125 | t.Fatalf("wasn't notified of new accounts")
126 | }
127 | return
128 | }
129 | time.Sleep(d)
130 | }
131 | t.Errorf("\ngot %v\nwant %v", list, wantAccounts)
132 | }
133 |
134 | func TestCacheInitialReload(t *testing.T) {
135 | cache, _ := newAccountCache(cachetestDir)
136 | accounts := cache.accounts()
137 | if !reflect.DeepEqual(accounts, cachetestAccounts) {
138 | t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
139 | }
140 | }
141 |
142 | func TestCacheAddDeleteOrder(t *testing.T) {
143 | cache, _ := newAccountCache("testdata/no-such-dir")
144 | cache.watcher.running = true // prevent unexpected reloads
145 |
146 | accs := []accounts.Account{
147 | {
148 | Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
149 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
150 | },
151 | {
152 | Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
153 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
154 | },
155 | {
156 | Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
157 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
158 | },
159 | {
160 | Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
161 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
162 | },
163 | {
164 | Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
165 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
166 | },
167 | {
168 | Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
169 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
170 | },
171 | {
172 | Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
173 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
174 | },
175 | }
176 | for _, a := range accs {
177 | cache.add(a)
178 | }
179 | // Add some of them twice to check that they don't get reinserted.
180 | cache.add(accs[0])
181 | cache.add(accs[2])
182 |
183 | // Check that the account list is sorted by filename.
184 | wantAccounts := make([]accounts.Account, len(accs))
185 | copy(wantAccounts, accs)
186 | sort.Sort(accountsByURL(wantAccounts))
187 | list := cache.accounts()
188 | if !reflect.DeepEqual(list, wantAccounts) {
189 | t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
190 | }
191 | for _, a := range accs {
192 | if !cache.hasAddress(a.Address) {
193 | t.Errorf("expected hasAccount(%x) to return true", a.Address)
194 | }
195 | }
196 | if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
197 | t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
198 | }
199 |
200 | // Delete a few keys from the cache.
201 | for i := 0; i < len(accs); i += 2 {
202 | cache.delete(wantAccounts[i])
203 | }
204 | cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
205 |
206 | // Check content again after deletion.
207 | wantAccountsAfterDelete := []accounts.Account{
208 | wantAccounts[1],
209 | wantAccounts[3],
210 | wantAccounts[5],
211 | }
212 | list = cache.accounts()
213 | if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
214 | t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
215 | }
216 | for _, a := range wantAccountsAfterDelete {
217 | if !cache.hasAddress(a.Address) {
218 | t.Errorf("expected hasAccount(%x) to return true", a.Address)
219 | }
220 | }
221 | if cache.hasAddress(wantAccounts[0].Address) {
222 | t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
223 | }
224 | }
225 |
226 | func TestCacheFind(t *testing.T) {
227 | dir := filepath.Join("testdata", "dir")
228 | cache, _ := newAccountCache(dir)
229 | cache.watcher.running = true // prevent unexpected reloads
230 |
231 | accs := []accounts.Account{
232 | {
233 | Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
234 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
235 | },
236 | {
237 | Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
238 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
239 | },
240 | {
241 | Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
242 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
243 | },
244 | {
245 | Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
246 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
247 | },
248 | }
249 | for _, a := range accs {
250 | cache.add(a)
251 | }
252 |
253 | nomatchAccount := accounts.Account{
254 | Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
255 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
256 | }
257 | tests := []struct {
258 | Query accounts.Account
259 | WantResult accounts.Account
260 | WantError error
261 | }{
262 | // by address
263 | {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
264 | // by file
265 | {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
266 | // by basename
267 | {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
268 | // by file and address
269 | {Query: accs[0], WantResult: accs[0]},
270 | // ambiguous address, tie resolved by file
271 | {Query: accs[2], WantResult: accs[2]},
272 | // ambiguous address error
273 | {
274 | Query: accounts.Account{Address: accs[2].Address},
275 | WantError: &AmbiguousAddrError{
276 | Addr: accs[2].Address,
277 | Matches: []accounts.Account{accs[2], accs[3]},
278 | },
279 | },
280 | // no match error
281 | {Query: nomatchAccount, WantError: ErrNoMatch},
282 | {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
283 | {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
284 | {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
285 | }
286 | for i, test := range tests {
287 | a, err := cache.find(test.Query)
288 | if !reflect.DeepEqual(err, test.WantError) {
289 | t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
290 | continue
291 | }
292 | if a != test.WantResult {
293 | t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
294 | continue
295 | }
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/accounts/keystore/cracker.go:
--------------------------------------------------------------------------------
1 | package keystore
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "time"
8 |
9 | "github.com/ethereum/go-ethereum/crypto"
10 |
11 | //"io/ioutil"
12 | // "github.com/pborman/uuid"
13 | "encoding/hex"
14 | "errors"
15 | "io/ioutil"
16 | "sync"
17 |
18 | // "strconv"
19 | "crypto/sha256"
20 |
21 | "golang.org/x/crypto/pbkdf2"
22 | )
23 |
24 | type CrackerParams struct {
25 | key_version string
26 | key_v1 *encryptedKeyJSONV1
27 | key_v3 *encryptedKeyJSONV3
28 |
29 | // for presale
30 | iv []byte
31 | cipherText []byte
32 | EthAddr string
33 |
34 | V int // Verbosity
35 | Start_from int
36 |
37 | N int
38 | Total int
39 | RE int
40 | Skipped int
41 |
42 | StartTime time.Time
43 | }
44 |
45 | var mutex = &sync.Mutex{}
46 |
47 | func LoadPresaleFile(params *CrackerParams, path string) error {
48 |
49 | preSaleKeyStruct := struct {
50 | EncSeed string
51 | EthAddr string
52 | Email string
53 | BtcAddr string
54 | }{}
55 |
56 | keyFileContent, err := ioutil.ReadFile(path)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | params.key_version = "presale"
62 |
63 | err = json.Unmarshal(keyFileContent, &preSaleKeyStruct)
64 | if err != nil {
65 | return err
66 | }
67 |
68 | params.EthAddr = preSaleKeyStruct.EthAddr
69 |
70 | encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
71 | params.iv = encSeedBytes[:16]
72 | params.cipherText = encSeedBytes[16:]
73 |
74 | println("Key file version:", params.key_version)
75 | return nil
76 | }
77 |
78 | func LoadKeyFile(params *CrackerParams, path string, log_level int) error {
79 |
80 | keyFileContent, err := ioutil.ReadFile(path)
81 | if err != nil {
82 | return err
83 | }
84 |
85 | // println( "Private key file content:", string( keyFileContent ) )
86 |
87 | params.key_version = "v3"
88 | params.key_v3, err = LoadKeyVersion3(keyFileContent)
89 |
90 | if err != nil {
91 | params.key_version = "v1"
92 | params.key_v1, err = LoadKeyVersion1(keyFileContent)
93 | if err != nil {
94 | return err
95 | }
96 | //println( "Private key JSON (version 1):", string( pk_log ) )
97 | } else {
98 | //println( "Private key JSON (version 3):", string( pk_log ) )
99 | }
100 |
101 | if log_level > 0 {
102 | println("Key file version:", params.key_version)
103 | }
104 | return nil
105 | }
106 |
107 | func LoadKeyVersion1(fileContent []byte) (*encryptedKeyJSONV1, error) {
108 | key := new(encryptedKeyJSONV1)
109 | err := json.Unmarshal(fileContent, key)
110 | return key, err
111 | }
112 |
113 | func LoadKeyVersion3(fileContent []byte) (*encryptedKeyJSONV3, error) {
114 | key := new(encryptedKeyJSONV3)
115 | err := json.Unmarshal(fileContent, key)
116 | return key, err
117 | }
118 |
119 | func Test_pass(params *CrackerParams, s string, thread int) error {
120 | var err error
121 |
122 | mutex.Lock()
123 | params.N++
124 | if params.V > 0 {
125 | if params.N+params.Skipped > params.Start_from {
126 | h := time.Since(params.StartTime).Hours() *
127 | float64(params.Total-(params.N+params.Skipped)) / float64(params.N+params.Skipped-params.Start_from)
128 |
129 | if params.N%params.RE == 0 {
130 | fmt.Printf("TH%d-> %d/%d %d%% Skipped: %d Left: %d years %d days %d hours %d minutes %v\n",
131 | thread,
132 | params.N+params.Skipped,
133 | params.Total,
134 | (params.N+params.Skipped)*100/params.Total,
135 | params.Skipped,
136 | int64(h)/(24*365), (int64(h)%(24*365))/24, int64(h)%24, int64(h*60)%60,
137 | s)
138 | }
139 |
140 | } else {
141 | if params.Start_from > 0 && (params.N+params.Skipped)%(100000) == 0 {
142 |
143 | h := time.Since(params.StartTime).Hours() *
144 | float64(params.Start_from-(params.N+params.Skipped)) / float64(params.Start_from)
145 |
146 | fmt.Printf("Skipping first %d -> %d %d%% Skipped: %d Left: %d years %d days %d hours %d minutes %v\n",
147 | params.Start_from,
148 | params.N+params.Skipped,
149 | (params.N+params.Skipped)*100/params.Start_from,
150 | params.Skipped,
151 | int64(h)/(24*365), (int64(h)%(24*365))/24, int64(h)%24, int64(h*60)%60,
152 | s)
153 |
154 | }
155 | }
156 | }
157 | mutex.Unlock()
158 | if params.N+params.Skipped < params.Start_from {
159 | return errors.New("skipped")
160 | }
161 |
162 | var addr string
163 | var pk []byte
164 | switch params.key_version {
165 | case "v3":
166 | err, addr, pk = Test_pass_v3(params.key_v3, s)
167 | case "v1":
168 | err, addr, pk = Test_pass_v1(params.key_v1, s)
169 | case "presale":
170 | err, addr, pk = Test_pass_presale(params, s)
171 | }
172 |
173 | if err == nil {
174 | println("")
175 | println("")
176 | println("-------------------------------------------------------------------------")
177 | println(" CONGRATULATIONS !!! WE FOUND YOUR PASSWORD !!!")
178 | println("-------------------------------------------------------------------------")
179 | println("")
180 | println(" Password:", s)
181 | println(" Address:", addr)
182 | println("Private Key:", hex.EncodeToString(pk))
183 | println("")
184 | println(" Do not forget to donate some ETH to the developer:")
185 | println(" Ethereum Address: 0x281694Fabfdd9735e01bB59942B18c469b6e3df6")
186 | println("-------------------------------------------------------------------------")
187 | println("")
188 | println("")
189 | os.Exit(0)
190 | }
191 |
192 | return err
193 | }
194 |
195 | func Test_pass_v1(k *encryptedKeyJSONV1, auth string) (error, string, []byte) {
196 | pk, _, err := decryptKeyV1(k, auth)
197 | return err, k.Address, pk
198 | }
199 |
200 | func Test_pass_v3(k *encryptedKeyJSONV3, auth string) (error, string, []byte) {
201 | pk, _, err := decryptKeyV3(k, auth)
202 | return err, k.Address, pk
203 | }
204 |
205 | func Test_pass_presale(params *CrackerParams, password string) (error, string, []byte) {
206 | passBytes := []byte(password)
207 | derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
208 | // _, err := aesCBCDecrypt(derivedKey, params.cipherText, params.iv)
209 |
210 | plainText, err := aesCBCDecrypt(derivedKey, params.cipherText, params.iv)
211 | if err != nil {
212 | return err, "", nil
213 | }
214 | ethPriv := crypto.Keccak256(plainText) // Sha3(plainText)
215 | ecKey, err := crypto.ToECDSA(ethPriv)
216 | if err != nil {
217 | return err, "", nil
218 | }
219 | // key = &Key {
220 | // Id: nil,
221 | // Address: PubkeyToAddress( ecKey.PublicKey),
222 | // PrivateKey: ecKey,
223 | // }
224 |
225 | Address := crypto.PubkeyToAddress(ecKey.PublicKey)
226 |
227 | // derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
228 | derivedAddr := hex.EncodeToString(Address.Bytes()) // needed because .Hex() gives leading "0x"
229 | expectedAddr := params.EthAddr
230 | if derivedAddr != expectedAddr {
231 | err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
232 | }
233 |
234 | return err, derivedAddr, ethPriv
235 | }
236 |
--------------------------------------------------------------------------------
/accounts/keystore/key.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "bytes"
21 | "crypto/ecdsa"
22 | "encoding/hex"
23 | "encoding/json"
24 | "fmt"
25 | "io"
26 | "io/ioutil"
27 | "os"
28 | "path/filepath"
29 | "strings"
30 | "time"
31 |
32 | "github.com/ethereum/go-ethereum/accounts"
33 | "github.com/ethereum/go-ethereum/common"
34 | "github.com/ethereum/go-ethereum/crypto"
35 | "github.com/pborman/uuid"
36 | )
37 |
38 | const (
39 | version = 3
40 | )
41 |
42 | type Key struct {
43 | Id uuid.UUID // Version 4 "random" for unique id not derived from key data
44 | // to simplify lookups we also store the address
45 | Address common.Address
46 | // we only store privkey as pubkey/address can be derived from it
47 | // privkey in this struct is always in plaintext
48 | PrivateKey *ecdsa.PrivateKey
49 | }
50 |
51 | type keyStore interface {
52 | // Loads and decrypts the key from disk.
53 | GetKey(addr common.Address, filename string, auth string) (*Key, error)
54 | // Writes and encrypts the key.
55 | StoreKey(filename string, k *Key, auth string) error
56 | // Joins filename with the key directory unless it is already absolute.
57 | JoinPath(filename string) string
58 | }
59 |
60 | type plainKeyJSON struct {
61 | Address string `json:"address"`
62 | PrivateKey string `json:"privatekey"`
63 | Id string `json:"id"`
64 | Version int `json:"version"`
65 | }
66 |
67 | type encryptedKeyJSONV3 struct {
68 | Address string `json:"address"`
69 | Crypto CryptoJSON `json:"crypto"`
70 | Id string `json:"id"`
71 | Version int `json:"version"`
72 | }
73 |
74 | type encryptedKeyJSONV1 struct {
75 | Address string `json:"address"`
76 | Crypto CryptoJSON `json:"crypto"`
77 | Id string `json:"id"`
78 | Version string `json:"version"`
79 | }
80 |
81 | type CryptoJSON struct {
82 | Cipher string `json:"cipher"`
83 | CipherText string `json:"ciphertext"`
84 | CipherParams cipherparamsJSON `json:"cipherparams"`
85 | KDF string `json:"kdf"`
86 | KDFParams map[string]interface{} `json:"kdfparams"`
87 | MAC string `json:"mac"`
88 | }
89 |
90 | type cipherparamsJSON struct {
91 | IV string `json:"iv"`
92 | }
93 |
94 | func (k *Key) MarshalJSON() (j []byte, err error) {
95 | jStruct := plainKeyJSON{
96 | hex.EncodeToString(k.Address[:]),
97 | hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)),
98 | k.Id.String(),
99 | version,
100 | }
101 | j, err = json.Marshal(jStruct)
102 | return j, err
103 | }
104 |
105 | func (k *Key) UnmarshalJSON(j []byte) (err error) {
106 | keyJSON := new(plainKeyJSON)
107 | err = json.Unmarshal(j, &keyJSON)
108 | if err != nil {
109 | return err
110 | }
111 |
112 | u := new(uuid.UUID)
113 | *u = uuid.Parse(keyJSON.Id)
114 | k.Id = *u
115 | addr, err := hex.DecodeString(keyJSON.Address)
116 | if err != nil {
117 | return err
118 | }
119 | privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey)
120 | if err != nil {
121 | return err
122 | }
123 |
124 | k.Address = common.BytesToAddress(addr)
125 | k.PrivateKey = privkey
126 |
127 | return nil
128 | }
129 |
130 | func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
131 | id := uuid.NewRandom()
132 | key := &Key{
133 | Id: id,
134 | Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
135 | PrivateKey: privateKeyECDSA,
136 | }
137 | return key
138 | }
139 |
140 | // NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit
141 | // into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we
142 | // retry until the first byte is 0.
143 | func NewKeyForDirectICAP(rand io.Reader) *Key {
144 | randBytes := make([]byte, 64)
145 | _, err := rand.Read(randBytes)
146 | if err != nil {
147 | panic("key generation: could not read from random source: " + err.Error())
148 | }
149 | reader := bytes.NewReader(randBytes)
150 | privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader)
151 | if err != nil {
152 | panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
153 | }
154 | key := newKeyFromECDSA(privateKeyECDSA)
155 | if !strings.HasPrefix(key.Address.Hex(), "0x00") {
156 | return NewKeyForDirectICAP(rand)
157 | }
158 | return key
159 | }
160 |
161 | func newKey(rand io.Reader) (*Key, error) {
162 | privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
163 | if err != nil {
164 | return nil, err
165 | }
166 | return newKeyFromECDSA(privateKeyECDSA), nil
167 | }
168 |
169 | func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {
170 | key, err := newKey(rand)
171 | if err != nil {
172 | return nil, accounts.Account{}, err
173 | }
174 | a := accounts.Account{
175 | Address: key.Address,
176 | URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))},
177 | }
178 | if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
179 | zeroKey(key.PrivateKey)
180 | return nil, a, err
181 | }
182 | return key, a, err
183 | }
184 |
185 | func writeTemporaryKeyFile(file string, content []byte) (string, error) {
186 | // Create the keystore directory with appropriate permissions
187 | // in case it is not present yet.
188 | const dirPerm = 0700
189 | if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
190 | return "", err
191 | }
192 | // Atomic write: create a temporary hidden file first
193 | // then move it into place. TempFile assigns mode 0600.
194 | f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
195 | if err != nil {
196 | return "", err
197 | }
198 | if _, err := f.Write(content); err != nil {
199 | f.Close()
200 | os.Remove(f.Name())
201 | return "", err
202 | }
203 | f.Close()
204 | return f.Name(), nil
205 | }
206 |
207 | func writeKeyFile(file string, content []byte) error {
208 | name, err := writeTemporaryKeyFile(file, content)
209 | if err != nil {
210 | return err
211 | }
212 | return os.Rename(name, file)
213 | }
214 |
215 | // keyFileName implements the naming convention for keyfiles:
216 | // UTC---
217 | func keyFileName(keyAddr common.Address) string {
218 | ts := time.Now().UTC()
219 | return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
220 | }
221 |
222 | func toISO8601(t time.Time) string {
223 | var tz string
224 | name, offset := t.Zone()
225 | if name == "UTC" {
226 | tz = "Z"
227 | } else {
228 | tz = fmt.Sprintf("%03d00", offset/3600)
229 | }
230 | return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
231 | t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
232 | }
233 |
--------------------------------------------------------------------------------
/accounts/keystore/keystore.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | // Package keystore implements encrypted storage of secp256k1 private keys.
18 | //
19 | // Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
20 | // See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
21 | package keystore
22 |
23 | import (
24 | "crypto/ecdsa"
25 | crand "crypto/rand"
26 | "errors"
27 | "fmt"
28 | "math/big"
29 | "os"
30 | "path/filepath"
31 | "reflect"
32 | "runtime"
33 | "sync"
34 | "time"
35 |
36 | "github.com/ethereum/go-ethereum/accounts"
37 | "github.com/ethereum/go-ethereum/common"
38 | "github.com/ethereum/go-ethereum/core/types"
39 | "github.com/ethereum/go-ethereum/crypto"
40 | "github.com/ethereum/go-ethereum/event"
41 | )
42 |
43 | var (
44 | ErrLocked = accounts.NewAuthNeededError("password or unlock")
45 | ErrNoMatch = errors.New("no key for given address or file")
46 | ErrDecrypt = errors.New("could not decrypt key with given passphrase")
47 | )
48 |
49 | // KeyStoreType is the reflect type of a keystore backend.
50 | var KeyStoreType = reflect.TypeOf(&KeyStore{})
51 |
52 | // KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
53 | const KeyStoreScheme = "keystore"
54 |
55 | // Maximum time between wallet refreshes (if filesystem notifications don't work).
56 | const walletRefreshCycle = 3 * time.Second
57 |
58 | // KeyStore manages a key storage directory on disk.
59 | type KeyStore struct {
60 | storage keyStore // Storage backend, might be cleartext or encrypted
61 | cache *accountCache // In-memory account cache over the filesystem storage
62 | changes chan struct{} // Channel receiving change notifications from the cache
63 | unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
64 |
65 | wallets []accounts.Wallet // Wallet wrappers around the individual key files
66 | updateFeed event.Feed // Event feed to notify wallet additions/removals
67 | updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
68 | updating bool // Whether the event notification loop is running
69 |
70 | mu sync.RWMutex
71 | }
72 |
73 | type unlocked struct {
74 | *Key
75 | abort chan struct{}
76 | }
77 |
78 | // NewKeyStore creates a keystore for the given directory.
79 | func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
80 | keydir, _ = filepath.Abs(keydir)
81 | ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
82 | ks.init(keydir)
83 | return ks
84 | }
85 |
86 | // NewPlaintextKeyStore creates a keystore for the given directory.
87 | // Deprecated: Use NewKeyStore.
88 | func NewPlaintextKeyStore(keydir string) *KeyStore {
89 | keydir, _ = filepath.Abs(keydir)
90 | ks := &KeyStore{storage: &keyStorePlain{keydir}}
91 | ks.init(keydir)
92 | return ks
93 | }
94 |
95 | func (ks *KeyStore) init(keydir string) {
96 | // Lock the mutex since the account cache might call back with events
97 | ks.mu.Lock()
98 | defer ks.mu.Unlock()
99 |
100 | // Initialize the set of unlocked keys and the account cache
101 | ks.unlocked = make(map[common.Address]*unlocked)
102 | ks.cache, ks.changes = newAccountCache(keydir)
103 |
104 | // TODO: In order for this finalizer to work, there must be no references
105 | // to ks. addressCache doesn't keep a reference but unlocked keys do,
106 | // so the finalizer will not trigger until all timed unlocks have expired.
107 | runtime.SetFinalizer(ks, func(m *KeyStore) {
108 | m.cache.close()
109 | })
110 | // Create the initial list of wallets from the cache
111 | accs := ks.cache.accounts()
112 | ks.wallets = make([]accounts.Wallet, len(accs))
113 | for i := 0; i < len(accs); i++ {
114 | ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
115 | }
116 | }
117 |
118 | // Wallets implements accounts.Backend, returning all single-key wallets from the
119 | // keystore directory.
120 | func (ks *KeyStore) Wallets() []accounts.Wallet {
121 | // Make sure the list of wallets is in sync with the account cache
122 | ks.refreshWallets()
123 |
124 | ks.mu.RLock()
125 | defer ks.mu.RUnlock()
126 |
127 | cpy := make([]accounts.Wallet, len(ks.wallets))
128 | copy(cpy, ks.wallets)
129 | return cpy
130 | }
131 |
132 | // refreshWallets retrieves the current account list and based on that does any
133 | // necessary wallet refreshes.
134 | func (ks *KeyStore) refreshWallets() {
135 | // Retrieve the current list of accounts
136 | ks.mu.Lock()
137 | accs := ks.cache.accounts()
138 |
139 | // Transform the current list of wallets into the new one
140 | wallets := make([]accounts.Wallet, 0, len(accs))
141 | events := []accounts.WalletEvent{}
142 |
143 | for _, account := range accs {
144 | // Drop wallets while they were in front of the next account
145 | for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
146 | events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
147 | ks.wallets = ks.wallets[1:]
148 | }
149 | // If there are no more wallets or the account is before the next, wrap new wallet
150 | if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
151 | wallet := &keystoreWallet{account: account, keystore: ks}
152 |
153 | events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
154 | wallets = append(wallets, wallet)
155 | continue
156 | }
157 | // If the account is the same as the first wallet, keep it
158 | if ks.wallets[0].Accounts()[0] == account {
159 | wallets = append(wallets, ks.wallets[0])
160 | ks.wallets = ks.wallets[1:]
161 | continue
162 | }
163 | }
164 | // Drop any leftover wallets and set the new batch
165 | for _, wallet := range ks.wallets {
166 | events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
167 | }
168 | ks.wallets = wallets
169 | ks.mu.Unlock()
170 |
171 | // Fire all wallet events and return
172 | for _, event := range events {
173 | ks.updateFeed.Send(event)
174 | }
175 | }
176 |
177 | // Subscribe implements accounts.Backend, creating an async subscription to
178 | // receive notifications on the addition or removal of keystore wallets.
179 | func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
180 | // We need the mutex to reliably start/stop the update loop
181 | ks.mu.Lock()
182 | defer ks.mu.Unlock()
183 |
184 | // Subscribe the caller and track the subscriber count
185 | sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
186 |
187 | // Subscribers require an active notification loop, start it
188 | if !ks.updating {
189 | ks.updating = true
190 | go ks.updater()
191 | }
192 | return sub
193 | }
194 |
195 | // updater is responsible for maintaining an up-to-date list of wallets stored in
196 | // the keystore, and for firing wallet addition/removal events. It listens for
197 | // account change events from the underlying account cache, and also periodically
198 | // forces a manual refresh (only triggers for systems where the filesystem notifier
199 | // is not running).
200 | func (ks *KeyStore) updater() {
201 | for {
202 | // Wait for an account update or a refresh timeout
203 | select {
204 | case <-ks.changes:
205 | case <-time.After(walletRefreshCycle):
206 | }
207 | // Run the wallet refresher
208 | ks.refreshWallets()
209 |
210 | // If all our subscribers left, stop the updater
211 | ks.mu.Lock()
212 | if ks.updateScope.Count() == 0 {
213 | ks.updating = false
214 | ks.mu.Unlock()
215 | return
216 | }
217 | ks.mu.Unlock()
218 | }
219 | }
220 |
221 | // HasAddress reports whether a key with the given address is present.
222 | func (ks *KeyStore) HasAddress(addr common.Address) bool {
223 | return ks.cache.hasAddress(addr)
224 | }
225 |
226 | // Accounts returns all key files present in the directory.
227 | func (ks *KeyStore) Accounts() []accounts.Account {
228 | return ks.cache.accounts()
229 | }
230 |
231 | // Delete deletes the key matched by account if the passphrase is correct.
232 | // If the account contains no filename, the address must match a unique key.
233 | func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
234 | // Decrypting the key isn't really necessary, but we do
235 | // it anyway to check the password and zero out the key
236 | // immediately afterwards.
237 | a, key, err := ks.getDecryptedKey(a, passphrase)
238 | if key != nil {
239 | zeroKey(key.PrivateKey)
240 | }
241 | if err != nil {
242 | return err
243 | }
244 | // The order is crucial here. The key is dropped from the
245 | // cache after the file is gone so that a reload happening in
246 | // between won't insert it into the cache again.
247 | err = os.Remove(a.URL.Path)
248 | if err == nil {
249 | ks.cache.delete(a)
250 | ks.refreshWallets()
251 | }
252 | return err
253 | }
254 |
255 | // SignHash calculates a ECDSA signature for the given hash. The produced
256 | // signature is in the [R || S || V] format where V is 0 or 1.
257 | func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
258 | // Look up the key to sign with and abort if it cannot be found
259 | ks.mu.RLock()
260 | defer ks.mu.RUnlock()
261 |
262 | unlockedKey, found := ks.unlocked[a.Address]
263 | if !found {
264 | return nil, ErrLocked
265 | }
266 | // Sign the hash using plain ECDSA operations
267 | return crypto.Sign(hash, unlockedKey.PrivateKey)
268 | }
269 |
270 | // SignTx signs the given transaction with the requested account.
271 | func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
272 | // Look up the key to sign with and abort if it cannot be found
273 | ks.mu.RLock()
274 | defer ks.mu.RUnlock()
275 |
276 | unlockedKey, found := ks.unlocked[a.Address]
277 | if !found {
278 | return nil, ErrLocked
279 | }
280 | // Depending on the presence of the chain ID, sign with EIP155 or homestead
281 | if chainID != nil {
282 | return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
283 | }
284 | return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)
285 | }
286 |
287 | // SignHashWithPassphrase signs hash if the private key matching the given address
288 | // can be decrypted with the given passphrase. The produced signature is in the
289 | // [R || S || V] format where V is 0 or 1.
290 | func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
291 | _, key, err := ks.getDecryptedKey(a, passphrase)
292 | if err != nil {
293 | return nil, err
294 | }
295 | defer zeroKey(key.PrivateKey)
296 | return crypto.Sign(hash, key.PrivateKey)
297 | }
298 |
299 | // SignTxWithPassphrase signs the transaction if the private key matching the
300 | // given address can be decrypted with the given passphrase.
301 | func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
302 | _, key, err := ks.getDecryptedKey(a, passphrase)
303 | if err != nil {
304 | return nil, err
305 | }
306 | defer zeroKey(key.PrivateKey)
307 |
308 | // Depending on the presence of the chain ID, sign with EIP155 or homestead
309 | if chainID != nil {
310 | return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey)
311 | }
312 | return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey)
313 | }
314 |
315 | // Unlock unlocks the given account indefinitely.
316 | func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
317 | return ks.TimedUnlock(a, passphrase, 0)
318 | }
319 |
320 | // Lock removes the private key with the given address from memory.
321 | func (ks *KeyStore) Lock(addr common.Address) error {
322 | ks.mu.Lock()
323 | if unl, found := ks.unlocked[addr]; found {
324 | ks.mu.Unlock()
325 | ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
326 | } else {
327 | ks.mu.Unlock()
328 | }
329 | return nil
330 | }
331 |
332 | // TimedUnlock unlocks the given account with the passphrase. The account
333 | // stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
334 | // until the program exits. The account must match a unique key file.
335 | //
336 | // If the account address is already unlocked for a duration, TimedUnlock extends or
337 | // shortens the active unlock timeout. If the address was previously unlocked
338 | // indefinitely the timeout is not altered.
339 | func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
340 | a, key, err := ks.getDecryptedKey(a, passphrase)
341 | if err != nil {
342 | return err
343 | }
344 |
345 | ks.mu.Lock()
346 | defer ks.mu.Unlock()
347 | u, found := ks.unlocked[a.Address]
348 | if found {
349 | if u.abort == nil {
350 | // The address was unlocked indefinitely, so unlocking
351 | // it with a timeout would be confusing.
352 | zeroKey(key.PrivateKey)
353 | return nil
354 | }
355 | // Terminate the expire goroutine and replace it below.
356 | close(u.abort)
357 | }
358 | if timeout > 0 {
359 | u = &unlocked{Key: key, abort: make(chan struct{})}
360 | go ks.expire(a.Address, u, timeout)
361 | } else {
362 | u = &unlocked{Key: key}
363 | }
364 | ks.unlocked[a.Address] = u
365 | return nil
366 | }
367 |
368 | // Find resolves the given account into a unique entry in the keystore.
369 | func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) {
370 | ks.cache.maybeReload()
371 | ks.cache.mu.Lock()
372 | a, err := ks.cache.find(a)
373 | ks.cache.mu.Unlock()
374 | return a, err
375 | }
376 |
377 | func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
378 | a, err := ks.Find(a)
379 | if err != nil {
380 | return a, nil, err
381 | }
382 | key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
383 | return a, key, err
384 | }
385 |
386 | func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
387 | t := time.NewTimer(timeout)
388 | defer t.Stop()
389 | select {
390 | case <-u.abort:
391 | // just quit
392 | case <-t.C:
393 | ks.mu.Lock()
394 | // only drop if it's still the same key instance that dropLater
395 | // was launched with. we can check that using pointer equality
396 | // because the map stores a new pointer every time the key is
397 | // unlocked.
398 | if ks.unlocked[addr] == u {
399 | zeroKey(u.PrivateKey)
400 | delete(ks.unlocked, addr)
401 | }
402 | ks.mu.Unlock()
403 | }
404 | }
405 |
406 | // NewAccount generates a new key and stores it into the key directory,
407 | // encrypting it with the passphrase.
408 | func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
409 | _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
410 | if err != nil {
411 | return accounts.Account{}, err
412 | }
413 | // Add the account to the cache immediately rather
414 | // than waiting for file system notifications to pick it up.
415 | ks.cache.add(account)
416 | ks.refreshWallets()
417 | return account, nil
418 | }
419 |
420 | // Export exports as a JSON key, encrypted with newPassphrase.
421 | func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
422 | _, key, err := ks.getDecryptedKey(a, passphrase)
423 | if err != nil {
424 | return nil, err
425 | }
426 | var N, P int
427 | if store, ok := ks.storage.(*keyStorePassphrase); ok {
428 | N, P = store.scryptN, store.scryptP
429 | } else {
430 | N, P = StandardScryptN, StandardScryptP
431 | }
432 | return EncryptKey(key, newPassphrase, N, P)
433 | }
434 |
435 | // Import stores the given encrypted JSON key into the key directory.
436 | func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
437 | key, err := DecryptKey(keyJSON, passphrase)
438 | if key != nil && key.PrivateKey != nil {
439 | defer zeroKey(key.PrivateKey)
440 | }
441 | if err != nil {
442 | return accounts.Account{}, err
443 | }
444 | return ks.importKey(key, newPassphrase)
445 | }
446 |
447 | // ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
448 | func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
449 | key := newKeyFromECDSA(priv)
450 | if ks.cache.hasAddress(key.Address) {
451 | return accounts.Account{}, fmt.Errorf("account already exists")
452 | }
453 | return ks.importKey(key, passphrase)
454 | }
455 |
456 | func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
457 | a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
458 | if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
459 | return accounts.Account{}, err
460 | }
461 | ks.cache.add(a)
462 | ks.refreshWallets()
463 | return a, nil
464 | }
465 |
466 | // Update changes the passphrase of an existing account.
467 | func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error {
468 | a, key, err := ks.getDecryptedKey(a, passphrase)
469 | if err != nil {
470 | return err
471 | }
472 | return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
473 | }
474 |
475 | // ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
476 | // a key file in the key directory. The key file is encrypted with the same passphrase.
477 | func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
478 | a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
479 | if err != nil {
480 | return a, err
481 | }
482 | ks.cache.add(a)
483 | ks.refreshWallets()
484 | return a, nil
485 | }
486 |
487 | // zeroKey zeroes a private key in memory.
488 | func zeroKey(k *ecdsa.PrivateKey) {
489 | b := k.D.Bits()
490 | for i := range b {
491 | b[i] = 0
492 | }
493 | }
494 |
--------------------------------------------------------------------------------
/accounts/keystore/keystore_passphrase.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | /*
18 |
19 | This key store behaves as KeyStorePlain with the difference that
20 | the private key is encrypted and on disk uses another JSON encoding.
21 |
22 | The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
23 |
24 | */
25 |
26 | package keystore
27 |
28 | import (
29 | "bytes"
30 | "crypto/aes"
31 | "crypto/rand"
32 | "crypto/sha256"
33 | "encoding/hex"
34 | "encoding/json"
35 | "fmt"
36 | "io"
37 | "io/ioutil"
38 | "os"
39 | "path/filepath"
40 |
41 | "github.com/ethereum/go-ethereum/common"
42 | "github.com/ethereum/go-ethereum/common/math"
43 | "github.com/ethereum/go-ethereum/crypto"
44 | "github.com/pborman/uuid"
45 | "golang.org/x/crypto/pbkdf2"
46 | "golang.org/x/crypto/scrypt"
47 | )
48 |
49 | const (
50 | keyHeaderKDF = "scrypt"
51 |
52 | // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
53 | // memory and taking approximately 1s CPU time on a modern processor.
54 | StandardScryptN = 1 << 18
55 |
56 | // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
57 | // memory and taking approximately 1s CPU time on a modern processor.
58 | StandardScryptP = 1
59 |
60 | // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
61 | // memory and taking approximately 100ms CPU time on a modern processor.
62 | LightScryptN = 1 << 12
63 |
64 | // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
65 | // memory and taking approximately 100ms CPU time on a modern processor.
66 | LightScryptP = 6
67 |
68 | scryptR = 8
69 | scryptDKLen = 32
70 | )
71 |
72 | type keyStorePassphrase struct {
73 | keysDirPath string
74 | scryptN int
75 | scryptP int
76 | // skipKeyFileVerification disables the security-feature which does
77 | // reads and decrypts any newly created keyfiles. This should be 'false' in all
78 | // cases except tests -- setting this to 'true' is not recommended.
79 | skipKeyFileVerification bool
80 | }
81 |
82 | func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
83 | // Load the key from the keystore and decrypt its contents
84 | keyjson, err := ioutil.ReadFile(filename)
85 | if err != nil {
86 | return nil, err
87 | }
88 | key, err := DecryptKey(keyjson, auth)
89 | if err != nil {
90 | return nil, err
91 | }
92 | // Make sure we're really operating on the requested key (no swap attacks)
93 | if key.Address != addr {
94 | return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
95 | }
96 | return key, nil
97 | }
98 |
99 | // StoreKey generates a key, encrypts with 'auth' and stores in the given directory
100 | func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
101 | _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)
102 | return a.Address, err
103 | }
104 |
105 | func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
106 | keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
107 | if err != nil {
108 | return err
109 | }
110 | // Write into temporary file
111 | tmpName, err := writeTemporaryKeyFile(filename, keyjson)
112 | if err != nil {
113 | return err
114 | }
115 | if !ks.skipKeyFileVerification {
116 | // Verify that we can decrypt the file with the given password.
117 | _, err = ks.GetKey(key.Address, tmpName, auth)
118 | if err != nil {
119 | msg := "An error was encountered when saving and verifying the keystore file. \n" +
120 | "This indicates that the keystore is corrupted. \n" +
121 | "The corrupted file is stored at \n%v\n" +
122 | "Please file a ticket at:\n\n" +
123 | "https://github.com/ethereum/go-ethereum/issues." +
124 | "The error was : %s"
125 | return fmt.Errorf(msg, tmpName, err)
126 | }
127 | }
128 | return os.Rename(tmpName, filename)
129 | }
130 |
131 | func (ks keyStorePassphrase) JoinPath(filename string) string {
132 | if filepath.IsAbs(filename) {
133 | return filename
134 | }
135 | return filepath.Join(ks.keysDirPath, filename)
136 | }
137 |
138 | // EncryptDataV3 encrypts the data given as 'data' with the password 'auth'.
139 | func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
140 |
141 | salt := make([]byte, 32)
142 | if _, err := io.ReadFull(rand.Reader, salt); err != nil {
143 | panic("reading from crypto/rand failed: " + err.Error())
144 | }
145 | derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
146 | if err != nil {
147 | return CryptoJSON{}, err
148 | }
149 | encryptKey := derivedKey[:16]
150 |
151 | iv := make([]byte, aes.BlockSize) // 16
152 | if _, err := io.ReadFull(rand.Reader, iv); err != nil {
153 | panic("reading from crypto/rand failed: " + err.Error())
154 | }
155 | cipherText, err := aesCTRXOR(encryptKey, data, iv)
156 | if err != nil {
157 | return CryptoJSON{}, err
158 | }
159 | mac := crypto.Keccak256(derivedKey[16:32], cipherText)
160 |
161 | scryptParamsJSON := make(map[string]interface{}, 5)
162 | scryptParamsJSON["n"] = scryptN
163 | scryptParamsJSON["r"] = scryptR
164 | scryptParamsJSON["p"] = scryptP
165 | scryptParamsJSON["dklen"] = scryptDKLen
166 | scryptParamsJSON["salt"] = hex.EncodeToString(salt)
167 | cipherParamsJSON := cipherparamsJSON{
168 | IV: hex.EncodeToString(iv),
169 | }
170 |
171 | cryptoStruct := CryptoJSON{
172 | Cipher: "aes-128-ctr",
173 | CipherText: hex.EncodeToString(cipherText),
174 | CipherParams: cipherParamsJSON,
175 | KDF: keyHeaderKDF,
176 | KDFParams: scryptParamsJSON,
177 | MAC: hex.EncodeToString(mac),
178 | }
179 | return cryptoStruct, nil
180 | }
181 |
182 | // EncryptKey encrypts a key using the specified scrypt parameters into a json
183 | // blob that can be decrypted later on.
184 | func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
185 | keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
186 | cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
187 | if err != nil {
188 | return nil, err
189 | }
190 | encryptedKeyJSONV3 := encryptedKeyJSONV3{
191 | hex.EncodeToString(key.Address[:]),
192 | cryptoStruct,
193 | key.Id.String(),
194 | version,
195 | }
196 | return json.Marshal(encryptedKeyJSONV3)
197 | }
198 |
199 | // DecryptKey decrypts a key from a json blob, returning the private key itself.
200 | func DecryptKey(keyjson []byte, auth string) (*Key, error) {
201 | // Parse the json into a simple map to fetch the key version
202 | m := make(map[string]interface{})
203 | if err := json.Unmarshal(keyjson, &m); err != nil {
204 | return nil, err
205 | }
206 | // Depending on the version try to parse one way or another
207 | var (
208 | keyBytes, keyId []byte
209 | err error
210 | )
211 | if version, ok := m["version"].(string); ok && version == "1" {
212 | k := new(encryptedKeyJSONV1)
213 | if err := json.Unmarshal(keyjson, k); err != nil {
214 | return nil, err
215 | }
216 | keyBytes, keyId, err = decryptKeyV1(k, auth)
217 | } else {
218 | k := new(encryptedKeyJSONV3)
219 | if err := json.Unmarshal(keyjson, k); err != nil {
220 | return nil, err
221 | }
222 | keyBytes, keyId, err = decryptKeyV3(k, auth)
223 | }
224 | // Handle any decryption errors and return the key
225 | if err != nil {
226 | return nil, err
227 | }
228 | key := crypto.ToECDSAUnsafe(keyBytes)
229 |
230 | return &Key{
231 | Id: uuid.UUID(keyId),
232 | Address: crypto.PubkeyToAddress(key.PublicKey),
233 | PrivateKey: key,
234 | }, nil
235 | }
236 |
237 | func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
238 | if cryptoJson.Cipher != "aes-128-ctr" {
239 | return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
240 | }
241 | mac, err := hex.DecodeString(cryptoJson.MAC)
242 | if err != nil {
243 | return nil, err
244 | }
245 |
246 | iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
247 | if err != nil {
248 | return nil, err
249 | }
250 |
251 | cipherText, err := hex.DecodeString(cryptoJson.CipherText)
252 | if err != nil {
253 | return nil, err
254 | }
255 |
256 | derivedKey, err := getKDFKey(cryptoJson, auth)
257 | if err != nil {
258 | return nil, err
259 | }
260 |
261 | calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
262 | if !bytes.Equal(calculatedMAC, mac) {
263 | return nil, ErrDecrypt
264 | }
265 |
266 | plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
267 | if err != nil {
268 | return nil, err
269 | }
270 | return plainText, err
271 | }
272 |
273 | func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
274 | if keyProtected.Version != version {
275 | return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
276 | }
277 | keyId = uuid.Parse(keyProtected.Id)
278 | plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
279 | if err != nil {
280 | return nil, nil, err
281 | }
282 | return plainText, keyId, err
283 | }
284 |
285 | func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
286 | keyId = uuid.Parse(keyProtected.Id)
287 | mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
288 | if err != nil {
289 | return nil, nil, err
290 | }
291 |
292 | iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
293 | if err != nil {
294 | return nil, nil, err
295 | }
296 |
297 | cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
298 | if err != nil {
299 | return nil, nil, err
300 | }
301 |
302 | derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
303 | if err != nil {
304 | return nil, nil, err
305 | }
306 |
307 | calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
308 | if !bytes.Equal(calculatedMAC, mac) {
309 | return nil, nil, ErrDecrypt
310 | }
311 |
312 | plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
313 | if err != nil {
314 | return nil, nil, err
315 | }
316 | return plainText, keyId, err
317 | }
318 |
319 | func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
320 | authArray := []byte(auth)
321 | salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
322 | if err != nil {
323 | return nil, err
324 | }
325 | dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
326 |
327 | if cryptoJSON.KDF == keyHeaderKDF {
328 | n := ensureInt(cryptoJSON.KDFParams["n"])
329 | r := ensureInt(cryptoJSON.KDFParams["r"])
330 | p := ensureInt(cryptoJSON.KDFParams["p"])
331 | return scrypt.Key(authArray, salt, n, r, p, dkLen)
332 |
333 | } else if cryptoJSON.KDF == "pbkdf2" {
334 | c := ensureInt(cryptoJSON.KDFParams["c"])
335 | prf := cryptoJSON.KDFParams["prf"].(string)
336 | if prf != "hmac-sha256" {
337 | return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
338 | }
339 | key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
340 | return key, nil
341 | }
342 |
343 | return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
344 | }
345 |
346 | // TODO: can we do without this when unmarshalling dynamic JSON?
347 | // why do integers in KDF params end up as float64 and not int after
348 | // unmarshal?
349 | func ensureInt(x interface{}) int {
350 | res, ok := x.(int)
351 | if !ok {
352 | res = int(x.(float64))
353 | }
354 | return res
355 | }
356 |
--------------------------------------------------------------------------------
/accounts/keystore/keystore_passphrase_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "io/ioutil"
21 | "testing"
22 |
23 | "github.com/ethereum/go-ethereum/common"
24 | )
25 |
26 | const (
27 | veryLightScryptN = 2
28 | veryLightScryptP = 1
29 | )
30 |
31 | // Tests that a json key file can be decrypted and encrypted in multiple rounds.
32 | func TestKeyEncryptDecrypt(t *testing.T) {
33 | keyjson, err := ioutil.ReadFile("testdata/very-light-scrypt.json")
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 | password := ""
38 | address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8")
39 |
40 | // Do a few rounds of decryption and encryption
41 | for i := 0; i < 3; i++ {
42 | // Try a bad password first
43 | if _, err := DecryptKey(keyjson, password+"bad"); err == nil {
44 | t.Errorf("test %d: json key decrypted with bad password", i)
45 | }
46 | // Decrypt with the correct password
47 | key, err := DecryptKey(keyjson, password)
48 | if err != nil {
49 | t.Errorf("test %d: json key failed to decrypt: %v", i, err)
50 | }
51 | if key.Address != address {
52 | t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
53 | }
54 | // Recrypt with a new password and start over
55 | password += "new data appended"
56 | if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
57 | t.Errorf("test %d: failed to recrypt key %v", i, err)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/accounts/keystore/keystore_plain.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "encoding/json"
21 | "fmt"
22 | "os"
23 | "path/filepath"
24 |
25 | "github.com/ethereum/go-ethereum/common"
26 | )
27 |
28 | type keyStorePlain struct {
29 | keysDirPath string
30 | }
31 |
32 | func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) {
33 | fd, err := os.Open(filename)
34 | if err != nil {
35 | return nil, err
36 | }
37 | defer fd.Close()
38 | key := new(Key)
39 | if err := json.NewDecoder(fd).Decode(key); err != nil {
40 | return nil, err
41 | }
42 | if key.Address != addr {
43 | return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr)
44 | }
45 | return key, nil
46 | }
47 |
48 | func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
49 | content, err := json.Marshal(key)
50 | if err != nil {
51 | return err
52 | }
53 | return writeKeyFile(filename, content)
54 | }
55 |
56 | func (ks keyStorePlain) JoinPath(filename string) string {
57 | if filepath.IsAbs(filename) {
58 | return filename
59 | } else {
60 | return filepath.Join(ks.keysDirPath, filename)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/accounts/keystore/keystore_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "io/ioutil"
21 | "math/rand"
22 | "os"
23 | "runtime"
24 | "sort"
25 | "strings"
26 | "testing"
27 | "time"
28 |
29 | "github.com/ethereum/go-ethereum/accounts"
30 | "github.com/ethereum/go-ethereum/common"
31 | "github.com/ethereum/go-ethereum/event"
32 | )
33 |
34 | var testSigData = make([]byte, 32)
35 |
36 | func TestKeyStore(t *testing.T) {
37 | dir, ks := tmpKeyStore(t, true)
38 | defer os.RemoveAll(dir)
39 |
40 | a, err := ks.NewAccount("foo")
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 | if !strings.HasPrefix(a.URL.Path, dir) {
45 | t.Errorf("account file %s doesn't have dir prefix", a.URL)
46 | }
47 | stat, err := os.Stat(a.URL.Path)
48 | if err != nil {
49 | t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
50 | }
51 | if runtime.GOOS != "windows" && stat.Mode() != 0600 {
52 | t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
53 | }
54 | if !ks.HasAddress(a.Address) {
55 | t.Errorf("HasAccount(%x) should've returned true", a.Address)
56 | }
57 | if err := ks.Update(a, "foo", "bar"); err != nil {
58 | t.Errorf("Update error: %v", err)
59 | }
60 | if err := ks.Delete(a, "bar"); err != nil {
61 | t.Errorf("Delete error: %v", err)
62 | }
63 | if common.FileExist(a.URL.Path) {
64 | t.Errorf("account file %s should be gone after Delete", a.URL)
65 | }
66 | if ks.HasAddress(a.Address) {
67 | t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address)
68 | }
69 | }
70 |
71 | func TestSign(t *testing.T) {
72 | dir, ks := tmpKeyStore(t, true)
73 | defer os.RemoveAll(dir)
74 |
75 | pass := "" // not used but required by API
76 | a1, err := ks.NewAccount(pass)
77 | if err != nil {
78 | t.Fatal(err)
79 | }
80 | if err := ks.Unlock(a1, ""); err != nil {
81 | t.Fatal(err)
82 | }
83 | if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil {
84 | t.Fatal(err)
85 | }
86 | }
87 |
88 | func TestSignWithPassphrase(t *testing.T) {
89 | dir, ks := tmpKeyStore(t, true)
90 | defer os.RemoveAll(dir)
91 |
92 | pass := "passwd"
93 | acc, err := ks.NewAccount(pass)
94 | if err != nil {
95 | t.Fatal(err)
96 | }
97 |
98 | if _, unlocked := ks.unlocked[acc.Address]; unlocked {
99 | t.Fatal("expected account to be locked")
100 | }
101 |
102 | _, err = ks.SignHashWithPassphrase(acc, pass, testSigData)
103 | if err != nil {
104 | t.Fatal(err)
105 | }
106 |
107 | if _, unlocked := ks.unlocked[acc.Address]; unlocked {
108 | t.Fatal("expected account to be locked")
109 | }
110 |
111 | if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil {
112 | t.Fatal("expected SignHashWithPassphrase to fail with invalid password")
113 | }
114 | }
115 |
116 | func TestTimedUnlock(t *testing.T) {
117 | dir, ks := tmpKeyStore(t, true)
118 | defer os.RemoveAll(dir)
119 |
120 | pass := "foo"
121 | a1, err := ks.NewAccount(pass)
122 | if err != nil {
123 | t.Fatal(err)
124 | }
125 |
126 | // Signing without passphrase fails because account is locked
127 | _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
128 | if err != ErrLocked {
129 | t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
130 | }
131 |
132 | // Signing with passphrase works
133 | if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
134 | t.Fatal(err)
135 | }
136 |
137 | // Signing without passphrase works because account is temp unlocked
138 | _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
139 | if err != nil {
140 | t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
141 | }
142 |
143 | // Signing fails again after automatic locking
144 | time.Sleep(250 * time.Millisecond)
145 | _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
146 | if err != ErrLocked {
147 | t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
148 | }
149 | }
150 |
151 | func TestOverrideUnlock(t *testing.T) {
152 | dir, ks := tmpKeyStore(t, false)
153 | defer os.RemoveAll(dir)
154 |
155 | pass := "foo"
156 | a1, err := ks.NewAccount(pass)
157 | if err != nil {
158 | t.Fatal(err)
159 | }
160 |
161 | // Unlock indefinitely.
162 | if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil {
163 | t.Fatal(err)
164 | }
165 |
166 | // Signing without passphrase works because account is temp unlocked
167 | _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
168 | if err != nil {
169 | t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
170 | }
171 |
172 | // reset unlock to a shorter period, invalidates the previous unlock
173 | if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
174 | t.Fatal(err)
175 | }
176 |
177 | // Signing without passphrase still works because account is temp unlocked
178 | _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
179 | if err != nil {
180 | t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
181 | }
182 |
183 | // Signing fails again after automatic locking
184 | time.Sleep(250 * time.Millisecond)
185 | _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
186 | if err != ErrLocked {
187 | t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
188 | }
189 | }
190 |
191 | // This test should fail under -race if signing races the expiration goroutine.
192 | func TestSignRace(t *testing.T) {
193 | dir, ks := tmpKeyStore(t, false)
194 | defer os.RemoveAll(dir)
195 |
196 | // Create a test account.
197 | a1, err := ks.NewAccount("")
198 | if err != nil {
199 | t.Fatal("could not create the test account", err)
200 | }
201 |
202 | if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil {
203 | t.Fatal("could not unlock the test account", err)
204 | }
205 | end := time.Now().Add(500 * time.Millisecond)
206 | for time.Now().Before(end) {
207 | if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked {
208 | return
209 | } else if err != nil {
210 | t.Errorf("Sign error: %v", err)
211 | return
212 | }
213 | time.Sleep(1 * time.Millisecond)
214 | }
215 | t.Errorf("Account did not lock within the timeout")
216 | }
217 |
218 | // Tests that the wallet notifier loop starts and stops correctly based on the
219 | // addition and removal of wallet event subscriptions.
220 | func TestWalletNotifierLifecycle(t *testing.T) {
221 | // Create a temporary kesytore to test with
222 | dir, ks := tmpKeyStore(t, false)
223 | defer os.RemoveAll(dir)
224 |
225 | // Ensure that the notification updater is not running yet
226 | time.Sleep(250 * time.Millisecond)
227 | ks.mu.RLock()
228 | updating := ks.updating
229 | ks.mu.RUnlock()
230 |
231 | if updating {
232 | t.Errorf("wallet notifier running without subscribers")
233 | }
234 | // Subscribe to the wallet feed and ensure the updater boots up
235 | updates := make(chan accounts.WalletEvent)
236 |
237 | subs := make([]event.Subscription, 2)
238 | for i := 0; i < len(subs); i++ {
239 | // Create a new subscription
240 | subs[i] = ks.Subscribe(updates)
241 |
242 | // Ensure the notifier comes online
243 | time.Sleep(250 * time.Millisecond)
244 | ks.mu.RLock()
245 | updating = ks.updating
246 | ks.mu.RUnlock()
247 |
248 | if !updating {
249 | t.Errorf("sub %d: wallet notifier not running after subscription", i)
250 | }
251 | }
252 | // Unsubscribe and ensure the updater terminates eventually
253 | for i := 0; i < len(subs); i++ {
254 | // Close an existing subscription
255 | subs[i].Unsubscribe()
256 |
257 | // Ensure the notifier shuts down at and only at the last close
258 | for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
259 | ks.mu.RLock()
260 | updating = ks.updating
261 | ks.mu.RUnlock()
262 |
263 | if i < len(subs)-1 && !updating {
264 | t.Fatalf("sub %d: event notifier stopped prematurely", i)
265 | }
266 | if i == len(subs)-1 && !updating {
267 | return
268 | }
269 | time.Sleep(250 * time.Millisecond)
270 | }
271 | }
272 | t.Errorf("wallet notifier didn't terminate after unsubscribe")
273 | }
274 |
275 | // Tests that wallet notifications and correctly fired when accounts are added
276 | // or deleted from the keystore.
277 | func TestWalletNotifications(t *testing.T) {
278 | // Create a temporary kesytore to test with
279 | dir, ks := tmpKeyStore(t, false)
280 | defer os.RemoveAll(dir)
281 |
282 | // Subscribe to the wallet feed
283 | updates := make(chan accounts.WalletEvent, 1)
284 | sub := ks.Subscribe(updates)
285 | defer sub.Unsubscribe()
286 |
287 | // Randomly add and remove account and make sure events and wallets are in sync
288 | live := make(map[common.Address]accounts.Account)
289 | for i := 0; i < 1024; i++ {
290 | // Execute a creation or deletion and ensure event arrival
291 | if create := len(live) == 0 || rand.Int()%4 > 0; create {
292 | // Add a new account and ensure wallet notifications arrives
293 | account, err := ks.NewAccount("")
294 | if err != nil {
295 | t.Fatalf("failed to create test account: %v", err)
296 | }
297 | select {
298 | case event := <-updates:
299 | if event.Kind != accounts.WalletArrived {
300 | t.Errorf("non-arrival event on account creation")
301 | }
302 | if event.Wallet.Accounts()[0] != account {
303 | t.Errorf("account mismatch on created wallet: have %v, want %v", event.Wallet.Accounts()[0], account)
304 | }
305 | default:
306 | t.Errorf("wallet arrival event not fired on account creation")
307 | }
308 | live[account.Address] = account
309 | } else {
310 | // Select a random account to delete (crude, but works)
311 | var account accounts.Account
312 | for _, a := range live {
313 | account = a
314 | break
315 | }
316 | // Remove an account and ensure wallet notification arrives
317 | if err := ks.Delete(account, ""); err != nil {
318 | t.Fatalf("failed to delete test account: %v", err)
319 | }
320 | select {
321 | case event := <-updates:
322 | if event.Kind != accounts.WalletDropped {
323 | t.Errorf("non-drop event on account deletion")
324 | }
325 | if event.Wallet.Accounts()[0] != account {
326 | t.Errorf("account mismatch on deleted wallet: have %v, want %v", event.Wallet.Accounts()[0], account)
327 | }
328 | default:
329 | t.Errorf("wallet departure event not fired on account creation")
330 | }
331 | delete(live, account.Address)
332 | }
333 | // Retrieve the list of wallets and ensure it matches with our required live set
334 | liveList := make([]accounts.Account, 0, len(live))
335 | for _, account := range live {
336 | liveList = append(liveList, account)
337 | }
338 | sort.Sort(accountsByURL(liveList))
339 |
340 | wallets := ks.Wallets()
341 | if len(liveList) != len(wallets) {
342 | t.Errorf("wallet list doesn't match required accounts: have %v, want %v", wallets, liveList)
343 | } else {
344 | for j, wallet := range wallets {
345 | if accs := wallet.Accounts(); len(accs) != 1 {
346 | t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
347 | } else if accs[0] != liveList[j] {
348 | t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j])
349 | }
350 | }
351 | }
352 | }
353 | }
354 |
355 | func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
356 | d, err := ioutil.TempDir("", "eth-keystore-test")
357 | if err != nil {
358 | t.Fatal(err)
359 | }
360 | new := NewPlaintextKeyStore
361 | if encrypted {
362 | new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
363 | }
364 | return d, new(d)
365 | }
366 |
--------------------------------------------------------------------------------
/accounts/keystore/plain_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "crypto/rand"
21 | "encoding/hex"
22 | "fmt"
23 | "path/filepath"
24 | "reflect"
25 | "strings"
26 | "testing"
27 |
28 | "github.com/ethereum/go-ethereum/common"
29 | "github.com/ethereum/go-ethereum/crypto"
30 | )
31 |
32 | func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
33 | d := t.TempDir()
34 | if encrypted {
35 | ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
36 | } else {
37 | ks = &keyStorePlain{d}
38 | }
39 | return d, ks
40 | }
41 |
42 | func TestKeyStorePlain(t *testing.T) {
43 | t.Parallel()
44 | _, ks := tmpKeyStoreIface(t, false)
45 |
46 | pass := "" // not used but required by API
47 | k1, account, err := storeNewKey(ks, rand.Reader, pass)
48 | if err != nil {
49 | t.Fatal(err)
50 | }
51 | k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 | if !reflect.DeepEqual(k1.Address, k2.Address) {
56 | t.Fatal(err)
57 | }
58 | if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
59 | t.Fatal(err)
60 | }
61 | }
62 |
63 | func TestKeyStorePassphrase(t *testing.T) {
64 | t.Parallel()
65 | _, ks := tmpKeyStoreIface(t, true)
66 |
67 | pass := "foo"
68 | k1, account, err := storeNewKey(ks, rand.Reader, pass)
69 | if err != nil {
70 | t.Fatal(err)
71 | }
72 | k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 | if !reflect.DeepEqual(k1.Address, k2.Address) {
77 | t.Fatal(err)
78 | }
79 | if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
80 | t.Fatal(err)
81 | }
82 | }
83 |
84 | func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
85 | t.Parallel()
86 | _, ks := tmpKeyStoreIface(t, true)
87 |
88 | pass := "foo"
89 | k1, account, err := storeNewKey(ks, rand.Reader, pass)
90 | if err != nil {
91 | t.Fatal(err)
92 | }
93 | if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
94 | t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt)
95 | }
96 | }
97 |
98 | func TestImportPreSaleKey(t *testing.T) {
99 | t.Parallel()
100 | dir, ks := tmpKeyStoreIface(t, true)
101 |
102 | // file content of a presale key file generated with:
103 | // python pyethsaletool.py genwallet
104 | // with password "foo"
105 | fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
106 | pass := "foo"
107 | account, _, err := importPreSaleKey(ks, []byte(fileContent), pass)
108 | if err != nil {
109 | t.Fatal(err)
110 | }
111 | if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") {
112 | t.Errorf("imported account has wrong address %x", account.Address)
113 | }
114 | if !strings.HasPrefix(account.URL.Path, dir) {
115 | t.Errorf("imported account file not in keystore directory: %q", account.URL)
116 | }
117 | }
118 |
119 | // Test and utils for the key store tests in the Ethereum JSON tests;
120 | // testdataKeyStoreTests/basic_tests.json
121 | type KeyStoreTestV3 struct {
122 | Json encryptedKeyJSONV3
123 | Password string
124 | Priv string
125 | }
126 |
127 | type KeyStoreTestV1 struct {
128 | Json encryptedKeyJSONV1
129 | Password string
130 | Priv string
131 | }
132 |
133 | func TestV3_PBKDF2_1(t *testing.T) {
134 | t.Parallel()
135 | tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
136 | testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
137 | }
138 |
139 | var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests")
140 |
141 | func skipIfSubmoduleMissing(t *testing.T) {
142 | if !common.FileExist(testsSubmodule) {
143 | t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule)
144 | }
145 | }
146 |
147 | func TestV3_PBKDF2_2(t *testing.T) {
148 | skipIfSubmoduleMissing(t)
149 | t.Parallel()
150 | tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
151 | testDecryptV3(tests["test1"], t)
152 | }
153 |
154 | func TestV3_PBKDF2_3(t *testing.T) {
155 | skipIfSubmoduleMissing(t)
156 | t.Parallel()
157 | tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
158 | testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
159 | }
160 |
161 | func TestV3_PBKDF2_4(t *testing.T) {
162 | skipIfSubmoduleMissing(t)
163 | t.Parallel()
164 | tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
165 | testDecryptV3(tests["evilnonce"], t)
166 | }
167 |
168 | func TestV3_Scrypt_1(t *testing.T) {
169 | t.Parallel()
170 | tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
171 | testDecryptV3(tests["wikipage_test_vector_scrypt"], t)
172 | }
173 |
174 | func TestV3_Scrypt_2(t *testing.T) {
175 | skipIfSubmoduleMissing(t)
176 | t.Parallel()
177 | tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
178 | testDecryptV3(tests["test2"], t)
179 | }
180 |
181 | func TestV1_1(t *testing.T) {
182 | t.Parallel()
183 | tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t)
184 | testDecryptV1(tests["test1"], t)
185 | }
186 |
187 | func TestV1_2(t *testing.T) {
188 | t.Parallel()
189 | ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true}
190 | addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
191 | file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
192 | k, err := ks.GetKey(addr, file, "g")
193 | if err != nil {
194 | t.Fatal(err)
195 | }
196 | privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey))
197 | expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
198 | if privHex != expectedHex {
199 | t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex))
200 | }
201 | }
202 |
203 | func testDecryptV3(test KeyStoreTestV3, t *testing.T) {
204 | privBytes, _, err := decryptKeyV3(&test.Json, test.Password)
205 | if err != nil {
206 | t.Fatal(err)
207 | }
208 | privHex := hex.EncodeToString(privBytes)
209 | if test.Priv != privHex {
210 | t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
211 | }
212 | }
213 |
214 | func testDecryptV1(test KeyStoreTestV1, t *testing.T) {
215 | privBytes, _, err := decryptKeyV1(&test.Json, test.Password)
216 | if err != nil {
217 | t.Fatal(err)
218 | }
219 | privHex := hex.EncodeToString(privBytes)
220 | if test.Priv != privHex {
221 | t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
222 | }
223 | }
224 |
225 | func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 {
226 | tests := make(map[string]KeyStoreTestV3)
227 | err := common.LoadJSON(file, &tests)
228 | if err != nil {
229 | t.Fatal(err)
230 | }
231 | return tests
232 | }
233 |
234 | func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 {
235 | tests := make(map[string]KeyStoreTestV1)
236 | err := common.LoadJSON(file, &tests)
237 | if err != nil {
238 | t.Fatal(err)
239 | }
240 | return tests
241 | }
242 |
243 | func TestKeyForDirectICAP(t *testing.T) {
244 | t.Parallel()
245 | key := NewKeyForDirectICAP(rand.Reader)
246 | if !strings.HasPrefix(key.Address.Hex(), "0x00") {
247 | t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex())
248 | }
249 | }
250 |
251 | func TestV3_31_Byte_Key(t *testing.T) {
252 | t.Parallel()
253 | tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
254 | testDecryptV3(tests["31_byte_key"], t)
255 | }
256 |
257 | func TestV3_30_Byte_Key(t *testing.T) {
258 | t.Parallel()
259 | tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
260 | testDecryptV3(tests["30_byte_key"], t)
261 | }
262 |
--------------------------------------------------------------------------------
/accounts/keystore/presale.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "crypto/aes"
21 | "crypto/cipher"
22 | "crypto/sha256"
23 | "encoding/hex"
24 | "encoding/json"
25 | "errors"
26 | "fmt"
27 |
28 | "github.com/ethereum/go-ethereum/accounts"
29 | "github.com/ethereum/go-ethereum/crypto"
30 | "github.com/pborman/uuid"
31 | "golang.org/x/crypto/pbkdf2"
32 | )
33 |
34 | // creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
35 | func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) {
36 | key, err := decryptPreSaleKey(keyJSON, password)
37 | if err != nil {
38 | return accounts.Account{}, nil, err
39 | }
40 | key.Id = uuid.NewRandom()
41 | a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: keyStore.JoinPath(keyFileName(key.Address))}}
42 | err = keyStore.StoreKey(a.URL.Path, key, password)
43 | return a, key, err
44 | }
45 |
46 | func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
47 | preSaleKeyStruct := struct {
48 | EncSeed string
49 | EthAddr string
50 | Email string
51 | BtcAddr string
52 | }{}
53 | err = json.Unmarshal(fileContent, &preSaleKeyStruct)
54 | if err != nil {
55 | return nil, err
56 | }
57 | encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
58 | if err != nil {
59 | return nil, errors.New("invalid hex in encSeed")
60 | }
61 | iv := encSeedBytes[:16]
62 | cipherText := encSeedBytes[16:]
63 | /*
64 | See https://github.com/ethereum/pyethsaletool
65 |
66 | pyethsaletool generates the encryption key from password by
67 | 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
68 | 16 byte key length within PBKDF2 and resulting key is used as AES key
69 | */
70 | passBytes := []byte(password)
71 | derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
72 | plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
73 | if err != nil {
74 | return nil, err
75 | }
76 | ethPriv := crypto.Keccak256(plainText)
77 | ecKey, err := crypto.ToECDSA(ethPriv)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | key = &Key{
83 | Id: nil,
84 | Address: crypto.PubkeyToAddress(ecKey.PublicKey),
85 | PrivateKey: ecKey,
86 | }
87 | derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
88 | expectedAddr := preSaleKeyStruct.EthAddr
89 | if derivedAddr != expectedAddr {
90 | err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
91 | }
92 | return key, err
93 | }
94 |
95 | func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
96 | // AES-128 is selected due to size of encryptKey.
97 | aesBlock, err := aes.NewCipher(key)
98 | if err != nil {
99 | return nil, err
100 | }
101 | stream := cipher.NewCTR(aesBlock, iv)
102 | outText := make([]byte, len(inText))
103 | stream.XORKeyStream(outText, inText)
104 | return outText, err
105 | }
106 |
107 | func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
108 | aesBlock, err := aes.NewCipher(key)
109 | if err != nil {
110 | return nil, err
111 | }
112 | decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
113 | paddedPlaintext := make([]byte, len(cipherText))
114 | decrypter.CryptBlocks(paddedPlaintext, cipherText)
115 | plaintext := pkcs7Unpad(paddedPlaintext)
116 | if plaintext == nil {
117 | return nil, ErrDecrypt
118 | }
119 | return plaintext, err
120 | }
121 |
122 | // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
123 | func pkcs7Unpad(in []byte) []byte {
124 | if len(in) == 0 {
125 | return nil
126 | }
127 |
128 | padding := in[len(in)-1]
129 | if int(padding) > len(in) || padding > aes.BlockSize {
130 | return nil
131 | } else if padding == 0 {
132 | return nil
133 | }
134 |
135 | for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
136 | if in[i] != padding {
137 | return nil
138 | }
139 | }
140 | return in[:len(in)-int(padding)]
141 | }
142 |
--------------------------------------------------------------------------------
/accounts/keystore/testdata/dupes/1:
--------------------------------------------------------------------------------
1 | {"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/dupes/2:
--------------------------------------------------------------------------------
1 | {"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/dupes/foo:
--------------------------------------------------------------------------------
1 | {"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/.hiddenfile:
--------------------------------------------------------------------------------
1 | {"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
2 |
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/README:
--------------------------------------------------------------------------------
1 | This directory contains accounts for testing.
2 | The passphrase that unlocks them is "foobar".
3 |
4 | The "good" key files which are supposed to be loadable are:
5 |
6 | - File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
7 | Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8
8 | - File: aaa
9 | Address: 0xf466859ead1932d743d622cb74fc058882e8648a
10 | - File: zzz
11 | Address: 0x289d485d9771714cce91d3393d764e1311907acc
12 |
13 | The other files (including this README) are broken in various ways
14 | and should not be picked up by package accounts:
15 |
16 | - File: no-address (missing address field, otherwise same as "aaa")
17 | - File: garbage (file with random data)
18 | - File: empty (file with no content)
19 | - File: swapfile~ (should be skipped)
20 | - File: .hiddenfile (should be skipped)
21 | - File: foo/... (should be skipped because it is a directory)
22 |
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8:
--------------------------------------------------------------------------------
1 | {"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/aaa:
--------------------------------------------------------------------------------
1 | {"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/empty:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/accounts/keystore/testdata/keystore/empty
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e:
--------------------------------------------------------------------------------
1 | {"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/garbage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/accounts/keystore/testdata/keystore/garbage
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/no-address:
--------------------------------------------------------------------------------
1 | {"crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/zero:
--------------------------------------------------------------------------------
1 | {"address":"0000000000000000000000000000000000000000","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/keystore/zzz:
--------------------------------------------------------------------------------
1 | {"address":"289d485d9771714cce91d3393d764e1311907acc","crypto":{"cipher":"aes-128-ctr","ciphertext":"faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab","cipherparams":{"iv":"558833eec4a665a8c55608d7d503407d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04"},"mac":"21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe"},"id":"3279afcf-55ba-43ff-8997-02dcc46a6525","version":3}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e:
--------------------------------------------------------------------------------
1 | {"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"}
--------------------------------------------------------------------------------
/accounts/keystore/testdata/v1_test_vector.json:
--------------------------------------------------------------------------------
1 | {
2 | "test1": {
3 | "json": {
4 | "Crypto": {
5 | "cipher": "aes-128-cbc",
6 | "cipherparams": {
7 | "iv": "35337770fc2117994ecdcad026bccff4"
8 | },
9 | "ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0",
10 | "kdf": "scrypt",
11 | "kdfparams": {
12 | "dklen": 32,
13 | "n": 262144,
14 | "p": 1,
15 | "r": 8,
16 | "salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"
17 | },
18 | "mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644",
19 | "version": "1"
20 | },
21 | "address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e",
22 | "id": "e25f7c1f-d318-4f29-b62c-687190d4d299",
23 | "version": "1"
24 | },
25 | "password": "g",
26 | "priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/accounts/keystore/testdata/v3_test_vector.json:
--------------------------------------------------------------------------------
1 | {
2 | "wikipage_test_vector_scrypt": {
3 | "json": {
4 | "crypto" : {
5 | "cipher" : "aes-128-ctr",
6 | "cipherparams" : {
7 | "iv" : "83dbcc02d8ccb40e466191a123791e0e"
8 | },
9 | "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
10 | "kdf" : "scrypt",
11 | "kdfparams" : {
12 | "dklen" : 32,
13 | "n" : 262144,
14 | "r" : 1,
15 | "p" : 8,
16 | "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
17 | },
18 | "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
19 | },
20 | "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
21 | "version" : 3
22 | },
23 | "password": "testpassword",
24 | "priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
25 | },
26 | "wikipage_test_vector_pbkdf2": {
27 | "json": {
28 | "crypto" : {
29 | "cipher" : "aes-128-ctr",
30 | "cipherparams" : {
31 | "iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
32 | },
33 | "ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
34 | "kdf" : "pbkdf2",
35 | "kdfparams" : {
36 | "c" : 262144,
37 | "dklen" : 32,
38 | "prf" : "hmac-sha256",
39 | "salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
40 | },
41 | "mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
42 | },
43 | "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
44 | "version" : 3
45 | },
46 | "password": "testpassword",
47 | "priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
48 | },
49 | "31_byte_key": {
50 | "json": {
51 | "crypto" : {
52 | "cipher" : "aes-128-ctr",
53 | "cipherparams" : {
54 | "iv" : "e0c41130a323adc1446fc82f724bca2f"
55 | },
56 | "ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984",
57 | "kdf" : "scrypt",
58 | "kdfparams" : {
59 | "dklen" : 32,
60 | "n" : 2,
61 | "r" : 8,
62 | "p" : 1,
63 | "salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63"
64 | },
65 | "mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5"
66 | },
67 | "id" : "fecfc4ce-e956-48fd-953b-30f8b52ed66c",
68 | "version" : 3
69 | },
70 | "password": "foo",
71 | "priv": "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
72 | },
73 | "30_byte_key": {
74 | "json": {
75 | "crypto" : {
76 | "cipher" : "aes-128-ctr",
77 | "cipherparams" : {
78 | "iv" : "3ca92af36ad7c2cd92454c59cea5ef00"
79 | },
80 | "ciphertext" : "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa",
81 | "kdf" : "scrypt",
82 | "kdfparams" : {
83 | "dklen" : 32,
84 | "n" : 2,
85 | "r" : 8,
86 | "p" : 1,
87 | "salt" : "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b"
88 | },
89 | "mac" : "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420"
90 | },
91 | "id" : "a37e1559-5955-450d-8075-7b8931b392b2",
92 | "version" : 3
93 | },
94 | "password": "foo",
95 | "priv": "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/accounts/keystore/testdata/very-light-scrypt.json:
--------------------------------------------------------------------------------
1 | {"address":"45dea0fb0bba44f4fcf290bba71fd57d7117cbb8","crypto":{"cipher":"aes-128-ctr","ciphertext":"b87781948a1befd247bff51ef4063f716cf6c2d3481163e9a8f42e1f9bb74145","cipherparams":{"iv":"dc4926b48a105133d2f16b96833abf1e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"004244bbdc51cadda545b1cfa43cff9ed2ae88e08c61f1479dbb45410722f8f0"},"mac":"39990c1684557447940d4c69e06b1b82b2aceacb43f284df65c956daf3046b85"},"id":"ce541d8d-c79b-40f8-9f8c-20f59616faba","version":3}
2 |
--------------------------------------------------------------------------------
/accounts/keystore/wallet.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package keystore
18 |
19 | import (
20 | "math/big"
21 |
22 | "github.com/ethereum/go-ethereum"
23 | "github.com/ethereum/go-ethereum/accounts"
24 | "github.com/ethereum/go-ethereum/core/types"
25 | "github.com/ethereum/go-ethereum/crypto"
26 | )
27 |
28 | // keystoreWallet implements the accounts.Wallet interface for the original
29 | // keystore.
30 | type keystoreWallet struct {
31 | account accounts.Account // Single account contained in this wallet
32 | keystore *KeyStore // Keystore where the account originates from
33 | }
34 |
35 | // URL implements accounts.Wallet, returning the URL of the account within.
36 | func (w *keystoreWallet) URL() accounts.URL {
37 | return w.account.URL
38 | }
39 |
40 | // Status implements accounts.Wallet, returning whether the account held by the
41 | // keystore wallet is unlocked or not.
42 | func (w *keystoreWallet) Status() (string, error) {
43 | w.keystore.mu.RLock()
44 | defer w.keystore.mu.RUnlock()
45 |
46 | if _, ok := w.keystore.unlocked[w.account.Address]; ok {
47 | return "Unlocked", nil
48 | }
49 | return "Locked", nil
50 | }
51 |
52 | // Open implements accounts.Wallet, but is a noop for plain wallets since there
53 | // is no connection or decryption step necessary to access the list of accounts.
54 | func (w *keystoreWallet) Open(passphrase string) error { return nil }
55 |
56 | // Close implements accounts.Wallet, but is a noop for plain wallets since there
57 | // is no meaningful open operation.
58 | func (w *keystoreWallet) Close() error { return nil }
59 |
60 | // Accounts implements accounts.Wallet, returning an account list consisting of
61 | // a single account that the plain keystore wallet contains.
62 | func (w *keystoreWallet) Accounts() []accounts.Account {
63 | return []accounts.Account{w.account}
64 | }
65 |
66 | // Contains implements accounts.Wallet, returning whether a particular account is
67 | // or is not wrapped by this wallet instance.
68 | func (w *keystoreWallet) Contains(account accounts.Account) bool {
69 | return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL)
70 | }
71 |
72 | // Derive implements accounts.Wallet, but is a noop for plain wallets since there
73 | // is no notion of hierarchical account derivation for plain keystore accounts.
74 | func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
75 | return accounts.Account{}, accounts.ErrNotSupported
76 | }
77 |
78 | // SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
79 | // there is no notion of hierarchical account derivation for plain keystore accounts.
80 | func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
81 | }
82 |
83 | // signHash attempts to sign the given hash with
84 | // the given account. If the wallet does not wrap this particular account, an
85 | // error is returned to avoid account leakage (even though in theory we may be
86 | // able to sign via our shared keystore backend).
87 | func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
88 | // Make sure the requested account is contained within
89 | if !w.Contains(account) {
90 | return nil, accounts.ErrUnknownAccount
91 | }
92 | // Account seems valid, request the keystore to sign
93 | return w.keystore.SignHash(account, hash)
94 | }
95 |
96 | // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed.
97 | func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
98 | return w.signHash(account, crypto.Keccak256(data))
99 | }
100 |
101 | // SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed.
102 | func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
103 | // Make sure the requested account is contained within
104 | if !w.Contains(account) {
105 | return nil, accounts.ErrUnknownAccount
106 | }
107 | // Account seems valid, request the keystore to sign
108 | return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
109 | }
110 |
111 | // SignText implements accounts.Wallet, attempting to sign the hash of
112 | // the given text with the given account.
113 | func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
114 | return w.signHash(account, accounts.TextHash(text))
115 | }
116 |
117 | // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the
118 | // hash of the given text with the given account using passphrase as extra authentication.
119 | func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
120 | // Make sure the requested account is contained within
121 | if !w.Contains(account) {
122 | return nil, accounts.ErrUnknownAccount
123 | }
124 | // Account seems valid, request the keystore to sign
125 | return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
126 | }
127 |
128 | // SignTx implements accounts.Wallet, attempting to sign the given transaction
129 | // with the given account. If the wallet does not wrap this particular account,
130 | // an error is returned to avoid account leakage (even though in theory we may
131 | // be able to sign via our shared keystore backend).
132 | func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
133 | // Make sure the requested account is contained within
134 | if !w.Contains(account) {
135 | return nil, accounts.ErrUnknownAccount
136 | }
137 | // Account seems valid, request the keystore to sign
138 | return w.keystore.SignTx(account, tx, chainID)
139 | }
140 |
141 | // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
142 | // transaction with the given account using passphrase as extra authentication.
143 | func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
144 | // Make sure the requested account is contained within
145 | if !w.Contains(account) {
146 | return nil, accounts.ErrUnknownAccount
147 | }
148 | // Account seems valid, request the keystore to sign
149 | return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)
150 | }
151 |
--------------------------------------------------------------------------------
/accounts/keystore/watch.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | // +build darwin,!ios freebsd linux,!arm64 netbsd solaris
18 |
19 | package keystore
20 |
21 | import (
22 | "time"
23 |
24 | "github.com/ethereum/go-ethereum/log"
25 | "github.com/rjeczalik/notify"
26 | )
27 |
28 | type watcher struct {
29 | ac *accountCache
30 | starting bool
31 | running bool
32 | ev chan notify.EventInfo
33 | quit chan struct{}
34 | }
35 |
36 | func newWatcher(ac *accountCache) *watcher {
37 | return &watcher{
38 | ac: ac,
39 | ev: make(chan notify.EventInfo, 10),
40 | quit: make(chan struct{}),
41 | }
42 | }
43 |
44 | // starts the watcher loop in the background.
45 | // Start a watcher in the background if that's not already in progress.
46 | // The caller must hold w.ac.mu.
47 | func (w *watcher) start() {
48 | if w.starting || w.running {
49 | return
50 | }
51 | w.starting = true
52 | go w.loop()
53 | }
54 |
55 | func (w *watcher) close() {
56 | close(w.quit)
57 | }
58 |
59 | func (w *watcher) loop() {
60 | defer func() {
61 | w.ac.mu.Lock()
62 | w.running = false
63 | w.starting = false
64 | w.ac.mu.Unlock()
65 | }()
66 | logger := log.New("path", w.ac.keydir)
67 |
68 | if err := notify.Watch(w.ac.keydir, w.ev, notify.All); err != nil {
69 | logger.Trace("Failed to watch keystore folder", "err", err)
70 | return
71 | }
72 | defer notify.Stop(w.ev)
73 |
74 | logger.Trace("Started watching keystore folder")
75 | defer logger.Trace("Stopped watching keystore folder")
76 |
77 | w.ac.mu.Lock()
78 | w.running = true
79 | w.ac.mu.Unlock()
80 |
81 | // Wait for file system events and reload.
82 | // When an event occurs, the reload call is delayed a bit so that
83 | // multiple events arriving quickly only cause a single reload.
84 | var (
85 | debounce = time.NewTimer(0)
86 | debounceDuration = 500 * time.Millisecond
87 | inCycle, hadEvent bool
88 | )
89 | defer debounce.Stop()
90 | for {
91 | select {
92 | case <-w.quit:
93 | return
94 | case <-w.ev:
95 | if !inCycle {
96 | debounce.Reset(debounceDuration)
97 | inCycle = true
98 | } else {
99 | hadEvent = true
100 | }
101 | case <-debounce.C:
102 | w.ac.mu.Lock()
103 | w.ac.reload()
104 | w.ac.mu.Unlock()
105 | if hadEvent {
106 | debounce.Reset(debounceDuration)
107 | inCycle, hadEvent = true, false
108 | } else {
109 | inCycle, hadEvent = false, false
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/accounts/keystore/watch_fallback.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | // +build ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
18 |
19 | // This is the fallback implementation of directory watching.
20 | // It is used on unsupported platforms.
21 |
22 | package keystore
23 |
24 | type watcher struct{ running bool }
25 |
26 | func newWatcher(*accountCache) *watcher { return new(watcher) }
27 | func (*watcher) start() {}
28 | func (*watcher) close() {}
29 |
--------------------------------------------------------------------------------
/accounts/manager.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package accounts
18 |
19 | import (
20 | "reflect"
21 | "sort"
22 | "sync"
23 |
24 | "github.com/ethereum/go-ethereum/event"
25 | )
26 |
27 | // Manager is an overarching account manager that can communicate with various
28 | // backends for signing transactions.
29 | type Manager struct {
30 | backends map[reflect.Type][]Backend // Index of backends currently registered
31 | updaters []event.Subscription // Wallet update subscriptions for all backends
32 | updates chan WalletEvent // Subscription sink for backend wallet changes
33 | wallets []Wallet // Cache of all wallets from all registered backends
34 |
35 | feed event.Feed // Wallet feed notifying of arrivals/departures
36 |
37 | quit chan chan error
38 | lock sync.RWMutex
39 | }
40 |
41 | // NewManager creates a generic account manager to sign transaction via various
42 | // supported backends.
43 | func NewManager(backends ...Backend) *Manager {
44 | // Subscribe to wallet notifications from all backends
45 | updates := make(chan WalletEvent, 4*len(backends))
46 |
47 | subs := make([]event.Subscription, len(backends))
48 | for i, backend := range backends {
49 | subs[i] = backend.Subscribe(updates)
50 | }
51 | // Retrieve the initial list of wallets from the backends and sort by URL
52 | var wallets []Wallet
53 | for _, backend := range backends {
54 | wallets = merge(wallets, backend.Wallets()...)
55 | }
56 | // Assemble the account manager and return
57 | am := &Manager{
58 | backends: make(map[reflect.Type][]Backend),
59 | updaters: subs,
60 | updates: updates,
61 | wallets: wallets,
62 | quit: make(chan chan error),
63 | }
64 | for _, backend := range backends {
65 | kind := reflect.TypeOf(backend)
66 | am.backends[kind] = append(am.backends[kind], backend)
67 | }
68 | go am.update()
69 |
70 | return am
71 | }
72 |
73 | // Close terminates the account manager's internal notification processes.
74 | func (am *Manager) Close() error {
75 | errc := make(chan error)
76 | am.quit <- errc
77 | return <-errc
78 | }
79 |
80 | // update is the wallet event loop listening for notifications from the backends
81 | // and updating the cache of wallets.
82 | func (am *Manager) update() {
83 | // Close all subscriptions when the manager terminates
84 | defer func() {
85 | am.lock.Lock()
86 | for _, sub := range am.updaters {
87 | sub.Unsubscribe()
88 | }
89 | am.updaters = nil
90 | am.lock.Unlock()
91 | }()
92 |
93 | // Loop until termination
94 | for {
95 | select {
96 | case event := <-am.updates:
97 | // Wallet event arrived, update local cache
98 | am.lock.Lock()
99 | if event.Arrive {
100 | am.wallets = merge(am.wallets, event.Wallet)
101 | } else {
102 | am.wallets = drop(am.wallets, event.Wallet)
103 | }
104 | am.lock.Unlock()
105 |
106 | // Notify any listeners of the event
107 | am.feed.Send(event)
108 |
109 | case errc := <-am.quit:
110 | // Manager terminating, return
111 | errc <- nil
112 | return
113 | }
114 | }
115 | }
116 |
117 | // Backends retrieves the backend(s) with the given type from the account manager.
118 | func (am *Manager) Backends(kind reflect.Type) []Backend {
119 | return am.backends[kind]
120 | }
121 |
122 | // Wallets returns all signer accounts registered under this account manager.
123 | func (am *Manager) Wallets() []Wallet {
124 | am.lock.RLock()
125 | defer am.lock.RUnlock()
126 |
127 | cpy := make([]Wallet, len(am.wallets))
128 | copy(cpy, am.wallets)
129 | return cpy
130 | }
131 |
132 | // Wallet retrieves the wallet associated with a particular URL.
133 | func (am *Manager) Wallet(url string) (Wallet, error) {
134 | am.lock.RLock()
135 | defer am.lock.RUnlock()
136 |
137 | parsed, err := parseURL(url)
138 | if err != nil {
139 | return nil, err
140 | }
141 | for _, wallet := range am.Wallets() {
142 | if wallet.URL() == parsed {
143 | return wallet, nil
144 | }
145 | }
146 | return nil, ErrUnknownWallet
147 | }
148 |
149 | // Find attempts to locate the wallet corresponding to a specific account. Since
150 | // accounts can be dynamically added to and removed from wallets, this method has
151 | // a linear runtime in the number of wallets.
152 | func (am *Manager) Find(account Account) (Wallet, error) {
153 | am.lock.RLock()
154 | defer am.lock.RUnlock()
155 |
156 | for _, wallet := range am.wallets {
157 | if wallet.Contains(account) {
158 | return wallet, nil
159 | }
160 | }
161 | return nil, ErrUnknownAccount
162 | }
163 |
164 | // Subscribe creates an async subscription to receive notifications when the
165 | // manager detects the arrival or departure of a wallet from any of its backends.
166 | func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
167 | return am.feed.Subscribe(sink)
168 | }
169 |
170 | // merge is a sorted analogue of append for wallets, where the ordering of the
171 | // origin list is preserved by inserting new wallets at the correct position.
172 | //
173 | // The original slice is assumed to be already sorted by URL.
174 | func merge(slice []Wallet, wallets ...Wallet) []Wallet {
175 | for _, wallet := range wallets {
176 | n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
177 | if n == len(slice) {
178 | slice = append(slice, wallet)
179 | continue
180 | }
181 | slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...)
182 | }
183 | return slice
184 | }
185 |
186 | // drop is the counterpart of merge, which looks up wallets from within the sorted
187 | // cache and removes the ones specified.
188 | func drop(slice []Wallet, wallets ...Wallet) []Wallet {
189 | for _, wallet := range wallets {
190 | n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
191 | if n == len(slice) {
192 | // Wallet not found, may happen during startup
193 | continue
194 | }
195 | slice = append(slice[:n], slice[n+1:]...)
196 | }
197 | return slice
198 | }
199 |
--------------------------------------------------------------------------------
/accounts/url.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The go-ethereum Authors
2 | // This file is part of the go-ethereum library.
3 | //
4 | // The go-ethereum library is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Lesser General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // The go-ethereum library is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Lesser General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Lesser General Public License
15 | // along with the go-ethereum library. If not, see .
16 |
17 | package accounts
18 |
19 | import (
20 | "encoding/json"
21 | "errors"
22 | "fmt"
23 | "strings"
24 | )
25 |
26 | // URL represents the canonical identification URL of a wallet or account.
27 | //
28 | // It is a simplified version of url.URL, with the important limitations (which
29 | // are considered features here) that it contains value-copyable components only,
30 | // as well as that it doesn't do any URL encoding/decoding of special characters.
31 | //
32 | // The former is important to allow an account to be copied without leaving live
33 | // references to the original version, whereas the latter is important to ensure
34 | // one single canonical form opposed to many allowed ones by the RFC 3986 spec.
35 | //
36 | // As such, these URLs should not be used outside of the scope of an Ethereum
37 | // wallet or account.
38 | type URL struct {
39 | Scheme string // Protocol scheme to identify a capable account backend
40 | Path string // Path for the backend to identify a unique entity
41 | }
42 |
43 | // parseURL converts a user supplied URL into the accounts specific structure.
44 | func parseURL(url string) (URL, error) {
45 | parts := strings.Split(url, "://")
46 | if len(parts) != 2 || parts[0] == "" {
47 | return URL{}, errors.New("protocol scheme missing")
48 | }
49 | return URL{
50 | Scheme: parts[0],
51 | Path: parts[1],
52 | }, nil
53 | }
54 |
55 | // String implements the stringer interface.
56 | func (u URL) String() string {
57 | if u.Scheme != "" {
58 | return fmt.Sprintf("%s://%s", u.Scheme, u.Path)
59 | }
60 | return u.Path
61 | }
62 |
63 | // MarshalJSON implements the json.Marshaller interface.
64 | func (u URL) MarshalJSON() ([]byte, error) {
65 | return json.Marshal(u.String())
66 | }
67 |
68 | // Cmp compares x and y and returns:
69 | //
70 | // -1 if x < y
71 | // 0 if x == y
72 | // +1 if x > y
73 | //
74 | func (u URL) Cmp(url URL) int {
75 | if u.Scheme == url.Scheme {
76 | return strings.Compare(u.Path, url.Path)
77 | }
78 | return strings.Compare(u.Scheme, url.Scheme)
79 | }
80 |
--------------------------------------------------------------------------------
/build.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PROJECT_NAME="ethcracker"
4 |
5 | # Build for Linux
6 | GOOS=linux GOARCH=amd64 go build -o ${PROJECT_NAME}-linux
7 | if [ $? -eq 0 ]; then
8 | echo "Successfully built for Linux."
9 | else
10 | echo "Failed to build for Linux." >&2
11 | exit 1
12 | fi
13 |
14 | # Build for macOS (Intel)
15 | GOOS=darwin GOARCH=amd64 go build -o ${PROJECT_NAME}-macos
16 | if [ $? -eq 0 ]; then
17 | echo "Successfully built for macOS (Intel)."
18 | else
19 | echo "Failed to build for macOS (Intel)." >&2
20 | exit 1
21 | fi
22 |
23 | # Build for macOS (ARM, e.g., M1/M2)
24 | GOOS=darwin GOARCH=arm64 go build -o ${PROJECT_NAME}-macos-arm64
25 | if [ $? -eq 0 ]; then
26 | echo "Successfully built for macOS (ARM)."
27 | else
28 | echo "Failed to build for macOS (ARM)." >&2
29 | exit 1
30 | fi
31 |
32 | # Build for Windows
33 | GOOS=windows GOARCH=amd64 go build -o ${PROJECT_NAME}-windows.exe
34 | if [ $? -eq 0 ]; then
35 | echo "Successfully built for Windows."
36 | else
37 | echo "Failed to build for Windows." >&2
38 | exit 1
39 | fi
40 |
41 | # Summary
42 | echo "Builds completed:"
43 | ls -lh ${PROJECT_NAME}-*
--------------------------------------------------------------------------------
/dmg_pass.bash:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | i=0
4 |
5 | while IFS='' read -r line || [[ -n "$line" ]]; do
6 |
7 | ((i++))
8 |
9 | if [ $i -lt $3 ]; then
10 | echo "$i: $line skipped"
11 | continue
12 | fi
13 |
14 | echo "$i: $line"
15 |
16 | res="$(printf $line | hdiutil attach -quiet -stdinpass $2 )"
17 |
18 | # echo "RESULT: $?"
19 | if [ $? -eq 0 ]; then
20 |
21 | echo "================================================================"
22 | echo ""
23 | echo " The disk is successfully mounted!!! "
24 | echo " Your password is: $line"
25 | echo ""
26 | echo " Do not forget to donate some ETH to the developer:"
27 | echo " Ethereum Address: 0x281694Fabfdd9735e01bB59942B18c469b6e3df6"
28 | echo "================================================================"
29 | exit
30 | fi
31 |
32 | done < "$1"
--------------------------------------------------------------------------------
/ethcracker-linux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/ethcracker-linux
--------------------------------------------------------------------------------
/ethcracker-macos:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/ethcracker-macos
--------------------------------------------------------------------------------
/ethcracker-macos-arm64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/ethcracker-macos-arm64
--------------------------------------------------------------------------------
/ethcracker-windows.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/ethcracker-windows.exe
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/lexansoft/ethcracker
2 |
3 | go 1.22
4 |
5 | toolchain go1.22.10
6 |
7 | require (
8 | github.com/cespare/cp v1.1.1
9 | github.com/davecgh/go-spew v1.1.1
10 | github.com/ethereum/go-ethereum v1.14.12
11 | github.com/pborman/uuid v1.2.1
12 | github.com/rjeczalik/notify v0.9.3
13 | golang.org/x/crypto v0.31.0
14 | )
15 |
16 | require (
17 | github.com/bits-and-blooms/bitset v1.13.0 // indirect
18 | github.com/consensys/bavard v0.1.13 // indirect
19 | github.com/consensys/gnark-crypto v0.12.1 // indirect
20 | github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect
21 | github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
22 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
23 | github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
24 | github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect
25 | github.com/google/uuid v1.3.0 // indirect
26 | github.com/holiman/uint256 v1.3.1 // indirect
27 | github.com/mmcloughlin/addchain v0.4.0 // indirect
28 | github.com/supranational/blst v0.3.13 // indirect
29 | golang.org/x/sync v0.8.0 // indirect
30 | golang.org/x/sys v0.28.0 // indirect
31 | rsc.io/tmplfunc v0.0.3 // indirect
32 | )
33 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
2 | github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
3 | github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
4 | github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
5 | github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
6 | github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
7 | github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU=
8 | github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
9 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
10 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
11 | github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
12 | github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
13 | github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
14 | github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
15 | github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I=
16 | github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
17 | github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
18 | github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
22 | github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
23 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
25 | github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
26 | github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
27 | github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrTwcCQSe4=
28 | github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY=
29 | github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A=
30 | github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
31 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
32 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
33 | github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
34 | github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
35 | github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
36 | github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
37 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
38 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
39 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
40 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
41 | github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs=
42 | github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
43 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
44 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
45 | github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
46 | github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
47 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
48 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
49 | github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
50 | github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
51 | github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
52 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
53 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
54 | github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
55 | github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
58 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
59 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
60 | github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
61 | github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
62 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
63 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
64 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
65 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
66 | github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
67 | github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
68 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
69 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
70 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
71 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
72 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
73 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
74 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
75 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
76 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
77 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
78 | golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
79 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
80 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
81 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
82 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
83 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
84 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
85 | rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
86 | rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
87 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Written by @AlexNa
2 |
3 | package main
4 |
5 | import (
6 | "bufio"
7 | "flag"
8 | "os"
9 | "strconv"
10 | "strings"
11 | "sync"
12 | "unicode"
13 |
14 | // "encoding/json"
15 | "fmt"
16 | "time"
17 |
18 | "github.com/lexansoft/ethcracker/accounts/keystore"
19 | )
20 |
21 | var templates [][]string
22 |
23 | type TEMP_FLAGS struct {
24 | UseAlways bool
25 | Capitalize bool
26 | }
27 |
28 | var templates_flags []TEMP_FLAGS
29 |
30 | // note, that variables are pointers
31 | var pk = flag.String("pk", "", "Private key file")
32 | var t = flag.String("t", "", "Pattern file")
33 | var l = flag.String("l", "", "File with list of variants. If specified, -t is ignored")
34 | var min_len = flag.Int("min_len", 8, "Minimum password length")
35 | var max_len = flag.Int("max_len", 20, "Maximum password length")
36 | var n_threads = flag.Int("threads", 4, "Number of threads")
37 | var pre_sale = flag.Bool("presale", false, "The key file is the presale JSON")
38 | var keep_order = flag.Bool("keep_order", false, "Keep order of the lines (no permutations)")
39 | var v = flag.Int("v", 1, "Verbosity ( 0, 1, 2 )")
40 | var re = flag.Int("re", 1, "Report every N-th combination")
41 | var start_from = flag.String("start_from", "0", "Skip first N combinations")
42 | var dump = flag.String("dump", "", "Just output all the possible variants")
43 |
44 | var params keystore.CrackerParams
45 | var chans []chan string
46 | var wg sync.WaitGroup
47 | var f_dump *os.File
48 |
49 | func fact(x int) int {
50 | if x == 0 {
51 | return 1
52 | }
53 | return x * fact(x-1)
54 | }
55 |
56 | func safe_add(a []string, s string) []string {
57 | for _, n := range a {
58 | if n == s {
59 | return a
60 | }
61 | }
62 |
63 | return append(a, s)
64 | }
65 |
66 | func main() {
67 | var err error
68 |
69 | flag.Parse()
70 |
71 | if *dump != "" {
72 | *v = 0
73 | *n_threads = 1
74 |
75 | f_dump, err = os.Create(*dump)
76 | if err != nil {
77 | panic(err)
78 | }
79 |
80 | defer f_dump.Close()
81 | }
82 |
83 | if *v > 0 {
84 | println("------------------------------------------------")
85 | println("Ethereum Password Cracker v2.16 ")
86 | println("Author: @AlexNa ")
87 | println("------------------------------------------------")
88 | println("Private Key File:", *pk)
89 | println("Template File:", *t)
90 | println("Verbosity:", *v)
91 | println("Minimum password length:", *min_len)
92 | println("Maximum password length:", *max_len)
93 | println("Number of threads:", *n_threads)
94 | println("Presale file:", *pre_sale)
95 | println("Keep order:", *keep_order)
96 | }
97 |
98 | if *re <= 0 {
99 | panic("wrong -re")
100 | }
101 |
102 | if *v > 0 {
103 | println("Report every :", *re, "combination")
104 | }
105 |
106 | params.V = *v
107 |
108 | params.StartTime = time.Now()
109 | params.RE = *re
110 |
111 | if *pk == "" {
112 | panic("No key file")
113 | }
114 |
115 | if *n_threads < 1 || *n_threads > 32 {
116 | panic("Wrong muber of threads ")
117 | }
118 |
119 | if *n_threads > 1 {
120 | wg.Add(*n_threads)
121 | chans = make([]chan string, *n_threads)
122 | for i := 0; i < *n_threads; i++ {
123 | chans[i] = make(chan string)
124 |
125 | go func(index int) {
126 |
127 | for {
128 | s := <-chans[index]
129 |
130 | if s == "" {
131 | wg.Done()
132 | break
133 | }
134 |
135 | keystore.Test_pass(¶ms, s, index)
136 | }
137 |
138 | }(i)
139 | }
140 | }
141 |
142 | if *pre_sale {
143 | err := keystore.LoadPresaleFile(¶ms, *pk)
144 | if err != nil {
145 | panic(err)
146 | }
147 | } else {
148 | err := keystore.LoadKeyFile(¶ms, *pk, *v)
149 | if err != nil {
150 | panic(err)
151 | }
152 | }
153 |
154 | templates = make([][]string, 0)
155 | templates_flags = make([]TEMP_FLAGS, 0)
156 |
157 | pl := make([]string, 0)
158 |
159 | if *l != "" {
160 | f, err := os.Open(*l)
161 | if err != nil {
162 | panic(err)
163 | }
164 |
165 | scanner := bufio.NewScanner(f)
166 | for scanner.Scan() {
167 | pl = append(pl, scanner.Text())
168 | }
169 |
170 | if err := scanner.Err(); err != nil {
171 | panic(err)
172 | }
173 | f.Close()
174 | } else {
175 | if *t == "" {
176 | panic("No template file")
177 | }
178 |
179 | f, err := os.Open(*t)
180 | if err != nil {
181 | panic(err)
182 | }
183 |
184 | scanner := bufio.NewScanner(f)
185 | for scanner.Scan() {
186 |
187 | templ := make([]string, 0)
188 |
189 | tl := strings.Split(scanner.Text(), " ")
190 |
191 | for _, n := range tl {
192 | if n == "" {
193 | continue
194 | }
195 | if n == " " {
196 | continue
197 | }
198 |
199 | templ = safe_add(templ, n)
200 | }
201 |
202 | if len(templ) >= 0 {
203 |
204 | for i, _ := range templ {
205 | templ[i] = strings.Replace(templ[i], "\\s", " ", -1)
206 | }
207 |
208 | var tf TEMP_FLAGS
209 |
210 | if len(templ) > 0 && strings.HasPrefix(templ[0], "~") {
211 | if len(templ) == 1 {
212 | continue
213 | } //nothing but flags...
214 |
215 | tf.UseAlways = strings.Index(templ[0], "a") > 0
216 | tf.Capitalize = strings.Index(templ[0], "c") > 0
217 |
218 | templ = templ[1:] //remove the first
219 | }
220 |
221 | if tf.Capitalize {
222 | t := make([]string, 0)
223 |
224 | for _, n := range templ {
225 | if len(n) > 0 {
226 | t = safe_add(t, string(unicode.ToUpper([]rune(n)[0]))+n[1:])
227 | t = safe_add(t, string(unicode.ToLower([]rune(n)[0]))+n[1:])
228 | }
229 | templ = t
230 | }
231 | }
232 |
233 | if len(templ) > 0 {
234 | templates = append(templates, templ)
235 | templates_flags = append(templates_flags, tf)
236 | }
237 |
238 | //println( "templates_flags:", len( templates_flags ) - 1, tf.UseAlways )
239 | }
240 | }
241 |
242 | if err := scanner.Err(); err != nil {
243 | panic(err)
244 | }
245 |
246 | f.Close()
247 |
248 | if *v > 0 {
249 | println("Template lines:", len(templates))
250 | }
251 | }
252 |
253 | //calculate number of variants:
254 |
255 | counters := make([]int, len(templates)+1)
256 | indexes := make([]int, len(templates))
257 | for i := 0; i < len(indexes); i++ {
258 | if templates_flags[i].UseAlways {
259 | indexes[i] = 1
260 | }
261 | }
262 |
263 | if *l != "" {
264 | params.Total = len(pl)
265 | } else if *keep_order {
266 |
267 | a_exists := false
268 |
269 | params.Total = 1
270 | for i := 0; i < len(templates); i++ {
271 | if templates_flags[i].UseAlways {
272 | params.Total = params.Total * len(templates[i])
273 | a_exists = true
274 | } else {
275 | params.Total = params.Total * (len(templates[i]) + 1)
276 | }
277 | }
278 | if !a_exists {
279 | params.Total = params.Total - 1
280 | }
281 | } else {
282 | // if len( templates ) > 20 { panic( "Too many templates. No way you have so much powerful computer...")}
283 |
284 | counter:
285 | for {
286 | not_zero := 0
287 | for _, k := range indexes {
288 | if k != 0 {
289 | not_zero++
290 | }
291 | }
292 |
293 | counters[not_zero]++
294 |
295 | for i := 0; i < len(indexes); i++ {
296 |
297 | if indexes[i] < len(templates[i]) {
298 | indexes[i] = indexes[i] + 1
299 | break
300 | } else {
301 | if templates_flags[i].UseAlways {
302 | indexes[i] = 1
303 | } else {
304 | indexes[i] = 0
305 | }
306 | if i == len(templates)-1 {
307 | break counter
308 | }
309 | }
310 | }
311 | }
312 |
313 | for i, c := range counters {
314 | //println( "counters ", i, c )
315 | params.Total += c * fact(i)
316 |
317 | //XAXA if params.Total > 10000000000 { panic( "Too many templates. No way you have so much powerful computer...")}
318 |
319 | }
320 | }
321 |
322 | if *v > 0 {
323 | println("Total possible variants:", params.Total)
324 | }
325 |
326 | if strings.HasSuffix(*start_from, "%") {
327 |
328 | p, err := strconv.Atoi((*start_from)[:len(*start_from)-1])
329 | if err != nil {
330 | panic("Wrong start_from percents: " + *start_from)
331 | }
332 |
333 | if p < 0 || p >= 100 {
334 | panic("Wrong start_from percents: " + *start_from)
335 | }
336 |
337 | params.Start_from = params.Total * p / 100
338 | } else {
339 | n, err := strconv.Atoi(*start_from)
340 | if err != nil {
341 | panic("Wrong start_from: " + *start_from)
342 | }
343 |
344 | params.Start_from = n
345 | }
346 |
347 | if *v > 0 {
348 | println("---------------- STARTING ----------------------")
349 | }
350 |
351 | //main cycle
352 | if *l != "" {
353 | for _, np := range pl {
354 | if *n_threads == 1 {
355 | keystore.Test_pass(¶ms, np, 0)
356 | } else {
357 | chans[params.N%*n_threads] <- np
358 | }
359 | }
360 | } else {
361 |
362 | indexes = make([]int, len(templates))
363 | for i := 0; i < len(indexes); i++ {
364 | if templates_flags[i].UseAlways {
365 | indexes[i] = 1
366 | }
367 | }
368 |
369 | main:
370 | for {
371 |
372 | letters := make([]string, 0)
373 |
374 | for i := 0; i < len(indexes); i++ {
375 | if indexes[i] > 0 {
376 | letters = append(letters, templates[i][indexes[i]-1])
377 | }
378 | }
379 |
380 | letters_str := ""
381 | for _, l := range letters {
382 | letters_str += "(" + l + ") "
383 | }
384 |
385 | if *v > 1 && params.N >= params.Start_from {
386 | println("Selected letters:", letters_str)
387 | }
388 |
389 | if *keep_order {
390 | test(letters)
391 | } else {
392 |
393 | s := ""
394 | for _, n := range letters {
395 | s = s + n
396 | }
397 | if len(s) > *max_len {
398 | N := fact(len(letters))
399 | params.Skipped = params.Skipped + N
400 |
401 | if params.V > 1 {
402 | fmt.Printf("Skipped %d too long variants\n", N)
403 | }
404 |
405 | } else {
406 | AllPermutations(letters, 0)
407 | }
408 | }
409 |
410 | for i := 0; i < len(indexes); i++ {
411 |
412 | if indexes[i] < len(templates[i]) {
413 | indexes[i] = indexes[i] + 1
414 | break
415 | } else {
416 | if templates_flags[i].UseAlways {
417 | indexes[i] = 1
418 | } else {
419 | indexes[i] = 0
420 | }
421 | if i == len(templates)-1 {
422 | break main
423 | }
424 | }
425 | }
426 |
427 | }
428 | }
429 |
430 | //wait for threads to finish
431 | if *n_threads > 1 {
432 | for i := 0; i < *n_threads; i++ {
433 | chans[i] <- ""
434 | }
435 | wg.Wait()
436 | }
437 |
438 | if *v > 0 {
439 | println(":-( Sorry... password not found")
440 | if params.Skipped > 0 {
441 | println("NOTE:", params.Skipped, "variants skipped because of length limitations")
442 | }
443 | }
444 | }
445 |
446 | func test(l []string) {
447 | s := ""
448 | for _, n := range l {
449 | s = s + n
450 | }
451 |
452 | if s == "" {
453 | return
454 | }
455 |
456 | if len(s) < *min_len || len(s) > *max_len {
457 | params.Skipped = params.Skipped + 1
458 |
459 | h := time.Since(params.StartTime).Hours() *
460 | float64(params.Total-(params.N+params.Skipped)) / float64(params.N+params.Skipped-params.Start_from)
461 |
462 | if params.N+params.Skipped > params.Start_from {
463 | if params.Start_from > 0 && (params.N+params.Skipped)%(100000) == 0 {
464 | fmt.Printf("Skipping first %d -> %d %d%% Skipped: %d Left: %d years %d days %d hours %d minutes %v\n",
465 | params.Start_from,
466 | params.N+params.Skipped,
467 | (params.N+params.Skipped)*100/params.Start_from,
468 | params.Skipped,
469 | int64(h)/(24*365), (int64(h)%(24*365))/24, int64(h)%24, int64(h*60)%60,
470 | s)
471 | }
472 | } else {
473 | if (params.N+params.Skipped)%(params.RE*10) == 0 {
474 | fmt.Printf("-----> %d/%d %d%% Skipped: %d Left: %d years %d days %d hours %d minutes \n",
475 | params.N+params.Skipped,
476 | params.Total,
477 | (params.N+params.Skipped)*100/params.Total,
478 | params.Skipped,
479 | int64(h)/(24*365), (int64(h)%(24*365))/24, int64(h)%24, int64(h*60)%60)
480 | }
481 | }
482 |
483 | return
484 | }
485 |
486 | if *dump != "" {
487 | f_dump.Write([]byte(s + "\n"))
488 | return
489 | }
490 |
491 | if *n_threads == 1 {
492 | keystore.Test_pass(¶ms, s, 0)
493 | } else {
494 | chans[params.N%*n_threads] <- s
495 | }
496 | }
497 |
498 | func makecopy(l []string) []string {
499 | nl := make([]string, len(l))
500 | copy(nl, l)
501 | return nl
502 | }
503 |
504 | func AllPermutations(l []string, index int) {
505 |
506 | if index >= len(l)-1 {
507 | test(l)
508 | return
509 | }
510 |
511 | r := len(l) - index
512 |
513 | AllPermutations(l, index+1)
514 | for j := 1; j < r; j++ {
515 | //swap i and i + j
516 | tmp := l[index]
517 | l[index] = l[index+j]
518 | l[index+j] = tmp
519 | AllPermutations(makecopy(l), index+1)
520 | }
521 | }
522 |
--------------------------------------------------------------------------------
/run.bash:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | go run . -pk /data/ec/w.json -t /data/ec/templates.txt -threads 7 -keep_order -min_len 1 -max_len 50 -v 1 -re 1
4 | #go run src/ethcracker.go -pk ~/test/ethwallet-q.json -t ~/test/pattern.txt -presale -threads 4 -min_len 1 -v 1 -start_from 0
5 | # -dump ~/test/v.txt
--------------------------------------------------------------------------------
/tmp.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexansoft/ethcracker/543baa80147116513513993b4e4d20a8fe0b3c5a/tmp.txt
--------------------------------------------------------------------------------