github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/newcmd/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 ecrypto "github.com/ethereum/go-ethereum/crypto" 19 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 20 "github.com/iotexproject/go-pkgs/crypto" 21 "github.com/iotexproject/go-pkgs/hash" 22 "github.com/iotexproject/iotex-address/address" 23 "github.com/iotexproject/iotex-proto/golang/iotexapi" 24 "github.com/iotexproject/iotex-proto/golang/iotextypes" 25 "github.com/pkg/errors" 26 "github.com/spf13/cobra" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/status" 29 30 "github.com/iotexproject/iotex-core/ioctl" 31 "github.com/iotexproject/iotex-core/ioctl/config" 32 "github.com/iotexproject/iotex-core/ioctl/newcmd/hdwallet" 33 "github.com/iotexproject/iotex-core/ioctl/util" 34 "github.com/iotexproject/iotex-core/ioctl/validator" 35 ) 36 37 // Multi-language support 38 var ( 39 _accountCmdShorts = map[config.Language]string{ 40 config.English: "Manage accounts of IoTeX blockchain", 41 config.Chinese: "管理IoTeX区块链上的账号", 42 } 43 ) 44 45 // Errors 46 var ( 47 // ErrPasswdNotMatch indicates that the second input password is different from the first 48 ErrPasswdNotMatch = errors.New("password doesn't match") 49 ) 50 51 // NewAccountCmd represents the account command 52 func NewAccountCmd(client ioctl.Client) *cobra.Command { 53 accountShorts, _ := client.SelectTranslation(_accountCmdShorts) 54 55 ac := &cobra.Command{ 56 Use: "account", 57 Short: accountShorts, 58 } 59 ac.AddCommand(NewAccountCreate(client)) 60 ac.AddCommand(NewAccountCreateAdd(client)) 61 ac.AddCommand(NewAccountDelete(client)) 62 ac.AddCommand(NewAccountNonce(client)) 63 ac.AddCommand(NewAccountList(client)) 64 ac.AddCommand(NewAccountSign(client)) 65 ac.AddCommand(NewAccountUpdate(client)) 66 ac.AddCommand(NewAccountInfo(client)) 67 ac.AddCommand(NewAccountVerify(client)) 68 ac.AddCommand(NewAccountEthAddr(client)) 69 ac.AddCommand(NewAccountExportPublic(client)) 70 ac.AddCommand(NewAccountExport(client)) 71 ac.AddCommand(NewAccountImportCmd(client)) 72 ac.AddCommand(NewAccountBalance(client)) 73 74 client.SetEndpointWithFlag(ac.PersistentFlags().StringVar) 75 client.SetInsecureWithFlag(ac.PersistentFlags().BoolVar) 76 return ac 77 } 78 79 // Sign sign message with signer 80 func Sign(client ioctl.Client, cmd *cobra.Command, signer, password, message string) (string, error) { 81 pri, err := PrivateKeyFromSigner(client, cmd, signer, password) 82 if err != nil { 83 return "", err 84 } 85 mes := message 86 head := message[:2] 87 if strings.EqualFold(head, "0x") { 88 mes = message[2:] 89 } 90 b, err := hex.DecodeString(mes) 91 if err != nil { 92 return "", err 93 } 94 prefix := fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(b)) 95 msg := append([]byte(prefix), b...) 96 mesToSign := hash.Hash256b(msg) 97 ret, err := pri.Sign(mesToSign[:]) 98 if err != nil { 99 return "", err 100 } 101 return hex.EncodeToString(ret), nil 102 } 103 104 // keyStoreAccountToPrivateKey generates our PrivateKey interface from Keystore account 105 func keyStoreAccountToPrivateKey(client ioctl.Client, signer, password string) (crypto.PrivateKey, error) { 106 addrString, err := client.Address(signer) 107 if err != nil { 108 return nil, err 109 } 110 addr, err := address.FromString(addrString) 111 if err != nil { 112 return nil, fmt.Errorf("invalid account #%s, addr %s", signer, addrString) 113 } 114 115 if client.IsCryptoSm2() { 116 // find the account in pem files 117 pemFilePath := sm2KeyPath(client, addr) 118 prvKey, err := crypto.ReadPrivateKeyFromPem(pemFilePath, password) 119 if err == nil { 120 return prvKey, nil 121 } 122 } else { 123 // find the account in keystore 124 ks := client.NewKeyStore() 125 for _, account := range ks.Accounts() { 126 if bytes.Equal(addr.Bytes(), account.Address.Bytes()) { 127 return crypto.KeystoreToPrivateKey(account, password) 128 } 129 } 130 } 131 return nil, fmt.Errorf("account #%s does not match all local keys", signer) 132 } 133 134 // PrivateKeyFromSigner returns private key from signer 135 func PrivateKeyFromSigner(client ioctl.Client, cmd *cobra.Command, signer, password string) (crypto.PrivateKey, error) { 136 var ( 137 prvKey crypto.PrivateKey 138 err error 139 ) 140 141 if !IsSignerExist(client, signer) && !util.AliasIsHdwalletKey(signer) { 142 return nil, fmt.Errorf("invalid address #%s", signer) 143 } 144 145 if password == "" { 146 cmd.Printf("Enter password for #%s:\n", signer) 147 password, err = client.ReadSecret() 148 if err != nil { 149 return nil, errors.Wrap(err, "failed to get password") 150 } 151 } 152 153 if util.AliasIsHdwalletKey(signer) { 154 account, change, index, err := util.ParseHdwPath(signer) 155 if err != nil { 156 return nil, errors.Wrap(err, "invalid HDWallet key format") 157 } 158 _, prvKey, err = hdwallet.DeriveKey(client, account, change, index, password) 159 if err != nil { 160 return nil, errors.Wrap(err, "failed to derive key from HDWallet") 161 } 162 return prvKey, nil 163 } 164 return keyStoreAccountToPrivateKey(client, signer, password) 165 } 166 167 // Meta gets account metadata 168 func Meta(client ioctl.Client, addr string) (*iotextypes.AccountMeta, error) { 169 apiServiceClient, err := client.APIServiceClient() 170 if err != nil { 171 return nil, err 172 } 173 174 ctx := context.Background() 175 jwtMD, err := util.JwtAuth() 176 if err == nil { 177 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 178 } 179 180 response, err := apiServiceClient.GetAccount(ctx, &iotexapi.GetAccountRequest{Address: addr}) 181 if err != nil { 182 if sta, ok := status.FromError(err); ok { 183 if sta.Code() == codes.Unavailable { 184 return nil, ioctl.ErrInvalidEndpointOrInsecure 185 } 186 return nil, errors.New(sta.Message()) 187 } 188 return nil, errors.Wrap(err, "failed to invoke GetAccount api") 189 } 190 return response.AccountMeta, nil 191 } 192 193 // IsSignerExist checks whether signer account is existed 194 func IsSignerExist(client ioctl.Client, signer string) bool { 195 addr, err := address.FromString(signer) 196 if err != nil { 197 return false 198 } 199 200 if client.IsCryptoSm2() { 201 // find the account in pem files 202 _, err = findSm2PemFile(client, addr) 203 return err == nil 204 } 205 206 // find the account in keystore 207 ks := client.NewKeyStore() 208 for _, ksAccount := range ks.Accounts() { 209 if address.Equal(addr, ksAccount.Address) { 210 return true 211 } 212 } 213 214 return false 215 } 216 217 func newAccount(client ioctl.Client, cmd *cobra.Command, alias string) (string, error) { 218 cmd.Printf("#%s: Set password\n", alias) 219 password, err := client.ReadSecret() 220 if err != nil { 221 return "", errors.Wrap(err, "failed to get password") 222 } 223 cmd.Printf("#%s: Enter password again\n", alias) 224 passwordAgain, err := client.ReadSecret() 225 if err != nil { 226 return "", errors.Wrap(err, "failed to get password") 227 } 228 if password != passwordAgain { 229 return "", ErrPasswdNotMatch 230 } 231 ks := client.NewKeyStore() 232 account, err := ks.NewAccount(password) 233 if err != nil { 234 return "", errors.Wrap(err, "failed to create new keystore") 235 } 236 addr, err := address.FromBytes(account.Address.Bytes()) 237 if err != nil { 238 return "", errors.Wrap(err, "failed to convert bytes into address") 239 } 240 return addr.String(), nil 241 } 242 243 func newAccountSm2(client ioctl.Client, cmd *cobra.Command, alias string) (string, error) { 244 cmd.Printf("#%s: Set password\n", alias) 245 password, err := client.ReadSecret() 246 if err != nil { 247 return "", errors.Wrap(err, "failed to get password") 248 } 249 cmd.Printf("#%s: Enter password again\n", alias) 250 passwordAgain, err := client.ReadSecret() 251 if err != nil { 252 return "", errors.Wrap(err, "failed to get password") 253 } 254 if password != passwordAgain { 255 return "", ErrPasswdNotMatch 256 } 257 priKey, err := crypto.GenerateKeySm2() 258 if err != nil { 259 return "", errors.Wrap(err, "failed to generate sm2 private key") 260 } 261 262 addr := priKey.PublicKey().Address() 263 if addr == nil { 264 return "", errors.New("failed to convert bytes into address") 265 } 266 267 pemFilePath := sm2KeyPath(client, addr) 268 if err := crypto.WritePrivateKeyToPem(pemFilePath, priKey.(*crypto.P256sm2PrvKey), password); err != nil { 269 return "", errors.Wrap(err, "failed to save private key into pem file ") 270 } 271 272 return addr.String(), nil 273 } 274 275 func newAccountByKey(client ioctl.Client, cmd *cobra.Command, alias string, privateKey string) (string, error) { 276 cmd.Printf("#%s: Set password\n", alias) 277 password, err := client.ReadSecret() 278 if err != nil { 279 return "", errors.Wrap(err, "failed to get password") 280 } 281 cmd.Printf("#%s: Enter password again\n", alias) 282 passwordAgain, err := client.ReadSecret() 283 if err != nil { 284 return "", errors.Wrap(err, "failed to get password") 285 } 286 if password != passwordAgain { 287 return "", ErrPasswdNotMatch 288 } 289 290 return storeKey(client, privateKey, password) 291 } 292 293 func newAccountByKeyStore(client ioctl.Client, cmd *cobra.Command, alias, passwordOfKeyStore, keyStorePath string) (string, error) { 294 privateKey, err := client.DecryptPrivateKey(passwordOfKeyStore, keyStorePath) 295 if err != nil { 296 return "", err 297 } 298 return newAccountByKey(client, cmd, alias, hex.EncodeToString(ecrypto.FromECDSA(privateKey))) 299 } 300 301 func newAccountByPem(client ioctl.Client, cmd *cobra.Command, alias, passwordOfPem, pemFilePath string) (string, error) { 302 prvKey, err := crypto.ReadPrivateKeyFromPem(pemFilePath, passwordOfPem) 303 if err != nil { 304 return "", errors.Wrap(err, "failed to read private key from pem file") 305 } 306 307 return newAccountByKey(client, cmd, alias, prvKey.HexString()) 308 } 309 310 func storeKey(client ioctl.Client, privateKey, password string) (string, error) { 311 priKey, err := crypto.HexStringToPrivateKey(privateKey) 312 if err != nil { 313 return "", errors.Wrap(err, "failed to generate private key from hex string ") 314 } 315 defer priKey.Zero() 316 317 addr := priKey.PublicKey().Address() 318 if addr == nil { 319 return "", errors.New("failed to convert bytes into address") 320 } 321 322 switch sk := priKey.EcdsaPrivateKey().(type) { 323 case *ecdsa.PrivateKey: 324 ks := client.NewKeyStore() 325 if _, err := ks.ImportECDSA(sk, password); err != nil { 326 return "", errors.Wrap(err, "failed to import private key into keystore ") 327 } 328 case *crypto.P256sm2PrvKey: 329 pemFilePath := sm2KeyPath(client, addr) 330 if err := crypto.WritePrivateKeyToPem(pemFilePath, sk, password); err != nil { 331 return "", errors.Wrap(err, "failed to save private key into pem file ") 332 } 333 default: 334 return "", errors.New("invalid private key") 335 } 336 337 return addr.String(), nil 338 } 339 340 func sm2KeyPath(client ioctl.Client, addr address.Address) string { 341 return filepath.Join(client.Config().Wallet, "sm2sk-"+addr.String()+".pem") 342 } 343 344 func findSm2PemFile(client ioctl.Client, addr address.Address) (string, error) { 345 filePath := sm2KeyPath(client, addr) 346 _, err := os.Stat(filePath) 347 if err != nil { 348 return "", errors.Wrap(err, "crypto file not found") 349 } 350 return filePath, nil 351 } 352 353 func listSm2Account(client ioctl.Client) ([]string, error) { 354 sm2Accounts := make([]string, 0) 355 files, err := os.ReadDir(client.Config().Wallet) 356 if err != nil { 357 return nil, errors.Wrap(err, "failed to read files in wallet") 358 } 359 for _, f := range files { 360 if !f.IsDir() { 361 if strings.HasSuffix(f.Name(), ".pem") { 362 addr := strings.TrimSuffix(strings.TrimPrefix(f.Name(), "sm2sk-"), ".pem") 363 if err := validator.ValidateAddress(addr); err == nil { 364 sm2Accounts = append(sm2Accounts, addr) 365 } 366 } 367 } 368 } 369 return sm2Accounts, nil 370 }