diff --git a/cmd/wallet/import.go b/cmd/wallet/import.go index 19ec77d4..e11bd0de 100644 --- a/cmd/wallet/import.go +++ b/cmd/wallet/import.go @@ -1,8 +1,11 @@ package wallet import ( + "strings" + "github.com/AlecAivazis/survey/v2" "github.com/spf13/cobra" + flag "github.com/spf13/pflag" "github.com/oasisprotocol/cli/cmd/common" "github.com/oasisprotocol/cli/config" @@ -10,71 +13,120 @@ import ( walletFile "github.com/oasisprotocol/cli/wallet/file" ) -var importCmd = &cobra.Command{ - Use: "import ", - Short: "Import an existing account", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - cfg := config.Global() - name := args[0] - - checkAccountExists(cfg, name) - - // NOTE: We only support importing into the file-based wallet for now. - af, err := wallet.Load(walletFile.Kind) - cobra.CheckErr(err) - - // Ask for import kind. - var supportedKinds []string - for _, kind := range af.SupportedImportKinds() { - supportedKinds = append(supportedKinds, string(kind)) - } - - var kindRaw string - err = survey.AskOne(&survey.Select{ - Message: "Kind:", - Options: supportedKinds, - }, &kindRaw) - cobra.CheckErr(err) - - var kind wallet.ImportKind - err = kind.UnmarshalText([]byte(kindRaw)) - cobra.CheckErr(err) - - // Ask for wallet configuration. - afCfg, err := af.GetConfigFromSurvey(&kind) - cobra.CheckErr(err) - - // Ask for import data. - var answers struct { - Data string - } - questions := []*survey.Question{ - { - Name: "data", - Prompt: af.DataPrompt(kind, afCfg), - Validate: af.DataValidator(kind, afCfg), - }, - } - err = survey.Ask(questions, &answers) - cobra.CheckErr(err) - - // Ask for passphrase. - passphrase := common.AskNewPassphrase() - - accCfg := &config.Account{ - Kind: af.Kind(), - Config: afCfg, - } - src := &wallet.ImportSource{ - Kind: kind, - Data: answers.Data, - } - - err = cfg.Wallet.Import(name, passphrase, accCfg, src) - cobra.CheckErr(err) - - err = cfg.Save() - cobra.CheckErr(err) - }, +var ( + algorithm string + number uint32 + secret string + + passphrase string + accCfg *config.Account + kind wallet.ImportKind + + importCmd = &cobra.Command{ + Use: "import [flags]", + Short: "Import an existing account", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := config.Global() + name := args[0] + + checkAccountExists(cfg, name) + + // NOTE: We only support importing into the file-based wallet for now. + af, err := wallet.Load(walletFile.Kind) + cobra.CheckErr(err) + + afCfg := make(map[string]interface{}) + + if !common.GetAnswerYes() { + // Ask for import kind. + var supportedKinds []string + for _, kind := range af.SupportedImportKinds() { + supportedKinds = append(supportedKinds, string(kind)) + } + + var kindRaw string + err = survey.AskOne(&survey.Select{ + Message: "Kind:", + Options: supportedKinds, + }, &kindRaw) + cobra.CheckErr(err) + + err = kind.UnmarshalText([]byte(kindRaw)) + cobra.CheckErr(err) + + // Ask for wallet configuration. + afCfg, err = af.GetConfigFromSurvey(&kind) + cobra.CheckErr(err) + + // Ask for import data. + var answers struct { + Data string + } + questions := []*survey.Question{ + { + Name: "data", + Prompt: af.DataPrompt(kind, afCfg), + Validate: af.DataValidator(kind, afCfg), + }, + } + err = survey.Ask(questions, &answers) + cobra.CheckErr(err) + + // Ask for passphrase. + passphrase = common.AskNewPassphrase() + } + + if algorithm != "" { + afCfg["algorithm"] = algorithm + } + + if number != 0 { + afCfg["number"] = 0 + } + + accCfg = &config.Account{ + Kind: af.Kind(), + Config: afCfg, + } + + whitespace := strings.Contains(secret, " ") + if whitespace { + kind = wallet.ImportKindMnemonic + } else { + kind = wallet.ImportKindPrivateKey + } + + src := &wallet.ImportSource{ + Kind: kind, + Data: secret, + } + + if secret != "" { + src.Data = secret + } + + err = cfg.Wallet.Import(name, passphrase, accCfg, src) + cobra.CheckErr(err) + + err = cfg.Save() + cobra.CheckErr(err) + }, + } +) + +func init() { + importCmd.Flags().AddFlagSet(common.AnswerYesFlag) + + algorithmFlag := flag.NewFlagSet("", flag.ContinueOnError) + algorithmFlag.StringVar(&algorithm, "algorithm", "", "Cryptographic algorithm to use for this account [ed25519-adr8, secp256k1-bip44, sr25519-adr8] (default \"ed25519-adr8\")") + importCmd.Flags().AddFlagSet(algorithmFlag) + + numberFlag := flag.NewFlagSet("", flag.ContinueOnError) + numberFlag.Uint32Var(&number, "number", 0, "Key number to use in the key derivation scheme") + importCmd.Flags().AddFlagSet(numberFlag) + + secretFlag := flag.NewFlagSet("", flag.ContinueOnError) + secretFlag.StringVar(&secret, "secret", "", "A secret key or mnemonic to use for this account") + importCmd.Flags().AddFlagSet(secretFlag) } diff --git a/docs/wallet.md b/docs/wallet.md index 4ae0c7e1..45251df6 100644 --- a/docs/wallet.md +++ b/docs/wallet.md @@ -134,6 +134,24 @@ Let's make another Secp256k1 account and entering a hex-encoded raw private key: ![code](../examples/wallet/import-secp256k1-raw.out.static) +To override the defaults, you can pass `--algorithm`, `--number` and `--secret` +parameters. This is especially useful, if you are running the command in a +non-interactive mode: + +![code](../examples/wallet/import-secp256k1-bip44-y.in.static) + +:::danger Be cautious when importing accounts in non-interactive mode + +Since the account's secret is provided as a command line parameter in the +non-interactive mode, make sure you **read the account's secret from a file or +an environment variable**. Otherwise, the secret may be stored and exposed in +your shell history. + +Also, protecting your account with a password is currently not supported in the +non-interactive mode. + +::: + ## List Accounts Stored in Your Wallet {#list} You can list all available accounts in your wallet with `wallet list`: diff --git a/examples/wallet/import-secp256k1-bip44-y.in.static b/examples/wallet/import-secp256k1-bip44-y.in.static new file mode 100644 index 00000000..4794b02e --- /dev/null +++ b/examples/wallet/import-secp256k1-bip44-y.in.static @@ -0,0 +1 @@ +oasis wallet import eugene --algorithm secp256k1-bip44 --number 0 --secret 'man ankle mystery favorite tone number ice west spare marriage control lucky life together neither' -y diff --git a/examples/wallet/import-secp256k1-bip44-y.out.static b/examples/wallet/import-secp256k1-bip44-y.out.static new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/wallet/import-secp256k1-bip44-y.out.static @@ -0,0 +1 @@ +