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  }