diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index c74d358dd..b5f467683 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -292,6 +292,23 @@ func NewCommands() []cli.Command { Action: signStoredTransaction, Flags: signFlags, }, + { + Name: "strip-keys", + Usage: "remove private keys for all accounts", + UsageText: "neo-go wallet strip-keys -w wallet [--wallet-config path] [--force]", + Description: `Removes private keys for all accounts from the given wallet. Notice, + this is a very dangerous action (you can lose keys if you don't have a wallet + backup) that should not be performed unless you know what you're doing. It's + mostly useful for creation of special wallets that can be used to create + transactions, but can't be used to sign them (offline signing). +`, + Action: stripKeys, + Flags: []cli.Flag{ + walletPathFlag, + walletConfigFlag, + forceFlag, + }, + }, { Name: "nep17", Usage: "work with NEP-17 contracts", @@ -776,6 +793,29 @@ func dumpKeys(ctx *cli.Context) error { return nil } +func stripKeys(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + wall, _, err := readWallet(ctx) + if err != nil { + return cli.NewExitError(err, 1) + } + if !ctx.Bool("force") { + fmt.Fprintln(ctx.App.Writer, "All private keys for all accounts will be removed from the wallet. This action is irreversible.") + if ok := askForConsent(ctx.App.Writer); !ok { + return nil + } + } + for _, a := range wall.Accounts { + a.EncryptedWIF = "" + } + if err := wall.Save(); err != nil { + return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1) + } + return nil +} + func createWallet(ctx *cli.Context) error { if err := cmdargs.EnsureNone(ctx); err != nil { return err diff --git a/cli/wallet_test.go b/cli/wallet_test.go index 09519bbb9..ea37fd5f6 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -630,6 +630,36 @@ func TestWalletImportDeployed(t *testing.T) { }) } +func TestStripKeys(t *testing.T) { + e := newExecutor(t, true) + tmpDir := t.TempDir() + walletPath := filepath.Join(tmpDir, "wallet.json") + e.In.WriteString("acc1\r") + e.In.WriteString("pass\r") + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + w1, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + + e.RunWithError(t, "neo-go", "wallet", "strip-keys", "--wallet", walletPath, "something") + e.RunWithError(t, "neo-go", "wallet", "strip-keys", "--wallet", walletPath+".bad") + + e.In.WriteString("no") + e.Run(t, "neo-go", "wallet", "strip-keys", "--wallet", walletPath) + w2, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, w1, w2) + + e.In.WriteString("y\r") + e.Run(t, "neo-go", "wallet", "strip-keys", "--wallet", walletPath) + e.Run(t, "neo-go", "wallet", "strip-keys", "--wallet", walletPath, "--force") // Does nothing effectively, but tests the force flag. + w3, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + for _, a := range w3.Accounts { + require.Equal(t, "", a.EncryptedWIF) + } +} + func TestWalletDump(t *testing.T) { e := newExecutor(t, false) diff --git a/docs/cli.md b/docs/cli.md index 572b12c48..7825d87a2 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -272,6 +272,11 @@ transactions for this multisignature account with the imported key. contracts. They also can have WIF keys associated with them (in case your contract's `verify` method needs some signature). +#### Strip keys from accounts +`wallet strip-keys` allows you to remove private keys from the wallet, but let +it be used for other purposes (like creating transactions for subsequent +offline signing). Use with care, don't lose your keys with it. + ### Neo voting `wallet candidate` provides commands to register or unregister a committee (and therefore validator) candidate key: