github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/hdwallet/hdwalletderive.go (about)

     1  // Copyright (c) 2022 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 hdwallet
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	ecrypt "github.com/ethereum/go-ethereum/crypto"
    15  	hdwallet "github.com/miguelmota/go-ethereum-hdwallet"
    16  	"github.com/spf13/cobra"
    17  
    18  	"github.com/iotexproject/go-pkgs/crypto"
    19  	"github.com/iotexproject/iotex-core/ioctl/config"
    20  	"github.com/iotexproject/iotex-core/ioctl/output"
    21  	"github.com/iotexproject/iotex-core/ioctl/util"
    22  	"github.com/iotexproject/iotex-core/pkg/util/fileutil"
    23  )
    24  
    25  // Multi-language support
    26  var (
    27  	_hdwalletDeriveCmdShorts = map[config.Language]string{
    28  		config.English: "derive key from HDWallet",
    29  		config.Chinese: "查询HDWallet钱包的派生key地址",
    30  	}
    31  )
    32  
    33  // _hdwalletDeriveCmd represents the hdwallet derive command
    34  var _hdwalletDeriveCmd = &cobra.Command{
    35  	Use:   "derive id1/id2/id3",
    36  	Short: config.TranslateInLang(_hdwalletDeriveCmdShorts, config.UILanguage),
    37  	Args:  cobra.ExactArgs(1),
    38  	RunE: func(cmd *cobra.Command, args []string) error {
    39  		cmd.SilenceUsage = true
    40  		err := hdwalletDerive(args[0])
    41  		return output.PrintError(err)
    42  	},
    43  }
    44  
    45  func hdwalletDerive(path string) error {
    46  	signer := "hdw::" + path
    47  	account, change, index, err := util.ParseHdwPath(signer)
    48  	if err != nil {
    49  		return output.NewError(output.InputError, "invalid hdwallet key format", err)
    50  	}
    51  
    52  	output.PrintQuery("Enter password\n")
    53  	password, err := util.ReadSecretFromStdin()
    54  	if err != nil {
    55  		return output.NewError(output.InputError, "failed to get password", err)
    56  	}
    57  
    58  	addr, _, err := DeriveKey(account, change, index, password)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	output.PrintResult(fmt.Sprintf("address: %s\n", addr))
    63  	return nil
    64  }
    65  
    66  // DeriveKey derives the key according to path
    67  func DeriveKey(account, change, index uint32, password string) (string, crypto.PrivateKey, error) {
    68  	// derive key as "m/44'/304'/account'/change/index"
    69  	hdWalletConfigFile := config.ReadConfig.Wallet + "/hdwallet"
    70  	if !fileutil.FileExists(hdWalletConfigFile) {
    71  		return "", nil, output.NewError(output.InputError, "Run 'ioctl hdwallet create' to create your HDWallet first.", nil)
    72  	}
    73  
    74  	enctxt, err := os.ReadFile(filepath.Clean(hdWalletConfigFile))
    75  	if err != nil {
    76  		return "", nil, output.NewError(output.InputError, "failed to read config", err)
    77  	}
    78  
    79  	enckey := util.HashSHA256([]byte(password))
    80  	dectxt, err := util.Decrypt(enctxt, enckey)
    81  	if err != nil {
    82  		return "", nil, output.NewError(output.InputError, "failed to decrypt", err)
    83  	}
    84  
    85  	dectxtLen := len(dectxt)
    86  	if dectxtLen <= 32 {
    87  		return "", nil, output.NewError(output.ValidationError, "incorrect data", nil)
    88  	}
    89  
    90  	mnemonic, hash := dectxt[:dectxtLen-32], dectxt[dectxtLen-32:]
    91  	if !bytes.Equal(hash, util.HashSHA256(mnemonic)) {
    92  		return "", nil, output.NewError(output.ValidationError, "password error", nil)
    93  	}
    94  
    95  	wallet, err := hdwallet.NewFromMnemonic(string(mnemonic))
    96  	if err != nil {
    97  		return "", nil, err
    98  	}
    99  
   100  	derivationPath := fmt.Sprintf("%s/%d'/%d/%d", DefaultRootDerivationPath, account, change, index)
   101  	path := hdwallet.MustParseDerivationPath(derivationPath)
   102  	walletAccount, err := wallet.Derive(path, false)
   103  	if err != nil {
   104  		return "", nil, output.NewError(output.InputError, "failed to get account by derive path", err)
   105  	}
   106  
   107  	private, err := wallet.PrivateKey(walletAccount)
   108  	if err != nil {
   109  		return "", nil, output.NewError(output.InputError, "failed to get private key", err)
   110  	}
   111  	prvKey, err := crypto.BytesToPrivateKey(ecrypt.FromECDSA(private))
   112  	if err != nil {
   113  		return "", nil, output.NewError(output.InputError, "failed to Bytes private key", err)
   114  	}
   115  
   116  	addr := prvKey.PublicKey().Address()
   117  	if addr == nil {
   118  		return "", nil, output.NewError(output.ConvertError, "failed to convert public key into address", nil)
   119  	}
   120  	return addr.String(), prvKey, nil
   121  }