Repository: vegaprotocol/vegawallet (go)

Issue:
Introduce `key describe` sub-command
The `key describe` sub-command should describe a key with all its data.

We should use trim the `key list` sub-command to display the public key, and its name if any.


We should use this new command to improve the E2E tests.

Pull request:
Add key describe command to vegawallet (#329)
closes #329 

The output when describing a key looks as follows (but with more colours):
```
Name:              mykey
Public key:        fac2d2c7454169200273e448064242570a43a97b1869cdb3e67b628e02d54182
Algorithm Name:    vega/ed25519
Algorithm Version: 1

Key pair is: tainted
Tainting a key pair marks it as unsafe to use and ensures it will not be used to sign transactions.
This mechanism is useful when the key pair has been compromised.

Metadata:
name    | mykey
keytype | ed25519
some    | thing
```

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a66e9e..7cf30f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 
 ### Improvements
 - [300](https://github.com/vegaprotocol/vegawallet/pull/300) - Internal restructuring of CLI building blocks to improve testability
+- [359](https://github.com/vegaprotocol/vegawallet/pull/359) - Add `key describe` subcommand to allow querying of key information
 
 ### Fixes
 - [356](https://github.com/vegaprotocol/vegawallet/pull/356) - Ensure the interactive printer is listening to color management env vars (`NO_COLOR` and `CLICOLOR`)
diff --git a/cmd/flags/passphrase.go b/cmd/flags/passphrase.go
index 62669fe..e13814d 100644
--- a/cmd/flags/passphrase.go
+++ b/cmd/flags/passphrase.go
@@ -50,8 +50,9 @@ func ReadPassphraseFile(passphraseFilePath string) (string, error) {
 		return "", fmt.Errorf("couldn't read passphrase file: %w", err)
 	}
 
-	// user might have added \n at the end of the line, let's remove it.
-	cleanupPassphrase := strings.Trim(string(rawPassphrase), "\n")
+	// user might have added a newline at the end of the line, let's remove it,
+	// remembering Windows does things differently
+	cleanupPassphrase := strings.Trim(string(rawPassphrase), "\r\n")
 	if len(cleanupPassphrase) == 0 {
 		return "", ErrPassphraseMustBeSpecified
 	}
diff --git a/cmd/key.go b/cmd/key.go
index aa7d27f..8535613 100644
--- a/cmd/key.go
+++ b/cmd/key.go
@@ -21,6 +21,7 @@ func NewCmdKey(w io.Writer, rf *RootFlags) *cobra.Command {
 	cmd.AddCommand(NewCmdGenerateKey(w, rf))
 	cmd.AddCommand(NewCmdIsolateKey(w, rf))
 	cmd.AddCommand(NewCmdListKeys(w, rf))
+	cmd.AddCommand(NewCmdDescribeKey(w, rf))
 	cmd.AddCommand(NewCmdTaintKey(w, rf))
 	cmd.AddCommand(NewCmdUntaintKey(w, rf))
 	return cmd
@@ -35,7 +36,10 @@ func printMeta(p *printer.InteractivePrinter, meta []wallet.Meta) {
 		}
 	}
 
-	for _, m := range meta {
-		p.WarningText(fmt.Sprintf("%-*s", padding, m.Key)).Text(" | ").WarningText(m.Value).NextLine()
+	for i, m := range meta {
+		if i != 0 {
+			p.NextLine()
+		}
+		p.WarningText(fmt.Sprintf("%-*s", padding, m.Key)).Text(" | ").WarningText(m.Value)
 	}
 }
diff --git a/cmd/key_annotate.go b/cmd/key_annotate.go
index 93f7b4a..cc2f324 100644
--- a/cmd/key_annotate.go
+++ b/cmd/key_annotate.go
@@ -14,14 +14,14 @@ import (
 
 var (
 	annotateKeyLong = cli.LongDesc(`
-		Add metadata to a key pair. The metadata are not updated in place, they are
-		replaced.
+		Add metadata to a key pair. All existing metadata is removed and replaced
+		by the specified new metadata.
+
+		The metadata is a list of key-value pairs. A key-value is colon-separated, and
+		the key-values are comma-separated.
 
 		It is possible to give a name to a key pair, that is recognised by user
 		interfaces, by setting the metadata "name".
-
-		The metadata are a list of key-value. A key-value is colon-separated, and
-		the key-values are comma-separated.
 	`)
 
 	annotateKeyExample = cli.Examples(`
diff --git a/cmd/key_describe.go b/cmd/key_describe.go
new file mode 100644
index 0000000..a985d9b
--- /dev/null
+++ b/cmd/key_describe.go
@@ -0,0 +1,139 @@
+package cmd
+
+import (
+	"fmt"
+	"io"
+
+	"code.vegaprotocol.io/vegawallet/cmd/cli"
+	"code.vegaprotocol.io/vegawallet/cmd/flags"
+	"code.vegaprotocol.io/vegawallet/cmd/printer"
+	"code.vegaprotocol.io/vegawallet/wallet"
+	"code.vegaprotocol.io/vegawallet/wallets"
+	"github.com/spf13/cobra"
+)
+
+var (
+	describeKeyLong = cli.LongDesc(`
+		Describe all known information about the specified key pair
+	`)
+
+	describeKeyExample = cli.Examples(`
+		# Describe a key
+		vegawallet key describe --wallet WALLET --pubkey PUBKEY
+	`)
+)
+
+type DescribeKeyHandler func(*wallet.DescribeKeyRequest) (*wallet.DescribeKeyResponse, error)
+
+func NewCmdDescribeKey(w io.Writer, rf *RootFlags) *cobra.Command {
+	h := func(req *wallet.DescribeKeyRequest) (*wallet.DescribeKeyResponse, error) {
+		s, err := wallets.InitialiseStore(rf.Home)
+		if err != nil {
+			return nil, fmt.Errorf("couldn't initialise wallets store: %w", err)
+		}
+
+		return wallet.DescribeKey(s, req)
+	}
+
+	return BuildCmdDescribeKey(w, h, rf)
+}
+
+func BuildCmdDescribeKey(w io.Writer, handler DescribeKeyHandler, rf *RootFlags) *cobra.Command {
+	f := &DescribeKeyFlags{}
+
+	cmd := &cobra.Command{
+		Use:     "describe",
+		Short:   "Describe the specified key pair",
+		Long:    describeKeyLong,
+		Example: describeKeyExample,
+		RunE: func(_ *cobra.Command, _ []string) error {
+			req, err := f.Validate()
+			if err != nil {
+				return err
+			}
+
+			resp, err := handler(req)
+			if err != nil {
+				return err
+			}
+
+			switch rf.Output {
+			case flags.InteractiveOutput:
+				PrintDescribeKeyResponse(w, resp)
+			case flags.JSONOutput:
+				return printer.FprintJSON(w, resp)
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().StringVarP(&f.Wallet,
+		"wallet", "w",
+		"",
+		"Name of the wallet to use",
+	)
+	cmd.Flags().StringVarP(&f.PubKey,
+		"pubkey", "k",
+		"",
+		"Public key to describe (hex-encoded)",
+	)
+	cmd.Flags().StringVarP(&f.PassphraseFile,
+		"passphrase-file", "p",
+		"",
+		"Path to the file containing the wallet's passphrase",
+	)
+
+	return cmd
+}
+
+type DescribeKeyFlags struct {
+	Wallet         string
+	PassphraseFile string
+	PubKey         string
+}
+
+func (f *DescribeKeyFlags) Validate() (*wallet.DescribeKeyRequest, error) {
+	req := &wallet.DescribeKeyRequest{}
+
+	if len(f.Wallet) == 0 {
+		return nil, flags.FlagMustBeSpecifiedError("wallet")
+	}
+	req.Wallet = f.Wallet
+
+	if len(f.PubKey) == 0 {
+		return nil, flags.FlagMustBeSpecifiedError("pubkey")
+	}
+	req.PubKey = f.PubKey
+
+	passphrase, err := flags.GetPassphrase(f.PassphraseFile)
+	if err != nil {
+		return nil, err
+	}
+	req.Passphrase = passphrase
+
+	return req, nil
+}
+
+func PrintDescribeKeyResponse(w io.Writer, resp *wallet.DescribeKeyResponse) {
+	p := printer.NewInteractivePrinter(w).NextLine()
+
+	p.Text("Name:              ").WarningText(wallet.GetKeyName(resp.Meta)).NextLine()
+	p.Text("Public key:        ").WarningText(resp.PublicKey).NextLine()
+	p.Text("Algorithm Name:    ").WarningText(resp.Algorithm.Name).NextLine()
+	p.Text("Algorithm Version: ").WarningText(fmt.Sprint(resp.Algorithm.Version)).NextSection()
+
+	p.Text("Key pair is: ")
+	switch resp.IsTainted {
+	case true:
+		p.DangerText("tainted").NextLine()
+	case false:
+		p.SuccessText("not tainted").NextLine()
+	}
+	p.Text("Tainting a key pair marks it as unsafe to use and ensures it will not be used to sign transactions.").NextLine()
+	p.Text("This mechanism is useful when the key pair has been compromised.").NextSection()
+
+	p.Text("Metadata:").NextLine()
+	printMeta(p, resp.Meta)
+	p.NextSection()
+}
diff --git a/cmd/key_generate.go b/cmd/key_generate.go
index 87b6303..db62e82 100644
--- a/cmd/key_generate.go
+++ b/cmd/key_generate.go
@@ -160,7 +160,7 @@ func PrintGenerateKeyResponse(w io.Writer, resp *wallet.GenerateKeyResponse) {
 	p.WarningText(resp.Key.KeyPair.PublicKey).NextLine()
 	p.Text("Metadata:").NextLine()
 	printMeta(p, resp.Key.Meta)
-	p.NextLine()
+	p.NextSection()
 
 	p.RedArrow().DangerText("Important").NextLine()
 	if walletHasBeenCreated {
diff --git a/cmd/key_list.go b/cmd/key_list.go
index 557f20c..ea3e490 100644
--- a/cmd/key_list.go
+++ b/cmd/key_list.go
@@ -108,7 +108,7 @@ func PrintListKeysResponse(w io.Writer, resp *wallet.ListKeysResponse) {
 	p := printer.NewInteractivePrinter(w)
 
 	for _, key := range resp.Keys {
-		p.Text("Name: ").WarningText(key.Name).NextLine()
+		p.Text("Name:       ").WarningText(key.Name).NextLine()
 		p.Text("Public key: ").WarningText(key.PublicKey).NextSection()
 	}
 }
diff --git a/cmd/key_taint.go b/cmd/key_taint.go
index cd16a9c..b9f8d38 100644
--- a/cmd/key_taint.go
+++ b/cmd/key_taint.go
@@ -15,7 +15,7 @@ import (
 
 var (
 	taintKeyLong = cli.LongDesc(`
-		Tainting a key pair marks it as unsafe to use and ensure it will not be
+		Tainting a key pair marks it as unsafe to use and ensures it will not be
 		used to sign transactions.
 
 		This mechanism is useful when the key pair has been compromised.
diff --git a/version/adapter.go b/version/adapter.go
index 85aedba..0fe4e33 100644
--- a/version/adapter.go
+++ b/version/adapter.go
@@ -38,6 +38,15 @@ func BuildReleasesRequestFromGithub(ctx context.Context) ReleasesGetter {
 
 		responses := []releaseResponse{}
 		if err = json.Unmarshal(body, &responses); err != nil {
+			// try to parse as a general error message which would be useful information to know
+			// eg. if we were blocked due to githubs rate-limiting
+			m := struct {
+				Message string `json:"message"`
+			}{}
+			if mErr := json.Unmarshal(body, &m); mErr == nil {
+				return nil, fmt.Errorf("couldn't read releases API response message: %s: %w", m.Message, err)
+			}
+
 			return nil, fmt.Errorf("couldn't unmarshal releases API response body: %w", err)
 		}
 
diff --git a/wallet/handler.go b/wallet/handler.go
index dc63c83..8009b1e 100644
--- a/wallet/handler.go
+++ b/wallet/handler.go
@@ -198,6 +198,23 @@ type ListKeysResponse struct {
 	Keys []NamedPubKey `json:"keys"`
 }
 
+type DescribeKeyRequest struct {
+	Wallet     string
+	Passphrase string
+	PubKey     string
+}
+
+type DescribeKeyResponse struct {
+	PublicKey string `json:"publicKey"`
+
+	Algorithm struct {
+		Name    string `json:"name"`
+		Version uint32 `json:"version"`
+	} `json:"algorithm"`
+	Meta      []Meta `json:"meta"`
+	IsTainted bool   `json:"isTainted"`
+}
+
 type NamedPubKey struct {
 	Name      string `json:"name"`
 	PublicKey string `json:"publicKey"`
@@ -223,6 +240,25 @@ func ListKeys(store Store, req *ListKeysRequest) (*ListKeysResponse, error) {
 	}, nil
 }
 
+func DescribeKey(store Store, req *DescribeKeyRequest) (*DescribeKeyResponse, error) {
+	resp := &DescribeKeyResponse{}
+	w, err := getWallet(store, req.Wallet, req.Passphrase)
+	if err != nil {
+		return nil, err
+	}
+
+	kp, err := w.DescribeKeyPair(req.PubKey)
+	if err != nil {
+		return nil, err
+	}
+	resp.PublicKey = kp.PublicKey()
+	resp.Algorithm.Name = kp.AlgorithmName()
+	resp.Algorithm.Version = kp.AlgorithmVersion()
+	resp.Meta = kp.Meta()
+	resp.IsTainted = kp.IsTainted()
+	return resp, nil
+}
+
 type GetWalletInfoRequest struct {
 	Wallet     string
 	Passphrase string
diff --git a/wallet/hd_wallet.go b/wallet/hd_wallet.go
index bb72fa5..ce4030e 100644
--- a/wallet/hd_wallet.go
+++ b/wallet/hd_wallet.go
@@ -99,7 +99,16 @@ func (w *HDWallet) SetName(newName string) {
 	w.name = newName
 }
 
-// DescribePublicKey returns all the information associated to a public key,
+// DescribeKeyPair returns all the information associated with a public key.
+func (w *HDWallet) DescribeKeyPair(pubKey string) (KeyPair, error) {
+	keyPair, ok := w.keyRing.FindPair(pubKey)
+	if !ok {
+		return nil, ErrPubKeyDoesNotExist
+	}
+	return &keyPair, nil
+}
+
+// DescribePublicKey returns all the information associated with a public key,
 // except the private key.
 func (w *HDWallet) DescribePublicKey(pubKey string) (PublicKey, error) {
 	keyPair, ok := w.keyRing.FindPair(pubKey)
diff --git a/wallet/wallet.go b/wallet/wallet.go
index c41d2c3..5a9e03a 100644
--- a/wallet/wallet.go
+++ b/wallet/wallet.go
@@ -7,6 +7,7 @@ type Wallet interface {
 	ID() string
 	Type() string
 	DescribePublicKey(pubKey string) (PublicKey, error)
+	DescribeKeyPair(pubKey string) (KeyPair, error)
 	ListPublicKeys() []PublicKey
 	ListKeyPairs() []KeyPair
 	GenerateKeyPair(meta []Meta) (KeyPair, error)


Test patch:
diff --git a/tests/TODO.md b/tests/TODO.md
deleted file mode 100644
index 273afc5..0000000
--- a/tests/TODO.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Tests
-
-- [ ] verify key state on `key taint` using `key describe`
-- [ ] verify key state on `key annotate` using `key describe`
-- [ ] replace `key list` by `key describe` in key generation test
diff --git a/tests/commands_for_test.go b/tests/commands_for_test.go
index 60d9018..a0827d8 100644
--- a/tests/commands_for_test.go
+++ b/tests/commands_for_test.go
@@ -191,6 +191,79 @@ func KeyList(t *testing.T, args []string) (*ListKeysResponse, error) {
 	return resp, nil
 }
 
+type DescribeKeyResponse struct {
+	PublicKey string `json:"publicKey"`
+
+	Algorithm struct {
+		Name    string `json:"name"`
+		Version uint32 `json:"version"`
+	} `json:"algorithm"`
+	Meta []struct {
+		Key   string `json:"key"`
+		Value string `json:"value"`
+	}
+	IsTainted bool `json:"isTainted"`
+}
+
+func KeyDescribe(t *testing.T, args []string) (*DescribeKeyResponse, error) {
+	t.Helper()
+	argsWithCmd := []string{"key", "describe"}
+	argsWithCmd = append(argsWithCmd, args...)
+	output, err := ExecuteCmd(t, argsWithCmd)
+	if err != nil {
+		return nil, err
+	}
+	resp := &DescribeKeyResponse{}
+	if err := json.Unmarshal(output, resp); err != nil {
+		t.Fatalf("couldn't unmarshal command output: %v", err)
+	}
+	return resp, nil
+}
+
+type DescribeKeyAssertion struct {
+	t    *testing.T
+	resp *DescribeKeyResponse
+}
+
+func AssertDescribeKey(t *testing.T, resp *DescribeKeyResponse) *DescribeKeyAssertion {
+	t.Helper()
+
+	assert.NotNil(t, resp)
+	assert.NotEmpty(t, resp.PublicKey)
+	assert.NotEmpty(t, resp.Algorithm.Name)
+	assert.NotEmpty(t, resp.Algorithm.Version)
+
+	return &DescribeKeyAssertion{
+		t:    t,
+		resp: resp,
+	}
+}
+
+func (d *DescribeKeyAssertion) WithPubKey(pubkey string) *DescribeKeyAssertion {
+	assert.Equal(d.t, pubkey, d.resp.PublicKey)
+	return d
+}
+
+func (d *DescribeKeyAssertion) WithAlgorithm(name string, version uint32) *DescribeKeyAssertion {
+	assert.Equal(d.t, name, d.resp.Algorithm.Name)
+	assert.Equal(d.t, version, d.resp.Algorithm.Version)
+	return d
+}
+
+func (d *DescribeKeyAssertion) WithTainted(tainted bool) *DescribeKeyAssertion {
+	assert.Equal(d.t, tainted, d.resp.IsTainted)
+	return d
+}
+
+func (d *DescribeKeyAssertion) WithMeta(expected map[string]string) *DescribeKeyAssertion {
+	meta := map[string]string{}
+	for _, m := range d.resp.Meta {
+		meta[m.Key] = m.Value
+	}
+	assert.Equal(d.t, expected, meta)
+	return d
+}
+
 type IsolateKeyResponse struct {
 	Wallet   string `json:"wallet"`
 	FilePath string `json:"filePath"`
diff --git a/tests/sc_annotate_key_test.go b/tests/sc_annotate_key_test.go
index 399f60e..98a4d80 100644
--- a/tests/sc_annotate_key_test.go
+++ b/tests/sc_annotate_key_test.go
@@ -16,14 +16,17 @@ func TestAnnotateKey(t *testing.T) {
 
 	walletName := vgrand.RandomStr(5)
 
-	// when
-	generateKeyResp, err := KeyGenerate(t, []string{
+	cmd := []string{
 		"--home", home,
 		"--output", "json",
 		"--wallet", walletName,
 		"--passphrase-file", passphraseFilePath,
+	}
+
+	// when
+	generateKeyResp, err := KeyGenerate(t, append(cmd,
 		"--meta", "name:key-1,role:validation",
-	})
+	))
 
 	// then
 	require.NoError(t, err)
@@ -34,41 +37,59 @@ func TestAnnotateKey(t *testing.T) {
 		LocatedUnder(home)
 
 	// when
-	err = KeyAnnotate(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
+	err = KeyAnnotate(t, append(cmd,
 		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
-		"--passphrase-file", passphraseFilePath,
 		"--meta", "name:prefer-this-name",
-	})
+	))
 
 	// then
 	require.NoError(t, err)
 
 	// when
-	err = KeyAnnotate(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
+	descResp, err := KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertDescribeKey(t, descResp).
+		WithMeta(map[string]string{"name": "prefer-this-name"})
+
+	// when
+	err = KeyAnnotate(t, append(cmd,
 		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
-		"--passphrase-file", passphraseFilePath,
 		"--clear",
-	})
+	))
 
 	// then
 	require.NoError(t, err)
 
 	// when
-	err = KeyAnnotate(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
+	descResp, err = KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertDescribeKey(t, descResp).
+		WithMeta(map[string]string{})
+
+	// when
+	err = KeyAnnotate(t, append(cmd,
 		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
-		"--passphrase-file", passphraseFilePath,
 		"--meta", "name:key-1,role:validation",
-	})
+	))
+
+	// then
+	require.NoError(t, err)
+
+	// when
+	descResp, err = KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
+	))
 
 	// then
 	require.NoError(t, err)
+	AssertDescribeKey(t, descResp).
+		WithMeta(map[string]string{"name": "key-1", "role": "validation"})
 }
diff --git a/tests/sc_describe_key_test.go b/tests/sc_describe_key_test.go
new file mode 100644
index 0000000..84e2a2d
--- /dev/null
+++ b/tests/sc_describe_key_test.go
@@ -0,0 +1,58 @@
+package tests_test
+
+import (
+	"testing"
+
+	vgrand "code.vegaprotocol.io/shared/libs/rand"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDescribeKey(t *testing.T) {
+	// given
+	home, cleanUpFn := NewTempDir(t)
+	defer cleanUpFn(t)
+
+	_, passphraseFilePath := NewPassphraseFile(t, home)
+
+	walletName := vgrand.RandomStr(5)
+
+	cmd := []string{
+		"--home", home,
+		"--output", "json",
+		"--wallet", walletName,
+		"--passphrase-file", passphraseFilePath,
+	}
+
+	// when
+	generateKeyResp, err := KeyGenerate(t, append(cmd,
+		"--meta", "name:key-1,role:validation",
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertGenerateKey(t, generateKeyResp).
+		WithWalletCreation().
+		WithName(walletName).
+		WithMeta(map[string]string{"name": "key-1", "role": "validation"}).
+		LocatedUnder(home)
+
+	// when
+	descResp, err := KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey,
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertDescribeKey(t, descResp).
+		WithPubKey(generateKeyResp.Key.KeyPair.PublicKey).
+		WithMeta(map[string]string{"name": "key-1", "role": "validation"}).
+		WithAlgorithm("vega/ed25519", 1).
+		WithTainted(false)
+
+	// when non-existent public key
+	descResp, err = KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp.Key.KeyPair.PublicKey[1:],
+	))
+	require.Error(t, err)
+	require.Nil(t, descResp)
+}
diff --git a/tests/sc_generate_keys_test.go b/tests/sc_generate_keys_test.go
index f95c1f2..144a510 100644
--- a/tests/sc_generate_keys_test.go
+++ b/tests/sc_generate_keys_test.go
@@ -4,7 +4,6 @@ import (
 	"testing"
 
 	vgrand "code.vegaprotocol.io/shared/libs/rand"
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -17,14 +16,17 @@ func TestGenerateAndListKeys(t *testing.T) {
 
 	walletName := vgrand.RandomStr(5)
 
-	// when
-	generateKeyResp1, err := KeyGenerate(t, []string{
+	cmd := []string{
 		"--home", home,
 		"--output", "json",
 		"--wallet", walletName,
 		"--passphrase-file", passphraseFilePath,
+	}
+
+	// when
+	generateKeyResp1, err := KeyGenerate(t, append(cmd,
 		"--meta", "name:key-1,role:validation",
-	})
+	))
 
 	// then
 	require.NoError(t, err)
@@ -35,26 +37,19 @@ func TestGenerateAndListKeys(t *testing.T) {
 		LocatedUnder(home)
 
 	// when
-	listKeysResp1, err := KeyList(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
-		"--passphrase-file", passphraseFilePath,
-	})
+	descResp, err := KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp1.Key.KeyPair.PublicKey,
+	))
 
 	// then
 	require.NoError(t, err)
-	require.NotNil(t, listKeysResp1)
-	require.Len(t, listKeysResp1.Keys, 1)
-	assert.Equal(t, listKeysResp1.Keys[0].PublicKey, generateKeyResp1.Key.KeyPair.PublicKey)
+	AssertDescribeKey(t, descResp).
+		WithMeta(map[string]string{"name": "key-1", "role": "validation"}).
+		WithAlgorithm("vega/ed25519", 1).
+		WithTainted(false)
 
 	// when
-	generateKeyResp2, err := KeyGenerate(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
-		"--passphrase-file", passphraseFilePath,
-	})
+	generateKeyResp2, err := KeyGenerate(t, cmd)
 
 	// then
 	require.NoError(t, err)
@@ -65,17 +60,14 @@ func TestGenerateAndListKeys(t *testing.T) {
 		LocatedUnder(home)
 
 	// when
-	listKeysResp2, err := KeyList(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
-		"--passphrase-file", passphraseFilePath,
-	})
+	descResp, err = KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp2.Key.KeyPair.PublicKey,
+	))
 
 	// then
 	require.NoError(t, err)
-	require.NotNil(t, listKeysResp2)
-	require.Len(t, listKeysResp2.Keys, 2)
-	assert.Equal(t, listKeysResp2.Keys[0].PublicKey, generateKeyResp1.Key.KeyPair.PublicKey)
-	assert.Equal(t, listKeysResp2.Keys[1].PublicKey, generateKeyResp2.Key.KeyPair.PublicKey)
+	AssertDescribeKey(t, descResp).
+		WithMeta(map[string]string{"name": DefaultMetaName(t, walletName, 2)}).
+		WithAlgorithm("vega/ed25519", 1).
+		WithTainted(false)
 }
diff --git a/tests/sc_list_wallets_test.go b/tests/sc_list_keys_test.go
similarity index 100%
rename from tests/sc_list_wallets_test.go
rename to tests/sc_list_keys_test.go
diff --git a/tests/sc_taint_key_test.go b/tests/sc_taint_key_test.go
index aeeaff0..ac3fa47 100644
--- a/tests/sc_taint_key_test.go
+++ b/tests/sc_taint_key_test.go
@@ -16,14 +16,17 @@ func TestTaintKeys(t *testing.T) {
 
 	walletName := vgrand.RandomStr(5)
 
-	// when
-	generateKeyResp1, err := KeyGenerate(t, []string{
+	cmd := []string{
 		"--home", home,
 		"--output", "json",
 		"--wallet", walletName,
 		"--passphrase-file", passphraseFilePath,
+	}
+
+	// when
+	generateKeyResp1, err := KeyGenerate(t, append(cmd,
 		"--meta", "name:key-1,role:validation",
-	})
+	))
 
 	// then
 	require.NoError(t, err)
@@ -34,26 +37,36 @@ func TestTaintKeys(t *testing.T) {
 		LocatedUnder(home)
 
 	// when
-	err = KeyTaint(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
+	err = KeyTaint(t, append(cmd,
 		"--pubkey", generateKeyResp1.Key.KeyPair.PublicKey,
-		"--passphrase-file", passphraseFilePath,
-	})
+	))
 
 	// then
 	require.NoError(t, err)
 
 	// when
-	err = KeyUntaint(t, []string{
-		"--home", home,
-		"--output", "json",
-		"--wallet", walletName,
+	descResp, err := KeyDescribe(t, append(cmd,
 		"--pubkey", generateKeyResp1.Key.KeyPair.PublicKey,
-		"--passphrase-file", passphraseFilePath,
-	})
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertDescribeKey(t, descResp).WithTainted(true)
+
+	// when
+	err = KeyUntaint(t, append(cmd,
+		"--pubkey", generateKeyResp1.Key.KeyPair.PublicKey,
+	))
+
+	// then
+	require.NoError(t, err)
+
+	// when
+	descResp, err = KeyDescribe(t, append(cmd,
+		"--pubkey", generateKeyResp1.Key.KeyPair.PublicKey,
+	))
 
 	// then
 	require.NoError(t, err)
+	AssertDescribeKey(t, descResp).WithTainted(false)
 }