github.com/0xsequence/ethkit@v1.25.0/cmd/ethkit/wallet.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "log" 9 "os" 10 "strings" 11 "syscall" 12 13 "github.com/0xsequence/ethkit/ethwallet" 14 "github.com/0xsequence/ethkit/go-ethereum/accounts/keystore" 15 "github.com/0xsequence/ethkit/go-ethereum/common" 16 "github.com/spf13/cobra" 17 "golang.org/x/crypto/ssh/terminal" 18 ) 19 20 func init() { 21 wallet := &wallet{} 22 cmd := &cobra.Command{ 23 Use: "wallet", 24 Short: "EOA wallet", 25 Run: wallet.Run, 26 } 27 28 cmd.Flags().String("keyfile", "", "wallet key file path") 29 cmd.Flags().Bool("new", false, "create a new wallet and save it to the keyfile") 30 cmd.Flags().Bool("print-account", true, "print wallet account address from keyfile") 31 cmd.Flags().Bool("print-mnemonic", false, "print wallet secret mnemonic from keyfile (danger!)") 32 cmd.Flags().Bool("print-private-key", false, "print wallet private key from keyfile (danger!)") 33 cmd.Flags().Bool("import-mnemonic", false, "import a secret mnemonic to a new keyfile") 34 cmd.Flags().String("path", "", fmt.Sprintf("set derivation path, default: %s", ethwallet.DefaultWalletOptions.DerivationPath)) 35 36 rootCmd.AddCommand(cmd) 37 } 38 39 type wallet struct { 40 // flags 41 fKeyFile string 42 fCreateNew bool 43 fPrintAccount bool 44 fPrintMnemonic bool 45 fPrintPrivateKey bool 46 fImportMnemonic bool 47 fPath string 48 49 // wallet key file 50 keyFile walletKeyFile 51 wallet *ethwallet.Wallet 52 } 53 54 func (c *wallet) Run(cmd *cobra.Command, args []string) { 55 c.fKeyFile, _ = cmd.Flags().GetString("keyfile") 56 c.fCreateNew, _ = cmd.Flags().GetBool("new") 57 c.fPrintAccount, _ = cmd.Flags().GetBool("print-account") 58 c.fPrintMnemonic, _ = cmd.Flags().GetBool("print-mnemonic") 59 c.fPrintPrivateKey, _ = cmd.Flags().GetBool("print-private-key") 60 c.fImportMnemonic, _ = cmd.Flags().GetBool("import-mnemonic") 61 c.fPath, _ = cmd.Flags().GetString("path") 62 63 if c.fKeyFile == "" { 64 fmt.Println("error: please pass --keyfile") 65 help(cmd) 66 return 67 } 68 if fileExists(c.fKeyFile) && (c.fCreateNew || c.fImportMnemonic) { 69 fmt.Println("error: keyfile already exists on this filename, for safety we do not overwrite existing keyfiles.") 70 help(cmd) 71 return 72 } 73 if !c.fCreateNew && !c.fImportMnemonic && !c.fPrintMnemonic && !c.fPrintPrivateKey && !c.fPrintAccount { 74 fmt.Println("error: not enough options provided to ethkit cli.") 75 help(cmd) 76 return 77 } 78 79 // Gen new wallet 80 if c.fCreateNew || c.fImportMnemonic { 81 if err := c.createNew(); err != nil { 82 log.Fatal(err) 83 } 84 return 85 } 86 87 // Load wallet from the key file 88 data, err := os.ReadFile(c.fKeyFile) 89 if err != nil { 90 log.Fatal(err) 91 } 92 keyFile := walletKeyFile{} 93 err = json.Unmarshal(data, &keyFile) 94 if err != nil { 95 log.Fatal(err) 96 } 97 c.keyFile = keyFile 98 99 derivationPath := c.fPath 100 if derivationPath == "" { 101 derivationPath = c.keyFile.Path 102 } 103 104 pw, err := readSecretInput("Password: ") 105 if err != nil { 106 log.Fatal(err) 107 } 108 109 cipherText, err := keystore.DecryptDataV3(c.keyFile.Crypto, string(pw)) 110 if err != nil { 111 log.Fatal(err) 112 } 113 114 wallet, err := ethwallet.NewWalletFromMnemonic(string(cipherText), derivationPath) 115 if err != nil { 116 log.Fatal(err) 117 } 118 c.wallet = wallet 119 120 // Print mnemonic 121 if c.fPrintMnemonic { 122 if err := c.printMnemonic(); err != nil { 123 log.Fatal(err) 124 } 125 return 126 } 127 128 // Print private key 129 if c.fPrintPrivateKey { 130 if err := c.printPrivateKey(); err != nil { 131 log.Fatal(err) 132 } 133 return 134 } 135 136 // Print acconut address 137 if c.fPrintAccount { 138 if err := c.printAccount(); err != nil { 139 log.Fatal(err) 140 } 141 return 142 } 143 } 144 145 func (c *wallet) printMnemonic() error { 146 fmt.Println("") 147 fmt.Println("=> Your Ethereum private mnemonic is:") 148 fmt.Println("=>", c.wallet.HDNode().Mnemonic()) 149 fmt.Println("") 150 return nil 151 } 152 153 func (c *wallet) printPrivateKey() error { 154 fmt.Println("") 155 fmt.Println("=> Your Ethereum private key is:") 156 fmt.Println("=>", c.wallet.PrivateKeyHex()) 157 fmt.Println("") 158 return nil 159 } 160 161 func (c *wallet) printAccount() error { 162 fmt.Println("") 163 fmt.Println("") 164 fmt.Println("=> Your Ethereum wallet address is:", c.wallet.Address().String()) 165 fmt.Println("") 166 return nil 167 } 168 169 func (c *wallet) createNew() error { 170 var err error 171 var importMnemonic string 172 173 if c.fImportMnemonic { 174 var mnemonic []byte 175 // TODO: use crypto/terminal and print *'s on each keypress of input 176 mnemonic, err = readPlainInput("Enter your mnemonic to import: ") 177 if err != nil { 178 return err 179 } 180 importMnemonic = strings.TrimSpace(string(mnemonic)) 181 } 182 183 derivationPath := c.fPath 184 if derivationPath == "" { 185 derivationPath = ethwallet.DefaultWalletOptions.DerivationPath 186 } 187 188 c.wallet, err = getWallet(importMnemonic, derivationPath) 189 if err != nil { 190 return err 191 } 192 193 pw, err := readSecretInput("Password: ") 194 if err != nil { 195 return err 196 } 197 if len(pw) < 8 { 198 return errors.New("password must be at least 8 characters") 199 } 200 201 fmt.Println("") 202 confirmPw, err := readSecretInput("Confirm Password: ") 203 if err != nil { 204 return err 205 } 206 if string(pw) != string(confirmPw) { 207 return errors.New("passwords do not match") 208 } 209 210 cryptoJSON, err := keystore.EncryptDataV3([]byte(c.wallet.HDNode().Mnemonic()), pw, keystore.StandardScryptN, keystore.StandardScryptP) 211 if err != nil { 212 return err 213 } 214 215 keyFile := walletKeyFile{ 216 Address: c.wallet.Address(), 217 Path: c.wallet.HDNode().DerivationPath().String(), 218 Crypto: cryptoJSON, 219 Client: fmt.Sprintf("ethkit/%s - github.com/0xsequence/ethkit", VERSION), 220 } 221 222 data, err := json.MarshalIndent(keyFile, "", " ") 223 if err != nil { 224 return err 225 } 226 data = append(data, []byte("\n")...) 227 228 if err := os.WriteFile(c.fKeyFile, data, 0600); err != nil { 229 return err 230 } 231 232 fmt.Println("") 233 fmt.Println("") 234 fmt.Println("=> success! ethkit has generated a new Ethereum wallet for you and saved") 235 fmt.Println("=> it in an encrypted+password protected file at:") 236 fmt.Println("=> ---") 237 fmt.Println("=>", c.fKeyFile) 238 fmt.Println("") 239 fmt.Printf("=> to confirm, please run: ./ethkit wallet --keyfile=%s --print-account\n", c.fKeyFile) 240 fmt.Println("") 241 fmt.Println("=> Your new Ethereum wallet address is:", c.wallet.Address().String()) 242 fmt.Println("") 243 244 return nil 245 } 246 247 type walletKeyFile struct { 248 Address common.Address `json:"address"` 249 Path string `json:"path"` 250 Crypto keystore.CryptoJSON `json:"crypto"` 251 Client string `json:"client"` 252 } 253 254 func fileExists(filename string) bool { 255 info, err := os.Stat(filename) 256 if os.IsNotExist(err) { 257 return false 258 } 259 return !info.IsDir() 260 } 261 262 func readSecretInput(prompt string) ([]byte, error) { 263 fmt.Print(prompt) 264 password, err := terminal.ReadPassword(int(syscall.Stdin)) 265 if err != nil { 266 return nil, err 267 } 268 return password, nil 269 } 270 271 func readPlainInput(prompt string) ([]byte, error) { 272 fmt.Print(prompt) 273 reader := bufio.NewReader(os.Stdin) 274 text, _ := reader.ReadString('\n') 275 return []byte(text), nil 276 } 277 278 func getWallet(mnemonic, derivationPath string) (*ethwallet.Wallet, error) { 279 var err error 280 var wallet *ethwallet.Wallet 281 282 if derivationPath == "" { 283 return nil, fmt.Errorf("derivationPath cannot be empty") 284 } 285 286 if mnemonic != "" { 287 wallet, err = ethwallet.NewWalletFromMnemonic(mnemonic, derivationPath) 288 } else { 289 wallet, err = ethwallet.NewWalletFromRandomEntropy(ethwallet.WalletOptions{ 290 DerivationPath: derivationPath, 291 RandomWalletEntropyBitSize: ethwallet.EntropyBitSize24WordMnemonic, 292 }) 293 } 294 if err != nil { 295 return nil, err 296 } 297 298 return wallet, nil 299 }