├── 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 --------------------------------------------------------------------------------