diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index f9334a84b..0cf11483c 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" ) @@ -120,6 +121,18 @@ func NewCommands() []cli.Command { decryptFlag, }, }, + { + Name: "dump-keys", + Usage: "dump public keys for account", + Action: dumpKeys, + Flags: []cli.Flag{ + walletPathFlag, + cli.StringFlag{ + Name: "address, a", + Usage: "address to print public keys for", + }, + }, + }, { Name: "export", Usage: "export keys for address", @@ -537,6 +550,56 @@ func dumpWallet(ctx *cli.Context) error { return nil } +func dumpKeys(ctx *cli.Context) error { + wall, err := openWallet(ctx.String("wallet")) + if err != nil { + return cli.NewExitError(err, 1) + } + accounts := wall.Accounts + addr := ctx.String("address") + if addr != "" { + u, err := flags.ParseAddress(addr) + if err != nil { + return cli.NewExitError(fmt.Errorf("invalid address: %w", err), 1) + } + acc := wall.GetAccount(u) + if acc == nil { + return cli.NewExitError("account is missing", 1) + } + accounts = []*wallet.Account{acc} + } + + hasPrinted := false + for _, acc := range accounts { + pub, ok := vm.ParseSignatureContract(acc.Contract.Script) + if ok { + if hasPrinted { + fmt.Fprintln(ctx.App.Writer) + } + fmt.Fprintf(ctx.App.Writer, "%s (simple signature contract):\n", acc.Address) + fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(pub)) + hasPrinted = true + continue + } + n, bs, ok := vm.ParseMultiSigContract(acc.Contract.Script) + if ok { + if hasPrinted { + fmt.Fprintln(ctx.App.Writer) + } + fmt.Fprintf(ctx.App.Writer, "%s (%d out of %d multisig contract):\n", acc.Address, n, len(bs)) + for i := range bs { + fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(bs[i])) + } + hasPrinted = true + continue + } + if addr != "" { + return cli.NewExitError(fmt.Errorf("Unknown script type for address %s", addr), 1) + } + } + return nil +} + func createWallet(ctx *cli.Context) error { path := ctx.String("wallet") if len(path) == 0 { diff --git a/cli/wallet_test.go b/cli/wallet_test.go index 259d8ed0f..4ee7e09f2 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -337,6 +337,49 @@ func TestWalletDump(t *testing.T) { }) } +func TestDumpKeys(t *testing.T) { + e := newExecutor(t, false) + cmd := []string{"neo-go", "wallet", "dump-keys", "--wallet", validatorWallet} + pubRegex := "^0[23][a-hA-H0-9]{64}$" + t.Run("all", func(t *testing.T) { + e.Run(t, cmd...) + e.checkNextLine(t, "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc") + e.checkNextLine(t, pubRegex) + e.checkNextLine(t, "^\\s*$") + e.checkNextLine(t, "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY") + for i := 0; i < 4; i++ { + e.checkNextLine(t, pubRegex) + } + e.checkNextLine(t, "^\\s*$") + e.checkNextLine(t, "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK") + e.checkNextLine(t, pubRegex) + e.checkEOF(t) + }) + t.Run("simple signature", func(t *testing.T) { + cmd := append(cmd, "--address", "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc") + e.Run(t, cmd...) + e.checkNextLine(t, "simple signature contract") + e.checkNextLine(t, pubRegex) + e.checkEOF(t) + }) + t.Run("3/4 multisig", func(t *testing.T) { + cmd := append(cmd, "-a", "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY") + e.Run(t, cmd...) + e.checkNextLine(t, "3 out of 4 multisig contract") + for i := 0; i < 4; i++ { + e.checkNextLine(t, pubRegex) + } + e.checkEOF(t) + }) + t.Run("1/1 multisig", func(t *testing.T) { + cmd := append(cmd, "--address", "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK") + e.Run(t, cmd...) + e.checkNextLine(t, "1 out of 1 multisig contract") + e.checkNextLine(t, pubRegex) + e.checkEOF(t) + }) +} + // Testcase is the wallet of privnet validator. func TestWalletConvert(t *testing.T) { tmpDir := path.Join(os.TempDir(), "neogo.test.convert")