From 944223d05b237621806ea9ea091435576c5b4fab Mon Sep 17 00:00:00 2001 From: 0xjac Date: Thu, 13 Jul 2023 01:06:45 +0200 Subject: [PATCH 1/3] feat: add wallet address cmd The `wallet address` cmd lists all the addresses of a given wallet. Related to #38 --- cmd/internal/wallet.go | 36 ++++++++++++++++++++++++++++++++++++ cmd/wallet.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 cmd/internal/wallet.go diff --git a/cmd/internal/wallet.go b/cmd/internal/wallet.go new file mode 100644 index 0000000..fbba924 --- /dev/null +++ b/cmd/internal/wallet.go @@ -0,0 +1,36 @@ +package internal + +import ( + "fmt" + "os" + + "github.com/hashicorp/go-secure-stdlib/password" + + "github.com/spacemeshos/smcli/wallet" +) + +// LoadWallet from a file, asks for the password from stdin. +func LoadWallet(path string, debug bool) (*wallet.Wallet, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + + defer f.Close() + + fmt.Print("Enter wallet password: ") + pass, err := password.Read(os.Stdin) + fmt.Println() + if err != nil { + return nil, err + } + + wk := wallet.NewKey(wallet.WithPasswordOnly([]byte(pass))) + + w, err := wk.Open(f, debug) + if err != nil { + return nil, err + } + + return w, nil +} diff --git a/cmd/wallet.go b/cmd/wallet.go index a19d22b..031f1e6 100644 --- a/cmd/wallet.go +++ b/cmd/wallet.go @@ -12,8 +12,10 @@ import ( "github.com/btcsuite/btcutil/base58" "github.com/hashicorp/go-secure-stdlib/password" "github.com/jedib0t/go-pretty/v6/table" + "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spf13/cobra" + "github.com/spacemeshos/smcli/cmd/internal" "github.com/spacemeshos/smcli/common" "github.com/spacemeshos/smcli/wallet" ) @@ -281,14 +283,51 @@ only child keys).`, }, } +var addrCmd = &cobra.Command{ + Use: "address [wallet file] [--parent]", + DisableFlagsInUseLine: true, + Short: "Show wallet addresses", + Long: "Show the addresses associated with the given wallet", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + w, err := internal.LoadWallet(args[0], debug) + cobra.CheckErr(err) + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetTitle("Wallet Addresses") + t.AppendHeader(table.Row{"address", "name"}) + + if printParent { + if master := w.Secrets.MasterKeypair; master != nil { + t.AppendRow(table.Row{ + types.GenerateAddress(master.Public).String(), + master.DisplayName, + }) + } + } + + for _, account := range w.Secrets.Accounts { + t.AppendRow(table.Row{ + types.GenerateAddress(account.Public).String(), + account.DisplayName, + }) + } + + t.Render() + }, +} + func init() { rootCmd.AddCommand(walletCmd) walletCmd.AddCommand(createCmd) walletCmd.AddCommand(readCmd) + walletCmd.AddCommand(addrCmd) readCmd.Flags().BoolVarP(&printPrivate, "private", "p", false, "Print private keys") readCmd.Flags().BoolVarP(&printFull, "full", "f", false, "Print full keys (no abbreviation)") readCmd.Flags().BoolVar(&printBase58, "base58", false, "Print keys in base58 (rather than hex)") readCmd.Flags().BoolVar(&printParent, "parent", false, "Print parent key (not only child keys)") readCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "enable debug mode") createCmd.Flags().BoolVarP(&useLedger, "ledger", "l", false, "Create a wallet using a Ledger device") + addrCmd.Flags().BoolVar(&printParent, "parent", false, "Print parent address (not only child addresses)") } From f6d0707756edc42941b057f6104e2a40fa64f5a7 Mon Sep 17 00:00:00 2001 From: 0xjac Date: Thu, 13 Jul 2023 01:08:52 +0200 Subject: [PATCH 2/3] fix: display pubkeys in bech32 format - Public keys are displayed in the bech32 format by default. - The old hex format is still available with the `--hex` flag. Related to #38 --- cmd/wallet.go | 104 +++++++++++++++++++++++--------------------------- go.mod | 2 +- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/cmd/wallet.go b/cmd/wallet.go index 031f1e6..8f8cfcf 100644 --- a/cmd/wallet.go +++ b/cmd/wallet.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/btcsuite/btcutil/base58" + "github.com/cosmos/btcutil/bech32" "github.com/hashicorp/go-secure-stdlib/password" "github.com/jedib0t/go-pretty/v6/table" "github.com/spacemeshos/go-spacemesh/common/types" @@ -33,6 +34,9 @@ var ( // printBase58 indicates that keys should be printed in base58 format. printBase58 bool + // printHex indicates that keys should be printed in Hex format. + printHex bool + // printParent indicates that the parent key should be printed. printParent bool @@ -141,34 +145,22 @@ sure the device is connected, unlocked, and the Spacemesh app is open.`, // readCmd reads an existing wallet file. var readCmd = &cobra.Command{ - Use: "read [wallet file] [--full/-f] [--private/-p] [--base58]", - Short: "Reads an existing wallet file", + Use: "read [wallet file] [--full/-f] [--private/-p] [--parent] [--base58] [--hex]", + DisableFlagsInUseLine: true, + Short: "Reads an existing wallet file", Long: `This command can be used to verify whether an existing wallet file can be successfully read and decrypted, whether the password to open the file is correct, etc. It prints the accounts from the wallet file. By default it does not print private keys. Add --private to print private keys. Add --full to print full keys. Add --base58 to print -keys in base58 format rather than hexadecimal. Add --parent to print parent key (and not +keys in base58 format or --hex for hexdecimal rather than bech32. Add --parent to print parent key (and not only child keys).`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - walletFn := args[0] - - // make sure the file exists - f, err := os.Open(walletFn) - cobra.CheckErr(err) - defer f.Close() - - // get the password - fmt.Print("Enter wallet password: ") - password, err := password.Read(os.Stdin) - fmt.Println() - cobra.CheckErr(err) - - // attempt to read it - wk := wallet.NewKey(wallet.WithPasswordOnly([]byte(password))) - w, err := wk.Open(f, debug) + w, err := internal.LoadWallet(args[0], debug) cobra.CheckErr(err) + caption := make([]string, 0, 2) + maxWidth := 20 widthEnforcer := func(col string, maxLen int) string { if len(col) <= maxLen { return col @@ -179,55 +171,52 @@ only child keys).`, return fmt.Sprintf("%s..%s", col[:maxLen-7], col[len(col)-5:]) } - t := table.NewWriter() - t.SetOutputMirror(os.Stdout) - t.SetTitle("Wallet Contents") - caption := "" - if printPrivate { - caption = fmt.Sprintf("Mnemonic: %s", w.Mnemonic()) - } - if !printFull { - if printPrivate { - caption += "\n" - } - caption += "To print full keys, use the --full flag." - } - t.SetCaption(caption) - maxWidth := 20 + header := table.Row{"pubkey", "path", "name", "created"} + if printFull { // full key is 64 bytes which is 128 chars in hex, need to print at least this much maxWidth = 150 + } else { + caption = append(caption, "To print full keys, use the --full flag.") + } + + colCfgs := []table.ColumnConfig{ + {Number: 1, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, } + // TODO: add spacemesh address format (bech32) // https://github.com/spacemeshos/smcli/issues/38 if printPrivate { - t.AppendHeader(table.Row{ - "pubkey", - "privkey", - "path", - "name", - "created", - }) - t.SetColumnConfigs([]table.ColumnConfig{ - {Number: 1, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, - {Number: 2, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, - }) - } else { - t.AppendHeader(table.Row{ - "pubkey", - "path", - "name", - "created", - }) - t.SetColumnConfigs([]table.ColumnConfig{ - {Number: 1, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, + caption = append(caption, fmt.Sprintf("Mnemonic: %s", w.Mnemonic())) + header = append(header[:2], header[1:]...) + header[1] = "privkey" + colCfgs = append(colCfgs, table.ColumnConfig{ + Number: 2, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer, }) } + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetTitle("Wallet Contents") + t.SetCaption(strings.Join(caption, "\n")) + t.AppendHeader(header) + t.SetColumnConfigs(colCfgs) + // set the encoder - encoder := hex.EncodeToString - if printBase58 { + var encoder func([]byte) string + switch { + case printBase58: encoder = base58.Encode + case printHex: + encoder = hex.EncodeToString + default: + encoder = func(data []byte) string { + dataConverted, err := bech32.ConvertBits(data, 8, 5, true) + cobra.CheckErr(err) + encoded, err := bech32.Encode(types.NetworkHRP(), dataConverted) + cobra.CheckErr(err) + return encoded + } } privKeyEncoder := func(privKey []byte) string { @@ -325,7 +314,8 @@ func init() { walletCmd.AddCommand(addrCmd) readCmd.Flags().BoolVarP(&printPrivate, "private", "p", false, "Print private keys") readCmd.Flags().BoolVarP(&printFull, "full", "f", false, "Print full keys (no abbreviation)") - readCmd.Flags().BoolVar(&printBase58, "base58", false, "Print keys in base58 (rather than hex)") + readCmd.Flags().BoolVar(&printBase58, "base58", false, "Print keys in base58 (rather than bech32)") + readCmd.Flags().BoolVar(&printHex, "hex", false, "Print keys in hex (rather than bech32)") readCmd.Flags().BoolVar(&printParent, "parent", false, "Print parent key (not only child keys)") readCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "enable debug mode") createCmd.Flags().BoolVarP(&useLedger, "ledger", "l", false, "Create a wallet using a Ledger device") diff --git a/go.mod b/go.mod index 461f907..1f56a5f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/btcsuite/btcutil v1.0.2 + github.com/cosmos/btcutil v1.0.5 github.com/jedib0t/go-pretty/v6 v6.4.6 github.com/spacemeshos/economics v0.1.0 github.com/spacemeshos/go-spacemesh v1.0.2 @@ -15,7 +16,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/c0mm4nd/go-ripemd v0.0.0-20200326052756-bd1759ad7d10 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cosmos/btcutil v1.0.5 // indirect github.com/go-llsqlite/llsqlite v0.0.0-20230612031458-a9e271fe723a // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect From cadc58c448595d583067dfde9c5a8a67029e37d7 Mon Sep 17 00:00:00 2001 From: 0xjac Date: Thu, 13 Jul 2023 02:02:54 +0200 Subject: [PATCH 3/3] fix: include address when showing wallet The addresses associated with a wallet file are now displayed when the file is read. The flag `--no-address` hides the address, similar to the old behavior. Related to #38 --- cmd/wallet.go | 79 ++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/cmd/wallet.go b/cmd/wallet.go index 8f8cfcf..4025200 100644 --- a/cmd/wallet.go +++ b/cmd/wallet.go @@ -42,6 +42,10 @@ var ( // useLedger indicates that the Ledger device should be used. useLedger bool + + // noAddress indicates that the address should not be shown when printing a key. + // This matches the old behavior of the read cmd. + noAddress bool ) // walletCmd represents the wallet command. @@ -145,7 +149,7 @@ sure the device is connected, unlocked, and the Spacemesh app is open.`, // readCmd reads an existing wallet file. var readCmd = &cobra.Command{ - Use: "read [wallet file] [--full/-f] [--private/-p] [--parent] [--base58] [--hex]", + Use: "read [wallet file] [--full/-f] [--private/-p] [--parent] [--base58] [--hex] [--no-address]", DisableFlagsInUseLine: true, Short: "Reads an existing wallet file", Long: `This command can be used to verify whether an existing wallet file can be @@ -153,7 +157,7 @@ successfully read and decrypted, whether the password to open the file is correc It prints the accounts from the wallet file. By default it does not print private keys. Add --private to print private keys. Add --full to print full keys. Add --base58 to print keys in base58 format or --hex for hexdecimal rather than bech32. Add --parent to print parent key (and not -only child keys).`, +only child keys). Add --no-address to not print the address.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { w, err := internal.LoadWallet(args[0], debug) @@ -184,8 +188,11 @@ only child keys).`, {Number: 1, WidthMax: maxWidth, WidthMaxEnforcer: widthEnforcer}, } - // TODO: add spacemesh address format (bech32) - // https://github.com/spacemeshos/smcli/issues/38 + if !noAddress { + header = append(header[:3], header[2:]...) + header[2] = "address" + } + if printPrivate { caption = append(caption, fmt.Sprintf("Mnemonic: %s", w.Mnemonic())) header = append(header[:2], header[1:]...) @@ -219,55 +226,42 @@ only child keys).`, } } - privKeyEncoder := func(privKey []byte) string { - if len(privKey) == 0 { - return "(none)" + addRow := func(account *wallet.EDKeyPair) { + row := make([]any, 0, 6) // Row len is 4 w/o address, up to 6 w/ priv key. + row = append(row, encoder(account.Public)) + + if printPrivate { + privKey := "(none)" + if len(account.Private) > 0 { + privKey = encoder(account.Private) + } + + row = append(row, privKey) } - return encoder(privKey) + + row = append(row, account.Path.String()) + + if !noAddress { + row = append(row, types.GenerateAddress(account.Public).String()) + } + + row = append(row, account.DisplayName, account.Created) + + t.AppendRow(row) } // print the master account if printParent { - master := w.Secrets.MasterKeypair - if master != nil { - if printPrivate { - t.AppendRow(table.Row{ - encoder(master.Public), - privKeyEncoder(master.Private), - master.Path.String(), - master.DisplayName, - master.Created, - }) - } else { - t.AppendRow(table.Row{ - encoder(master.Public), - master.Path.String(), - master.DisplayName, - master.Created, - }) - } + if master := w.Secrets.MasterKeypair; master != nil { + addRow(master) } } // print child accounts for _, a := range w.Secrets.Accounts { - if printPrivate { - t.AppendRow(table.Row{ - encoder(a.Public), - privKeyEncoder(a.Private), - a.Path.String(), - a.DisplayName, - a.Created, - }) - } else { - t.AppendRow(table.Row{ - encoder(a.Public), - a.Path.String(), - a.DisplayName, - a.Created, - }) - } + addRow(a) } + t.Render() }, } @@ -317,6 +311,7 @@ func init() { readCmd.Flags().BoolVar(&printBase58, "base58", false, "Print keys in base58 (rather than bech32)") readCmd.Flags().BoolVar(&printHex, "hex", false, "Print keys in hex (rather than bech32)") readCmd.Flags().BoolVar(&printParent, "parent", false, "Print parent key (not only child keys)") + readCmd.Flags().BoolVar(&noAddress, "no-address", false, "Do not print the address associated with the key") readCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "enable debug mode") createCmd.Flags().BoolVarP(&useLedger, "ledger", "l", false, "Create a wallet using a Ledger device") addrCmd.Flags().BoolVar(&printParent, "parent", false, "Print parent address (not only child addresses)")