code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/wallet_import.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package cmd
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/cli"
    28  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/flags"
    29  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/printer"
    30  	vgfs "code.vegaprotocol.io/vega/libs/fs"
    31  	vgzap "code.vegaprotocol.io/vega/libs/zap"
    32  	"code.vegaprotocol.io/vega/wallet/api"
    33  	"code.vegaprotocol.io/vega/wallet/wallet"
    34  	"code.vegaprotocol.io/vega/wallet/wallets"
    35  
    36  	"github.com/spf13/cobra"
    37  )
    38  
    39  var (
    40  	importWalletLong = cli.LongDesc(`
    41  		Import a wallet using the recovery phrase and generate the first Ed25519 key pair.
    42  
    43  		You will be asked to create a passphrase. The passphrase is used to protect
    44  		the file in which the keys are stored. Hence, it can be different from the
    45  		original passphrase, used during the wallet creation. This doesn't affect the
    46  		key generation process in any way.
    47  	`)
    48  
    49  	importWalletExample = cli.Examples(`
    50  		# Import a wallet using the recovery phrase
    51  		{{.Software}} import --wallet WALLET --recovery-phrase-file PATH_TO_RECOVERY_PHRASE
    52  
    53  		# Import an older version of the wallet using the recovery phrase
    54  		{{.Software}} import --wallet WALLET --recovery-phrase-file PATH_TO_RECOVERY_PHRASE --version VERSION
    55  	`)
    56  )
    57  
    58  type ImportWalletHandler func(api.AdminImportWalletParams) (importWalletResult, error)
    59  
    60  type importedWallet struct {
    61  	Name                 string `json:"name"`
    62  	KeyDerivationVersion uint32 `json:"keyDerivationVersion"`
    63  	FilePath             string `json:"filePath"`
    64  }
    65  
    66  type importWalletResult struct {
    67  	Wallet importedWallet `json:"wallet"`
    68  	Key    firstPublicKey `json:"key"`
    69  }
    70  
    71  func NewCmdImportWallet(w io.Writer, rf *RootFlags) *cobra.Command {
    72  	h := func(params api.AdminImportWalletParams) (importWalletResult, error) {
    73  		walletStore, err := wallets.InitialiseStore(rf.Home, false)
    74  		if err != nil {
    75  			return importWalletResult{}, fmt.Errorf("couldn't initialise wallets store: %w", err)
    76  		}
    77  		defer walletStore.Close()
    78  
    79  		importWallet := api.NewAdminImportWallet(walletStore)
    80  
    81  		rawResult, errDetails := importWallet.Handle(context.Background(), params)
    82  		if errDetails != nil {
    83  			return importWalletResult{}, errors.New(errDetails.Data)
    84  		}
    85  
    86  		result := rawResult.(api.AdminImportWalletResult)
    87  		return importWalletResult{
    88  			Wallet: importedWallet{
    89  				Name:                 result.Wallet.Name,
    90  				KeyDerivationVersion: result.Wallet.KeyDerivationVersion,
    91  				FilePath:             walletStore.GetWalletPath(result.Wallet.Name),
    92  			},
    93  			Key: firstPublicKey{
    94  				PublicKey: result.Key.PublicKey,
    95  				Algorithm: result.Key.Algorithm,
    96  				Meta:      result.Key.Metadata,
    97  			},
    98  		}, nil
    99  	}
   100  
   101  	return BuildCmdImportWallet(w, h, rf)
   102  }
   103  
   104  func BuildCmdImportWallet(w io.Writer, handler ImportWalletHandler, rf *RootFlags) *cobra.Command {
   105  	f := &ImportWalletFlags{}
   106  
   107  	cmd := &cobra.Command{
   108  		Use:     "import",
   109  		Short:   "Import a wallet using the recovery phrase",
   110  		Long:    importWalletLong,
   111  		Example: importWalletExample,
   112  		RunE: func(_ *cobra.Command, _ []string) error {
   113  			req, err := f.Validate()
   114  			if err != nil {
   115  				return err
   116  			}
   117  
   118  			resp, err := handler(req)
   119  			if err != nil {
   120  				return err
   121  			}
   122  
   123  			switch rf.Output {
   124  			case flags.InteractiveOutput:
   125  				PrintImportWalletResponse(w, resp)
   126  			case flags.JSONOutput:
   127  				return printer.FprintJSON(w, resp)
   128  			}
   129  
   130  			return nil
   131  		},
   132  	}
   133  
   134  	cmd.Flags().StringVarP(&f.Wallet,
   135  		"wallet", "w",
   136  		"",
   137  		"Name of the wallet to use",
   138  	)
   139  	cmd.Flags().StringVarP(&f.PassphraseFile,
   140  		"passphrase-file", "p",
   141  		"",
   142  		"Path to the file containing the passphrase to access the wallet",
   143  	)
   144  	cmd.Flags().StringVar(&f.RecoveryPhraseFile,
   145  		"recovery-phrase-file",
   146  		"",
   147  		"Path to the file containing the recovery phrase of the wallet",
   148  	)
   149  	cmd.Flags().Uint32Var(&f.Version,
   150  		"version",
   151  		wallet.LatestVersion,
   152  		fmt.Sprintf("Version of the wallet to import: %v", wallet.SupportedKeyDerivationVersions),
   153  	)
   154  
   155  	_ = cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
   156  		vs := make([]string, 0, len(wallet.SupportedKeyDerivationVersions))
   157  		for i, v := range wallet.SupportedKeyDerivationVersions {
   158  			vs[i] = strconv.FormatUint(uint64(v), 10) //nolint:gomnd
   159  		}
   160  		return vgzap.SupportedLogLevels, cobra.ShellCompDirectiveDefault
   161  	})
   162  
   163  	return cmd
   164  }
   165  
   166  type ImportWalletFlags struct {
   167  	Wallet             string
   168  	PassphraseFile     string
   169  	RecoveryPhraseFile string
   170  	Version            uint32
   171  }
   172  
   173  func (f *ImportWalletFlags) Validate() (api.AdminImportWalletParams, error) {
   174  	params := api.AdminImportWalletParams{
   175  		KeyDerivationVersion: f.Version,
   176  	}
   177  
   178  	if len(f.Wallet) == 0 {
   179  		return api.AdminImportWalletParams{}, flags.MustBeSpecifiedError("wallet")
   180  	}
   181  	params.Wallet = f.Wallet
   182  
   183  	if len(f.RecoveryPhraseFile) == 0 {
   184  		return api.AdminImportWalletParams{}, flags.MustBeSpecifiedError("recovery-phrase-file")
   185  	}
   186  	recoveryPhrase, err := vgfs.ReadFile(f.RecoveryPhraseFile)
   187  	if err != nil {
   188  		return api.AdminImportWalletParams{}, fmt.Errorf("couldn't read recovery phrase file: %w", err)
   189  	}
   190  	params.RecoveryPhrase = strings.Trim(string(recoveryPhrase), "\n")
   191  
   192  	passphrase, err := flags.GetConfirmedPassphrase(f.PassphraseFile)
   193  	if err != nil {
   194  		return api.AdminImportWalletParams{}, err
   195  	}
   196  	params.Passphrase = passphrase
   197  
   198  	return params, nil
   199  }
   200  
   201  func PrintImportWalletResponse(w io.Writer, resp importWalletResult) {
   202  	p := printer.NewInteractivePrinter(w)
   203  
   204  	str := p.String()
   205  	defer p.Print(str)
   206  
   207  	str.CheckMark().Text("Wallet ").Bold(resp.Wallet.Name).Text(" has been imported at: ").SuccessText(resp.Wallet.FilePath).NextLine()
   208  	str.CheckMark().Text("First key pair has been generated for the wallet ").Bold(resp.Wallet.Name).Text(" at: ").SuccessText(resp.Wallet.FilePath).NextLine()
   209  	str.CheckMark().SuccessText("Importing the wallet succeeded").NextSection()
   210  
   211  	str.Text("First public key:").NextLine()
   212  	str.WarningText(resp.Key.PublicKey).NextLine()
   213  	str.NextSection()
   214  
   215  	str.BlueArrow().InfoText("Run the service").NextLine()
   216  	str.Text("Now, you can run the service. See the following command:").NextSection()
   217  	str.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextLine()
   218  }