github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/newcmd/account/account_test.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  	"crypto/ecdsa"
    11  	"fmt"
    12  	"math/rand"
    13  	"os"
    14  	"strconv"
    15  	"testing"
    16  
    17  	"github.com/ethereum/go-ethereum/accounts/keystore"
    18  	"github.com/golang/mock/gomock"
    19  	"github.com/iotexproject/go-pkgs/crypto"
    20  	"github.com/iotexproject/go-pkgs/hash"
    21  	"github.com/iotexproject/iotex-address/address"
    22  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    23  	"github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi"
    24  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    25  	"github.com/pkg/errors"
    26  	"github.com/spf13/cobra"
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"github.com/iotexproject/iotex-core/ioctl/config"
    30  	"github.com/iotexproject/iotex-core/ioctl/util"
    31  	"github.com/iotexproject/iotex-core/test/identityset"
    32  	"github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient"
    33  )
    34  
    35  const (
    36  	_testPath        = "testNewAccount"
    37  	veryLightScryptN = 2
    38  	veryLightScryptP = 1
    39  )
    40  
    41  func TestNewAccountCmd(t *testing.T) {
    42  	require := require.New(t)
    43  	ctrl := gomock.NewController(t)
    44  	defer ctrl.Finish()
    45  	client := mock_ioctlclient.NewMockClient(ctrl)
    46  	client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).AnyTimes()
    47  
    48  	testData := []struct {
    49  		endpoint string
    50  		insecure bool
    51  	}{
    52  		{
    53  			endpoint: "111:222:333:444:5678",
    54  			insecure: false,
    55  		},
    56  		{
    57  			endpoint: "",
    58  			insecure: true,
    59  		},
    60  	}
    61  	for _, test := range testData {
    62  		callbackEndpoint := func(cb func(*string, string, string, string)) {
    63  			cb(&test.endpoint, "endpoint", test.endpoint, "endpoint usage")
    64  		}
    65  		callbackInsecure := func(cb func(*bool, string, bool, string)) {
    66  			cb(&test.insecure, "insecure", !test.insecure, "insecure usage")
    67  		}
    68  		client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint)
    69  		client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure)
    70  
    71  		cmd := NewAccountCmd(client)
    72  		result, err := util.ExecuteCmd(cmd)
    73  		require.NoError(err)
    74  		require.Contains(result, "Available Commands")
    75  
    76  		result, err = util.ExecuteCmd(cmd, "--endpoint", "0.0.0.0:1", "--insecure")
    77  		require.NoError(err)
    78  		require.Contains(result, "Available Commands")
    79  		require.Equal("0.0.0.0:1", test.endpoint)
    80  		require.True(test.insecure)
    81  	}
    82  }
    83  
    84  func TestSign(t *testing.T) {
    85  	require := require.New(t)
    86  	_, ks, passwd, _, err := newTestAccountWithKeyStore(t, keystore.StandardScryptN, keystore.StandardScryptP)
    87  	require.NoError(err)
    88  
    89  	ctrl := gomock.NewController(t)
    90  	defer ctrl.Finish()
    91  	client := mock_ioctlclient.NewMockClient(ctrl)
    92  	client.EXPECT().NewKeyStore().Return(ks).Times(15)
    93  	client.EXPECT().IsCryptoSm2().Return(false).Times(15)
    94  
    95  	account, err := ks.NewAccount(passwd)
    96  	require.NoError(err)
    97  	addr, err := address.FromBytes(account.Address.Bytes())
    98  	require.NoError(err)
    99  	require.True(IsSignerExist(client, addr.String()))
   100  	cmd := &cobra.Command{}
   101  	cmd.SetOut(new(bytes.Buffer))
   102  	client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil).Times(7)
   103  
   104  	result, err := Sign(client, cmd, addr.String(), passwd, "abcd")
   105  	require.NoError(err)
   106  	require.NotEmpty(result)
   107  
   108  	result, err = Sign(client, cmd, addr.String(), passwd, "0xe3a1")
   109  	require.NoError(err)
   110  	require.NotEmpty(result)
   111  
   112  	// wrong message
   113  	_, err = Sign(client, cmd, addr.String(), passwd, "abc")
   114  	require.Error(err)
   115  	require.Contains(err.Error(), "odd length hex string")
   116  
   117  	// invalid singer
   118  	_, err = Sign(client, cmd, "hdw::aaaa", passwd, "0xe3a1")
   119  	require.Error(err)
   120  	require.Contains(err.Error(), "invalid HDWallet key format")
   121  
   122  	// wrong password
   123  	_, err = Sign(client, cmd, addr.String(), "123456", "abcd")
   124  	require.Error(err)
   125  	require.Contains(err.Error(), "could not decrypt key with given password")
   126  
   127  	// invalid signer
   128  	_, err = Sign(client, cmd, "bace9b2435db45b119e1570b4ea9c57993b2311e0c408d743d87cd22838ae892", "123456", "test")
   129  	require.Error(err)
   130  	require.Contains(err.Error(), "invalid address")
   131  
   132  	prvKey, err := PrivateKeyFromSigner(client, cmd, addr.String(), passwd)
   133  	require.NoError(err)
   134  	require.Equal(addr.String(), prvKey.PublicKey().Address().String())
   135  
   136  	// wrong password
   137  	prvKey, err = PrivateKeyFromSigner(client, cmd, addr.String(), "123456")
   138  	require.Error(err)
   139  	require.Contains(err.Error(), "could not decrypt key with given password")
   140  	require.Nil(prvKey)
   141  
   142  	// empty password
   143  	client.EXPECT().ReadSecret().Return(passwd, nil)
   144  	prvKey, err = PrivateKeyFromSigner(client, cmd, addr.String(), "")
   145  	require.NoError(err)
   146  	require.Equal(addr.String(), prvKey.PublicKey().Address().String())
   147  }
   148  
   149  func TestAccount(t *testing.T) {
   150  	require := require.New(t)
   151  	testWallet, ks, passwd, nonce, err := newTestAccountWithKeyStore(t, veryLightScryptN, veryLightScryptP)
   152  	require.NoError(err)
   153  
   154  	ctrl := gomock.NewController(t)
   155  	defer ctrl.Finish()
   156  	client := mock_ioctlclient.NewMockClient(ctrl)
   157  
   158  	t.Run("CryptoSm2 is false", func(t *testing.T) {
   159  		client.EXPECT().IsCryptoSm2().Return(false).Times(2)
   160  		client.EXPECT().NewKeyStore().Return(ks).Times(2)
   161  
   162  		// test new account by ks
   163  		account, err := ks.NewAccount(passwd)
   164  		require.NoError(err)
   165  		addr, err := address.FromBytes(account.Address.Bytes())
   166  		require.NoError(err)
   167  		require.True(IsSignerExist(client, addr.String()))
   168  		client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil)
   169  
   170  		// test keystore conversion and signing
   171  		prvKey, err := keyStoreAccountToPrivateKey(client, addr.String(), passwd)
   172  		require.NoError(err)
   173  		msg := hash.Hash256b([]byte(nonce))
   174  		sig, err := prvKey.Sign(msg[:])
   175  		require.NoError(err)
   176  		require.True(prvKey.PublicKey().Verify(msg[:], sig))
   177  
   178  		// test import existing key
   179  		sk, err := crypto.GenerateKey()
   180  		require.NoError(err)
   181  		p256k1, ok := sk.EcdsaPrivateKey().(*ecdsa.PrivateKey)
   182  		require.True(ok)
   183  		account, err = ks.ImportECDSA(p256k1, passwd)
   184  		require.NoError(err)
   185  		require.Equal(sk.PublicKey().Hash(), account.Address.Bytes())
   186  	})
   187  
   188  	t.Run("CryptoSm2 is true", func(t *testing.T) {
   189  		client.EXPECT().IsCryptoSm2().Return(true).Times(4)
   190  		client.EXPECT().Config().Return(config.Config{Wallet: testWallet}).Times(8)
   191  
   192  		// test store unexisted key
   193  		account2, err := crypto.GenerateKeySm2()
   194  		require.NoError(err)
   195  		require.NotNil(account2)
   196  		addr2 := account2.PublicKey().Address()
   197  		require.NotNil(addr2)
   198  		require.False(IsSignerExist(client, addr2.String()))
   199  		client.EXPECT().Address(gomock.Any()).Return(addr2.String(), nil).Times(2)
   200  		_, err = keyStoreAccountToPrivateKey(client, addr2.String(), passwd)
   201  		require.Contains(err.Error(), "does not match all local keys")
   202  		filePath := sm2KeyPath(client, addr2)
   203  		addrString, err := storeKey(client, account2.HexString(), passwd)
   204  		require.NoError(err)
   205  		require.Equal(addr2.String(), addrString)
   206  		require.True(IsSignerExist(client, addr2.String()))
   207  
   208  		// test findSm2PemFile
   209  		path, err := findSm2PemFile(client, addr2)
   210  		require.NoError(err)
   211  		require.Equal(filePath, path)
   212  
   213  		// test listSm2Account
   214  		accounts, err := listSm2Account(client)
   215  		require.NoError(err)
   216  		require.Equal(1, len(accounts))
   217  		require.Equal(addr2.String(), accounts[0])
   218  
   219  		// test keyStoreAccountToPrivateKey
   220  		prvKey2, err := keyStoreAccountToPrivateKey(client, addr2.String(), passwd)
   221  		require.NoError(err)
   222  		msg2 := hash.Hash256b([]byte(nonce))
   223  		sig2, err := prvKey2.Sign(msg2[:])
   224  		require.NoError(err)
   225  		require.True(prvKey2.PublicKey().Verify(msg2[:], sig2))
   226  	})
   227  }
   228  
   229  func TestMeta(t *testing.T) {
   230  	require := require.New(t)
   231  	ctrl := gomock.NewController(t)
   232  	defer ctrl.Finish()
   233  	client := mock_ioctlclient.NewMockClient(ctrl)
   234  	client.EXPECT().Config().Return(config.Config{}).AnyTimes()
   235  
   236  	apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl)
   237  	client.EXPECT().APIServiceClient().Return(apiServiceClient, nil)
   238  
   239  	accAddr := identityset.Address(28).String()
   240  	accountResponse := &iotexapi.GetAccountResponse{AccountMeta: &iotextypes.AccountMeta{
   241  		Address:      accAddr,
   242  		PendingNonce: 2,
   243  	}}
   244  	apiServiceClient.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Return(accountResponse, nil)
   245  	result, err := Meta(client, accAddr)
   246  	require.NoError(err)
   247  	require.Equal(accountResponse.AccountMeta, result)
   248  
   249  	expectedErr := errors.New("failed to dial grpc connection")
   250  	client.EXPECT().APIServiceClient().Return(nil, expectedErr)
   251  	result, err = Meta(client, accAddr)
   252  	require.Error(err)
   253  	require.Equal(expectedErr, err)
   254  	require.Nil(result)
   255  
   256  	expectedErr = errors.New("failed to invoke GetAccount api")
   257  	client.EXPECT().APIServiceClient().Return(apiServiceClient, nil)
   258  	apiServiceClient.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Return(nil, expectedErr)
   259  	result, err = Meta(client, accAddr)
   260  	require.Error(err)
   261  	require.Contains(err.Error(), expectedErr.Error())
   262  	require.Nil(result)
   263  }
   264  
   265  func TestAccountError(t *testing.T) {
   266  	require := require.New(t)
   267  	testFilePath := t.TempDir()
   268  	alias := "aaa"
   269  	passwordOfKeyStore := "123456"
   270  	keyStorePath := testFilePath
   271  
   272  	ctrl := gomock.NewController(t)
   273  	defer ctrl.Finish()
   274  	client := mock_ioctlclient.NewMockClient(ctrl)
   275  	testWallet := t.TempDir()
   276  
   277  	client.EXPECT().DecryptPrivateKey(gomock.Any(), gomock.Any()).DoAndReturn(
   278  		func(passwordOfKeyStore, keyStorePath string) (*ecdsa.PrivateKey, error) {
   279  			_, err := os.ReadFile(keyStorePath)
   280  			require.Error(err)
   281  			return nil, fmt.Errorf("keystore file \"%s\" read error", keyStorePath)
   282  		})
   283  	cmd := &cobra.Command{}
   284  	cmd.SetOut(new(bytes.Buffer))
   285  	_, err := newAccountByKeyStore(client, cmd, alias, passwordOfKeyStore, keyStorePath)
   286  	require.Error(err)
   287  	require.Contains(err.Error(), fmt.Sprintf("keystore file \"%s\" read error", keyStorePath))
   288  
   289  	asswordOfPem := "abc1234"
   290  	pemFilePath := testFilePath
   291  	_, err = newAccountByPem(client, cmd, alias, asswordOfPem, pemFilePath)
   292  	require.Error(err)
   293  	require.Contains(err.Error(), "failed to read private key from pem file")
   294  
   295  	addr2, err := address.FromString("io1aqazxjx4d6useyhdsq02ah5effg6293wumtldh")
   296  	require.NoError(err)
   297  	client.EXPECT().Config().Return(config.Config{Wallet: testWallet}).Times(1)
   298  	path, err := findSm2PemFile(client, addr2)
   299  	require.Error(err)
   300  	require.Contains(err.Error(), "crypto file not found")
   301  	require.Equal("", path)
   302  
   303  	client.EXPECT().Config().Return(config.Config{Wallet: ""}).Times(1)
   304  	accounts, err := listSm2Account(client)
   305  	require.Error(err)
   306  	require.Contains(err.Error(), "failed to read files in wallet")
   307  	require.Equal(0, len(accounts))
   308  }
   309  
   310  func TestStoreKey(t *testing.T) {
   311  	require := require.New(t)
   312  	testWallet, ks, passwd, _, err := newTestAccountWithKeyStore(t, veryLightScryptN, veryLightScryptP)
   313  	require.NoError(err)
   314  
   315  	ctrl := gomock.NewController(t)
   316  	defer ctrl.Finish()
   317  	client := mock_ioctlclient.NewMockClient(ctrl)
   318  
   319  	t.Run("CryptoSm2 is false", func(t *testing.T) {
   320  		client.EXPECT().IsCryptoSm2().Return(false).Times(4)
   321  		client.EXPECT().NewKeyStore().Return(ks).Times(6)
   322  
   323  		account, err := ks.NewAccount(passwd)
   324  		require.NoError(err)
   325  		addr, err := address.FromBytes(account.Address.Bytes())
   326  		require.NoError(err)
   327  		require.True(IsSignerExist(client, addr.String()))
   328  
   329  		// invalid private key
   330  		addrString, err := storeKey(client, account.Address.String(), passwd)
   331  		require.Error(err)
   332  		require.Contains(err.Error(), "failed to generate private key from hex string")
   333  		require.Equal("", addrString)
   334  
   335  		// valid private key
   336  		client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil)
   337  		prvKey, err := keyStoreAccountToPrivateKey(client, addr.String(), passwd)
   338  		require.NoError(err)
   339  		// import the existed account addr
   340  		addrString, err = storeKey(client, prvKey.HexString(), passwd)
   341  		require.Error(err)
   342  		require.Contains(err.Error(), "failed to import private key into keystore")
   343  		require.Equal("", addrString)
   344  
   345  		// import the unexisted account addr
   346  		prvKey, err = crypto.GenerateKey()
   347  		require.NoError(err)
   348  		addr = prvKey.PublicKey().Address()
   349  		require.NotNil(addr)
   350  		require.False(IsSignerExist(client, addr.String()))
   351  		addrString, err = storeKey(client, prvKey.HexString(), passwd)
   352  		require.NoError(err)
   353  		require.Equal(addr.String(), addrString)
   354  		require.True(IsSignerExist(client, addr.String()))
   355  	})
   356  
   357  	t.Run("CryptoSm2 is true", func(t *testing.T) {
   358  		client.EXPECT().IsCryptoSm2().Return(true).Times(2)
   359  		client.EXPECT().Config().Return(config.Config{Wallet: testWallet}).Times(4)
   360  
   361  		priKey2, err := crypto.GenerateKeySm2()
   362  		require.NoError(err)
   363  		addr2 := priKey2.PublicKey().Address()
   364  		require.NotNil(addr2)
   365  		require.False(IsSignerExist(client, addr2.String()))
   366  
   367  		pemFilePath := sm2KeyPath(client, addr2)
   368  		require.NoError(crypto.WritePrivateKeyToPem(pemFilePath, priKey2.(*crypto.P256sm2PrvKey), passwd))
   369  		require.True(IsSignerExist(client, addr2.String()))
   370  
   371  		addrString2, err := storeKey(client, priKey2.HexString(), passwd)
   372  		require.NoError(err)
   373  		require.Equal(addr2.String(), addrString2)
   374  	})
   375  }
   376  
   377  func TestNewAccount(t *testing.T) {
   378  	require := require.New(t)
   379  	_, ks, passwd, _, err := newTestAccountWithKeyStore(t, veryLightScryptN, veryLightScryptP)
   380  	require.NoError(err)
   381  
   382  	ctrl := gomock.NewController(t)
   383  	defer ctrl.Finish()
   384  	client := mock_ioctlclient.NewMockClient(ctrl)
   385  	client.EXPECT().ReadSecret().Return(passwd, nil).Times(2)
   386  	client.EXPECT().NewKeyStore().Return(ks)
   387  	cmd := &cobra.Command{}
   388  	cmd.SetOut(new(bytes.Buffer))
   389  	_, err = newAccount(client, cmd, "alias1234")
   390  	require.NoError(err)
   391  }
   392  
   393  func TestNewAccountSm2(t *testing.T) {
   394  	require := require.New(t)
   395  	testWallet, passwd, _, err := newTestAccount(t)
   396  	require.NoError(err)
   397  
   398  	ctrl := gomock.NewController(t)
   399  	defer ctrl.Finish()
   400  	client := mock_ioctlclient.NewMockClient(ctrl)
   401  	client.EXPECT().ReadSecret().Return(passwd, nil).Times(2)
   402  	client.EXPECT().Config().Return(config.Config{Wallet: testWallet}).Times(1)
   403  	cmd := &cobra.Command{}
   404  	cmd.SetOut(new(bytes.Buffer))
   405  	_, err = newAccountSm2(client, cmd, "alias1234")
   406  	require.NoError(err)
   407  }
   408  
   409  func TestNewAccountByKey(t *testing.T) {
   410  	require := require.New(t)
   411  	_, ks, passwd, _, err := newTestAccountWithKeyStore(t, veryLightScryptN, veryLightScryptP)
   412  	require.NoError(err)
   413  
   414  	ctrl := gomock.NewController(t)
   415  	defer ctrl.Finish()
   416  	client := mock_ioctlclient.NewMockClient(ctrl)
   417  	client.EXPECT().ReadSecret().Return(passwd, nil).Times(2)
   418  	client.EXPECT().NewKeyStore().Return(ks)
   419  
   420  	prvKey, err := crypto.GenerateKey()
   421  	require.NoError(err)
   422  	cmd := &cobra.Command{}
   423  	cmd.SetOut(new(bytes.Buffer))
   424  	result, err := newAccountByKey(client, cmd, "alias1234", prvKey.HexString())
   425  	require.NoError(err)
   426  	require.Equal(prvKey.PublicKey().Address().String(), result)
   427  }
   428  
   429  func newTestAccount(t *testing.T) (string, string, string, error) {
   430  	testWallet := t.TempDir()
   431  	nonce := strconv.FormatInt(rand.Int63(), 10)
   432  	passwd := "3dj,<>@@SF{}rj0ZF#" + nonce
   433  	return testWallet, passwd, nonce, nil
   434  }
   435  
   436  func newTestAccountWithKeyStore(t *testing.T, scryptN, scryptP int) (string, *keystore.KeyStore, string, string, error) {
   437  	testWallet, passwd, nonce, err := newTestAccount(t)
   438  	if err != nil {
   439  		return testWallet, nil, "", "", err
   440  	}
   441  	ks := keystore.NewKeyStore(testWallet, scryptN, scryptP)
   442  	return testWallet, ks, passwd, nonce, nil
   443  }