github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/account/account.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package account 7 8 import ( 9 "bytes" 10 "context" 11 "crypto/ecdsa" 12 "encoding/hex" 13 "fmt" 14 "os" 15 "path/filepath" 16 "strings" 17 18 "github.com/ethereum/go-ethereum/accounts/keystore" 19 ecrypto "github.com/ethereum/go-ethereum/crypto" 20 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 21 "github.com/pkg/errors" 22 "github.com/spf13/cobra" 23 "google.golang.org/grpc/status" 24 25 "github.com/iotexproject/go-pkgs/crypto" 26 "github.com/iotexproject/go-pkgs/hash" 27 "github.com/iotexproject/iotex-address/address" 28 "github.com/iotexproject/iotex-proto/golang/iotexapi" 29 "github.com/iotexproject/iotex-proto/golang/iotextypes" 30 31 "github.com/iotexproject/iotex-core/ioctl/cmd/hdwallet" 32 "github.com/iotexproject/iotex-core/ioctl/config" 33 "github.com/iotexproject/iotex-core/ioctl/output" 34 "github.com/iotexproject/iotex-core/ioctl/util" 35 "github.com/iotexproject/iotex-core/ioctl/validator" 36 ) 37 38 // Multi-language support 39 var ( 40 _accountCmdShorts = map[config.Language]string{ 41 config.English: "Manage accounts of IoTeX blockchain", 42 config.Chinese: "管理IoTeX区块链上的账号", 43 } 44 _flagEndpoint = map[config.Language]string{ 45 config.English: "set endpoint for once", 46 config.Chinese: "一次设置端点", 47 } 48 _flagInsecure = map[config.Language]string{ 49 config.English: "insecure connection for once", 50 config.Chinese: "一次不安全连接", 51 } 52 ) 53 54 // Errors 55 var ( 56 ErrPasswdNotMatch = errors.New("password doesn't match") 57 ) 58 59 // CryptoSm2 is a flag for sm2 cryptographic algorithm 60 var CryptoSm2 bool 61 62 // AccountCmd represents the account command 63 var AccountCmd = &cobra.Command{ 64 Use: "account", 65 Short: config.TranslateInLang(_accountCmdShorts, config.UILanguage), 66 } 67 68 func init() { 69 AccountCmd.AddCommand(accountBalanceCmd) 70 AccountCmd.AddCommand(_accountCreateCmd) 71 AccountCmd.AddCommand(_accountCreateAddCmd) 72 AccountCmd.AddCommand(_accountDeleteCmd) 73 AccountCmd.AddCommand(_accountEthaddrCmd) 74 AccountCmd.AddCommand(_accountExportCmd) 75 AccountCmd.AddCommand(_accountExportPublicCmd) 76 AccountCmd.AddCommand(_accountImportCmd) 77 AccountCmd.AddCommand(_accountInfoCmd) 78 AccountCmd.AddCommand(_accountListCmd) 79 AccountCmd.AddCommand(_accountNonceCmd) 80 AccountCmd.AddCommand(_accountSignCmd) 81 AccountCmd.AddCommand(_accountUpdateCmd) 82 AccountCmd.AddCommand(_accountVerifyCmd) 83 AccountCmd.AddCommand(_accountActionsCmd) 84 AccountCmd.PersistentFlags().StringVar(&config.ReadConfig.Endpoint, "endpoint", 85 config.ReadConfig.Endpoint, config.TranslateInLang(_flagEndpoint, config.UILanguage)) 86 AccountCmd.PersistentFlags().BoolVar(&config.Insecure, "insecure", config.Insecure, config.TranslateInLang(_flagInsecure, config.UILanguage)) 87 } 88 89 // Sign sign message with signer 90 func Sign(signer, password, message string) (string, error) { 91 pri, err := PrivateKeyFromSigner(signer, password) 92 if err != nil { 93 return "", err 94 } 95 mes := message 96 head := message[:2] 97 if strings.EqualFold(head, "0x") { 98 mes = message[2:] 99 } 100 b, err := hex.DecodeString(mes) 101 if err != nil { 102 return "", err 103 } 104 prefix := fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(b)) 105 msg := append([]byte(prefix), b...) 106 mesToSign := hash.Hash256b(msg) 107 ret, err := pri.Sign(mesToSign[:]) 108 if err != nil { 109 return "", err 110 } 111 return hex.EncodeToString(ret), nil 112 } 113 114 // keyStoreAccountToPrivateKey generates our PrivateKey interface from Keystore account 115 func keyStoreAccountToPrivateKey(signer, password string) (crypto.PrivateKey, error) { 116 addrString, err := util.Address(signer) 117 if err != nil { 118 return nil, err 119 } 120 addr, err := address.FromString(addrString) 121 if err != nil { 122 return nil, fmt.Errorf("invalid account #%s, addr %s", signer, addrString) 123 } 124 125 if CryptoSm2 { 126 // find the account in pem files 127 pemFilePath := sm2KeyPath(addr) 128 prvKey, err := crypto.ReadPrivateKeyFromPem(pemFilePath, password) 129 if err == nil { 130 return prvKey, nil 131 } 132 } else { 133 // find the account in keystore 134 ks := keystore.NewKeyStore(config.ReadConfig.Wallet, 135 keystore.StandardScryptN, keystore.StandardScryptP) 136 for _, account := range ks.Accounts() { 137 if bytes.Equal(addr.Bytes(), account.Address.Bytes()) { 138 return crypto.KeystoreToPrivateKey(account, password) 139 } 140 } 141 } 142 143 return nil, fmt.Errorf("account #%s does not match all local keys", signer) 144 } 145 146 // PrivateKeyFromSigner returns private key from signer 147 func PrivateKeyFromSigner(signer, password string) (crypto.PrivateKey, error) { 148 var ( 149 prvKey crypto.PrivateKey 150 err error 151 ) 152 153 if !IsSignerExist(signer) && !util.AliasIsHdwalletKey(signer) { 154 return nil, fmt.Errorf("invalid address #%s", signer) 155 } 156 157 if password == "" { 158 output.PrintQuery(fmt.Sprintf("Enter password for #%s:\n", signer)) 159 password, err = util.ReadSecretFromStdin() 160 if err != nil { 161 return nil, output.NewError(output.InputError, "failed to get password", err) 162 } 163 } 164 165 if util.AliasIsHdwalletKey(signer) { 166 account, change, index, err := util.ParseHdwPath(signer) 167 if err != nil { 168 return nil, output.NewError(output.InputError, "invalid HDWallet key format", err) 169 } 170 _, prvKey, err = hdwallet.DeriveKey(account, change, index, password) 171 if err != nil { 172 return nil, output.NewError(output.InputError, "failed to derive key from HDWallet", err) 173 } 174 return prvKey, nil 175 } 176 return keyStoreAccountToPrivateKey(signer, password) 177 } 178 179 // GetAccountMeta gets account metadata 180 func GetAccountMeta(addr string) (*iotextypes.AccountMeta, error) { 181 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 182 if err != nil { 183 return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err) 184 } 185 defer conn.Close() 186 cli := iotexapi.NewAPIServiceClient(conn) 187 ctx := context.Background() 188 request := iotexapi.GetAccountRequest{Address: addr} 189 190 jwtMD, err := util.JwtAuth() 191 if err == nil { 192 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 193 } 194 response, err := cli.GetAccount(ctx, &request) 195 196 if err != nil { 197 sta, ok := status.FromError(err) 198 if ok { 199 return nil, output.NewError(output.APIError, sta.Message(), nil) 200 } 201 return nil, output.NewError(output.NetworkError, "failed to invoke GetAccount api", err) 202 } 203 return response.AccountMeta, nil 204 } 205 206 // IsSignerExist checks whether signer account is existed 207 func IsSignerExist(signer string) bool { 208 addr, err := address.FromString(signer) 209 if err != nil { 210 return false 211 } 212 213 if CryptoSm2 { 214 // find the account in pem files 215 _, err = findSm2PemFile(addr) 216 return err == nil 217 } 218 219 // find the account in keystore 220 ks := keystore.NewKeyStore(config.ReadConfig.Wallet, 221 keystore.StandardScryptN, keystore.StandardScryptP) 222 for _, ksAccount := range ks.Accounts() { 223 if address.Equal(addr, ksAccount.Address) { 224 return true 225 } 226 } 227 228 return false 229 } 230 231 func newAccount(alias string) (string, error) { 232 output.PrintQuery(fmt.Sprintf("#%s: Set password\n", alias)) 233 password, err := util.ReadSecretFromStdin() 234 if err != nil { 235 return "", output.NewError(output.InputError, "failed to get password", err) 236 } 237 output.PrintQuery(fmt.Sprintf("#%s: Enter password again\n", alias)) 238 passwordAgain, err := util.ReadSecretFromStdin() 239 if err != nil { 240 return "", output.NewError(output.InputError, "failed to get password", err) 241 } 242 if password != passwordAgain { 243 return "", output.NewError(output.ValidationError, ErrPasswdNotMatch.Error(), nil) 244 } 245 ks := keystore.NewKeyStore(config.ReadConfig.Wallet, keystore.StandardScryptN, keystore.StandardScryptP) 246 account, err := ks.NewAccount(password) 247 if err != nil { 248 return "", output.NewError(output.KeystoreError, "failed to create new keystore", err) 249 } 250 addr, err := address.FromBytes(account.Address.Bytes()) 251 if err != nil { 252 return "", output.NewError(output.ConvertError, "failed to convert bytes into address", err) 253 } 254 return addr.String(), nil 255 } 256 257 func newAccountSm2(alias string) (string, error) { 258 output.PrintQuery(fmt.Sprintf("#%s: Set password\n", alias)) 259 password, err := util.ReadSecretFromStdin() 260 if err != nil { 261 return "", output.NewError(output.InputError, "failed to get password", err) 262 } 263 output.PrintQuery(fmt.Sprintf("#%s: Enter password again\n", alias)) 264 passwordAgain, err := util.ReadSecretFromStdin() 265 if err != nil { 266 return "", output.NewError(output.InputError, "failed to get password", err) 267 } 268 if password != passwordAgain { 269 return "", output.NewError(output.ValidationError, ErrPasswdNotMatch.Error(), nil) 270 } 271 priKey, err := crypto.GenerateKeySm2() 272 if err != nil { 273 return "", output.NewError(output.CryptoError, "failed to generate sm2 private key", err) 274 } 275 276 addr := priKey.PublicKey().Address() 277 if addr == nil { 278 return "", output.NewError(output.ConvertError, "failed to convert bytes into address", nil) 279 } 280 281 pemFilePath := sm2KeyPath(addr) 282 if err := crypto.WritePrivateKeyToPem(pemFilePath, priKey.(*crypto.P256sm2PrvKey), password); err != nil { 283 return "", output.NewError(output.KeystoreError, "failed to save private key into pem file ", err) 284 } 285 286 return addr.String(), nil 287 } 288 289 func newAccountByKey(alias string, privateKey string, walletDir string) (string, error) { 290 output.PrintQuery(fmt.Sprintf("#%s: Set password\n", alias)) 291 password, err := util.ReadSecretFromStdin() 292 if err != nil { 293 return "", output.NewError(output.InputError, "failed to get password", err) 294 } 295 output.PrintQuery(fmt.Sprintf("#%s: Enter password again\n", alias)) 296 passwordAgain, err := util.ReadSecretFromStdin() 297 if err != nil { 298 return "", output.NewError(output.InputError, "failed to get password", err) 299 } 300 if password != passwordAgain { 301 return "", output.NewError(output.ValidationError, ErrPasswdNotMatch.Error(), nil) 302 } 303 304 return storeKey(privateKey, walletDir, password) 305 } 306 307 func newAccountByKeyStore(alias, passwordOfKeyStore, keyStorePath string, walletDir string) (string, error) { 308 keyJSON, err := os.ReadFile(filepath.Clean(keyStorePath)) 309 if err != nil { 310 return "", output.NewError(output.ReadFileError, 311 fmt.Sprintf("keystore file \"%s\" read error", keyStorePath), nil) 312 } 313 key, err := keystore.DecryptKey(keyJSON, passwordOfKeyStore) 314 if key != nil && key.PrivateKey != nil { 315 // clear private key in memory prevent from attack 316 defer func(k *ecdsa.PrivateKey) { 317 b := k.D.Bits() 318 for i := range b { 319 b[i] = 0 320 } 321 }(key.PrivateKey) 322 } 323 if err != nil { 324 return "", output.NewError(output.KeystoreError, "failed to decrypt key", err) 325 } 326 return newAccountByKey(alias, hex.EncodeToString(ecrypto.FromECDSA(key.PrivateKey)), walletDir) 327 } 328 329 func newAccountByPem(alias, passwordOfPem, pemFilePath string, walletDir string) (string, error) { 330 prvKey, err := crypto.ReadPrivateKeyFromPem(pemFilePath, passwordOfPem) 331 if err != nil { 332 return "", output.NewError(output.CryptoError, "failed to read private key from pem file", err) 333 } 334 335 return newAccountByKey(alias, prvKey.HexString(), walletDir) 336 } 337 338 func storeKey(privateKey, walletDir, password string) (string, error) { 339 priKey, err := crypto.HexStringToPrivateKey(privateKey) 340 if err != nil { 341 return "", output.NewError(output.CryptoError, "failed to generate private key from hex string ", err) 342 } 343 defer priKey.Zero() 344 345 addr := priKey.PublicKey().Address() 346 if addr == nil { 347 return "", output.NewError(output.ConvertError, "failed to convert bytes into address", nil) 348 } 349 350 switch sk := priKey.EcdsaPrivateKey().(type) { 351 case *ecdsa.PrivateKey: 352 ks := keystore.NewKeyStore(walletDir, keystore.StandardScryptN, keystore.StandardScryptP) 353 if _, err := ks.ImportECDSA(sk, password); err != nil { 354 return "", output.NewError(output.KeystoreError, "failed to import private key into keystore ", err) 355 } 356 case *crypto.P256sm2PrvKey: 357 pemFilePath := sm2KeyPath(addr) 358 if err := crypto.WritePrivateKeyToPem(pemFilePath, sk, password); err != nil { 359 return "", output.NewError(output.KeystoreError, "failed to save private key into pem file ", err) 360 } 361 default: 362 return "", output.NewError(output.CryptoError, "invalid private key", nil) 363 } 364 365 return addr.String(), nil 366 } 367 368 func sm2KeyPath(addr address.Address) string { 369 return filepath.Join(config.ReadConfig.Wallet, "sm2sk-"+addr.String()+".pem") 370 } 371 372 func findSm2PemFile(addr address.Address) (string, error) { 373 filePath := sm2KeyPath(addr) 374 _, err := os.Stat(filePath) 375 if err != nil { 376 return "", output.NewError(output.ReadFileError, "crypto file not found", err) 377 } 378 return filePath, nil 379 } 380 381 func listSm2Account() ([]string, error) { 382 sm2Accounts := make([]string, 0) 383 files, err := os.ReadDir(config.ReadConfig.Wallet) 384 if err != nil { 385 return nil, output.NewError(output.ReadFileError, "failed to read files in wallet", err) 386 } 387 for _, f := range files { 388 if !f.IsDir() { 389 if strings.HasSuffix(f.Name(), ".pem") { 390 addr := strings.TrimSuffix(strings.TrimPrefix(f.Name(), "sm2sk-"), ".pem") 391 if err := validator.ValidateAddress(addr); err == nil { 392 sm2Accounts = append(sm2Accounts, addr) 393 } 394 } 395 } 396 } 397 return sm2Accounts, nil 398 }