diff --git a/cmd/wallet/import.go b/cmd/wallet/import.go index 19ec77d4..e37b28d6 100644 --- a/cmd/wallet/import.go +++ b/cmd/wallet/import.go @@ -1,8 +1,12 @@ package wallet import ( + "fmt" + "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 +14,117 @@ 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) + } else { + afCfg["algorithm"] = algorithm + afCfg["number"] = number + switch algorithm { + case wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Raw, wallet.AlgorithmSr25519Raw: + kind = wallet.ImportKindPrivateKey + default: + kind = wallet.ImportKindMnemonic + } + } + + accCfg = &config.Account{ + Kind: af.Kind(), + Config: afCfg, + } + + var src *wallet.ImportSource + if !common.GetAnswerYes() { + // 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() + + src = &wallet.ImportSource{ + Kind: kind, + Data: answers.Data, + } + } else { + src = &wallet.ImportSource{ + Kind: kind, + 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", wallet.AlgorithmEd25519Raw, fmt.Sprintf("Cryptographic algorithm to use for this account [%s]", strings.Join(walletFile.SupportedAlgorithmsForImport(nil), ", "))) + 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/transaction/sign.y.out b/examples/transaction/sign.y.out index 806b5087..13d5c181 100644 --- a/examples/transaction/sign.y.out +++ b/examples/transaction/sign.y.out @@ -3,7 +3,7 @@ Method: staking.Transfer Body: To: oasis1qrydpazemvuwtnp3efm7vmfvg3tde044qg6cxwzx Amount: 1.0 TEST -Nonce: 48 +Nonce: 49 Fee: Amount: 0.0 TEST Gas limit: 1265 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..7a4938b3 --- /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 @@ +