Repository: vegaprotocol/vegawallet (go)

Issue:
Update key rotate cmd to align with latest protos

Pull request description:
Add current public key hash to key rotate command
Close #411 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5574078..2fb7b45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
 ### 🐛 Fixes
 - [395](https://github.com/vegaprotocol/vegawallet/pull/395) - Fix `command send` examples
 - [401](https://github.com/vegaprotocol/vegawallet/pull/400) - Update key rotation submission command
+- [410](https://github.com/vegaprotocol/vegawallet/pull/410) - Add current pub key hash to key rotation submission command
 
 ## 0.10.0
 *2021-11-15*
diff --git a/cmd/key_rotate.go b/cmd/key_rotate.go
index 9d49261..1f118c1 100644
--- a/cmd/key_rotate.go
+++ b/cmd/key_rotate.go
@@ -22,7 +22,7 @@ var (
 
 	rotateKeyExample = cli.Examples(`
 		# Build signed transaction for rotating to new key public key
-		vegawallet key rotate --wallet WALLET --tx-height TX_HEIGHT --target-height TARGET_HEIGHT --pubkey PUBLIC_KEY
+		vegawallet key rotate --wallet WALLET --tx-height TX_HEIGHT --target-height TARGET_HEIGHT --pubkey PUBLIC_KEY --current-pubkey CURRENT_PUBLIC_KEY
 	`)
 )
 
@@ -81,18 +81,23 @@ func BuildCmdRotateKey(w io.Writer, handler RotateKeyHandler, rf *RootFlags) *co
 		"",
 		"Path to the file containing the wallet's passphrase",
 	)
-	cmd.Flags().StringVarP(&f.PublicKey,
-		"pubkey", "k",
+	cmd.Flags().StringVar(&f.NewPublicKey,
+		"new-pubkey",
 		"",
 		"A public key to rotate to. Should be generated by wallet's 'generate' command.",
 	)
-	cmd.Flags().Uint64VarP(&f.TxBlockHeight,
-		"tx-height", "x",
+	cmd.Flags().StringVar(&f.CurrentPubKey,
+		"current-pubkey",
+		"",
+		"A public key to rotate from. Should be currently used public key.",
+	)
+	cmd.Flags().Uint64Var(&f.TxBlockHeight,
+		"tx-height",
 		0,
 		"It should be close to the current block height when the transaction is applied, with a threshold of ~ - 150 blocks.",
 	)
-	cmd.Flags().Uint64VarP(&f.TargetBlockHeight,
-		"target-height", "t",
+	cmd.Flags().Uint64Var(&f.TargetBlockHeight,
+		"target-height",
 		0,
 		"Height of block where the public key change will take effect",
 	)
@@ -105,7 +110,8 @@ func BuildCmdRotateKey(w io.Writer, handler RotateKeyHandler, rf *RootFlags) *co
 type RotateKeyFlags struct {
 	Wallet            string
 	PassphraseFile    string
-	PublicKey         string
+	NewPublicKey      string
+	CurrentPubKey     string
 	TxBlockHeight     uint64
 	TargetBlockHeight uint64
 }
@@ -113,10 +119,15 @@ type RotateKeyFlags struct {
 func (f *RotateKeyFlags) Validate() (*wallet.RotateKeyRequest, error) {
 	req := &wallet.RotateKeyRequest{}
 
-	if f.PublicKey == "" {
-		return nil, flags.FlagMustBeSpecifiedError("pubkey")
+	if f.NewPublicKey == "" {
+		return nil, flags.FlagMustBeSpecifiedError("new-pubkey")
+	}
+	req.NewPublicKey = f.NewPublicKey
+
+	if f.CurrentPubKey == "" {
+		return nil, flags.FlagMustBeSpecifiedError("current-pubkey")
 	}
-	req.PublicKey = f.PublicKey
+	req.CurrentPublicKey = f.CurrentPubKey
 
 	if f.TargetBlockHeight == 0 {
 		return nil, flags.FlagMustBeSpecifiedError("target-height")
diff --git a/go.mod b/go.mod
index f9d903c..1c44611 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module code.vegaprotocol.io/vegawallet
 go 1.17
 
 require (
-	code.vegaprotocol.io/protos v0.46.1-0.20211124140330-6f1ee8ac737e
+	code.vegaprotocol.io/protos v0.46.1-0.20211129190702-538bee8fb036
 	code.vegaprotocol.io/shared v0.0.0-20211015074835-9ed837d93090
 	github.com/blang/semver/v4 v4.0.0
 	github.com/cenkalti/backoff/v4 v4.0.2
diff --git a/go.sum b/go.sum
index a8538d1..780c206 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 code.vegaprotocol.io/protos v0.46.1-0.20211124140330-6f1ee8ac737e h1:nru0Y8/D/ipsBTUYvtGFsNjJErJreZZa4Kvas2HDYRo=
 code.vegaprotocol.io/protos v0.46.1-0.20211124140330-6f1ee8ac737e/go.mod h1:NORK22Evpwn0GoetL7H6/oVUzGcfjAJEJTzw+zTkSyE=
+code.vegaprotocol.io/protos v0.46.1-0.20211129190702-538bee8fb036 h1:sEJsMLbFt5W1iLU5ntkbPquJjNTHCVDWkWc7+yq5JUA=
+code.vegaprotocol.io/protos v0.46.1-0.20211129190702-538bee8fb036/go.mod h1:NORK22Evpwn0GoetL7H6/oVUzGcfjAJEJTzw+zTkSyE=
 code.vegaprotocol.io/shared v0.0.0-20211015074835-9ed837d93090 h1:SF7ZRvZ/JASJNEQigbuUwk1rQuugLy6gqbOFs8D8VXI=
 code.vegaprotocol.io/shared v0.0.0-20211015074835-9ed837d93090/go.mod h1:dmahiiphuDJvjXXDvQvo22tFPx7yk9wW8z1glU/JgHE=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
diff --git a/wallet/handler.go b/wallet/handler.go
index 873c34d..f2f8e61 100644
--- a/wallet/handler.go
+++ b/wallet/handler.go
@@ -265,7 +265,8 @@ func DescribeKey(store Store, req *DescribeKeyRequest) (*DescribeKeyResponse, er
 type RotateKeyRequest struct {
 	Wallet            string
 	Passphrase        string
-	PublicKey         string
+	NewPublicKey      string
+	CurrentPublicKey  string
 	TxBlockHeight     uint64
 	TargetBlockHeight uint64
 }
@@ -289,21 +290,32 @@ func RotateKey(store Store, req *RotateKeyRequest) (*RotateKeyResponse, error) {
 		return nil, err
 	}
 
-	pubKey, err := w.DescribePublicKey(req.PublicKey)
+	pubKey, err := w.DescribePublicKey(req.NewPublicKey)
 	if err != nil {
 		return nil, fmt.Errorf("couldn't get the public key: %w", err)
 	}
 
+	currentPubKey, err := w.DescribePublicKey(req.CurrentPublicKey)
+	if err != nil {
+		return nil, fmt.Errorf("couldn't get the current public key: %w", err)
+	}
+
 	if pubKey.IsTainted() {
 		return nil, ErrPubKeyIsTainted
 	}
 
+	currentPubKeyHash, err := currentPubKey.Hash()
+	if err != nil {
+		return nil, fmt.Errorf("couldn't hash the current public key: %w", err)
+	}
+
 	inputData := commands.NewInputData(req.TxBlockHeight)
 	inputData.Command = &commandspb.InputData_KeyRotateSubmission{
 		KeyRotateSubmission: &commandspb.KeyRotateSubmission{
-			KeyNumber:   pubKey.Index(),
-			TargetBlock: req.TargetBlockHeight,
-			NewPubKey:   pubKey.Key(),
+			KeyNumber:         pubKey.Index(),
+			TargetBlock:       req.TargetBlockHeight,
+			NewPubKey:         pubKey.Key(),
+			CurrentPubKeyHash: currentPubKeyHash,
 		},
 	}
 
diff --git a/wallet/hd_public_key.go b/wallet/hd_public_key.go
index 24aff0e..05c4fc4 100644
--- a/wallet/hd_public_key.go
+++ b/wallet/hd_public_key.go
@@ -1,7 +1,11 @@
 package wallet
 
 import (
+	"encoding/hex"
 	"encoding/json"
+	"fmt"
+
+	vgcrypto "code.vegaprotocol.io/shared/libs/crypto"
 )
 
 type HDPublicKey struct {
@@ -36,6 +40,15 @@ func (k *HDPublicKey) AlgorithmName() string {
 	return k.Algorithm.Name
 }
 
+func (k *HDPublicKey) Hash() (string, error) {
+	decoded, err := hex.DecodeString(k.PublicKey)
+	if err != nil {
+		return "", fmt.Errorf("couldn't decode public key: %w", err)
+	}
+
+	return hex.EncodeToString(vgcrypto.Hash(decoded)), nil
+}
+
 func (k *HDPublicKey) MarshalJSON() ([]byte, error) {
 	type alias HDPublicKey
 	aliasPublicKey := (*alias)(k)
diff --git a/wallet/wallet.go b/wallet/wallet.go
index c14d251..f61bfab 100644
--- a/wallet/wallet.go
+++ b/wallet/wallet.go
@@ -41,6 +41,8 @@ type PublicKey interface {
 	Index() uint32
 	AlgorithmVersion() uint32
 	AlgorithmName() string
+	Hash() (string, error)
+
 	MarshalJSON() ([]byte, error)
 	UnmarshalJSON(data []byte) error
 }


Test patch:
diff --git a/cmd/key_rotate_test.go b/cmd/key_rotate_test.go
index 4bfa64d..7b75ee2 100644
--- a/cmd/key_rotate_test.go
+++ b/cmd/key_rotate_test.go
@@ -14,7 +14,8 @@ import (
 func TestRotateKeyFlags(t *testing.T) {
 	t.Run("Valid flags succeeds", testRotateKeyFlagsValidFlagsSucceeds)
 	t.Run("Missing wallet fails", testRotateKeyFlagsMissingWalletFails)
-	t.Run("Missing public key fails", testRotateKeyFlagsMissingPublicKeyFails)
+	t.Run("Missing new public key fails", testRotateKeyFlagsMissingNewPublicKeyFails)
+	t.Run("Missing current public key fails", testRotateKeyFlagsMissingCurrentPublicKeyFails)
 	t.Run("Missing tx height fails", testRotateKeyFlagsMissingTxBlockHeightFails)
 	t.Run("Missing target height fails", testRotateKeyFlagsMissingTargetBlockHeightFails)
 	t.Run("Validate fails when target height is less then tx height", testRotateKeyFlagsTargetFailsWhenBlockHeightIsLessThanTXHeight)
@@ -26,6 +27,7 @@ func testRotateKeyFlagsValidFlagsSucceeds(t *testing.T) {
 	// given
 	passphrase, passphraseFilePath := NewPassphraseFile(t, testDir)
 	walletName := vgrand.RandomStr(10)
+	currentPubKey := vgrand.RandomStr(20)
 	pubKey := vgrand.RandomStr(20)
 	txBlockHeight := uint64(20)
 	targetBlockHeight := uint64(25)
@@ -33,7 +35,8 @@ func testRotateKeyFlagsValidFlagsSucceeds(t *testing.T) {
 	f := &cmd.RotateKeyFlags{
 		Wallet:            walletName,
 		PassphraseFile:    passphraseFilePath,
-		PublicKey:         pubKey,
+		CurrentPubKey:     currentPubKey,
+		NewPublicKey:      pubKey,
 		TxBlockHeight:     txBlockHeight,
 		TargetBlockHeight: targetBlockHeight,
 	}
@@ -41,7 +44,8 @@ func testRotateKeyFlagsValidFlagsSucceeds(t *testing.T) {
 	expectedReq := &wallet.RotateKeyRequest{
 		Wallet:            walletName,
 		Passphrase:        passphrase,
-		PublicKey:         pubKey,
+		CurrentPublicKey:  currentPubKey,
+		NewPublicKey:      pubKey,
 		TxBlockHeight:     txBlockHeight,
 		TargetBlockHeight: targetBlockHeight,
 	}
@@ -100,18 +104,33 @@ func testRotateKeyFlagsMissingTargetBlockHeightFails(t *testing.T) {
 	assert.Nil(t, req)
 }
 
-func testRotateKeyFlagsMissingPublicKeyFails(t *testing.T) {
+func testRotateKeyFlagsMissingNewPublicKeyFails(t *testing.T) {
 	testDir := t.TempDir()
 
 	// given
 	f := newRotateKeyFlags(t, testDir)
-	f.PublicKey = ""
+	f.NewPublicKey = ""
 
 	// when
 	req, err := f.Validate()
 
 	// then
-	assert.ErrorIs(t, err, flags.FlagMustBeSpecifiedError("pubkey"))
+	assert.ErrorIs(t, err, flags.FlagMustBeSpecifiedError("new-pubkey"))
+	assert.Nil(t, req)
+}
+
+func testRotateKeyFlagsMissingCurrentPublicKeyFails(t *testing.T) {
+	testDir := t.TempDir()
+
+	// given
+	f := newRotateKeyFlags(t, testDir)
+	f.CurrentPubKey = ""
+
+	// when
+	req, err := f.Validate()
+
+	// then
+	assert.ErrorIs(t, err, flags.FlagMustBeSpecifiedError("current-pubkey"))
 	assert.Nil(t, req)
 }
 
@@ -137,12 +156,14 @@ func newRotateKeyFlags(t *testing.T, testDir string) *cmd.RotateKeyFlags {
 	_, passphraseFilePath := NewPassphraseFile(t, testDir)
 	walletName := vgrand.RandomStr(10)
 	pubKey := vgrand.RandomStr(20)
+	currentPubKey := vgrand.RandomStr(20)
 	txBlockHeight := uint64(20)
 	targetBlockHeight := uint64(25)
 
 	return &cmd.RotateKeyFlags{
 		Wallet:            walletName,
-		PublicKey:         pubKey,
+		NewPublicKey:      pubKey,
+		CurrentPubKey:     currentPubKey,
 		PassphraseFile:    passphraseFilePath,
 		TxBlockHeight:     txBlockHeight,
 		TargetBlockHeight: targetBlockHeight,
diff --git a/tests/sc_rotate_key_test.go b/tests/sc_rotate_key_test.go
index 781ddc0..21c943a 100644
--- a/tests/sc_rotate_key_test.go
+++ b/tests/sc_rotate_key_test.go
@@ -37,9 +37,22 @@ func TestRotateKeySucceeds(t *testing.T) {
 		WithMeta(map[string]string{"name": "key-1", "role": "validation"}).
 		LocatedUnder(home)
 
+	// when
+	generateKeyResp2, err := KeyGenerate(t, append(cmd,
+		"--meta", "name:key-2,role:validation",
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertGenerateKey(t, generateKeyResp2).
+		WithName(walletName).
+		WithMeta(map[string]string{"name": "key-2", "role": "validation"}).
+		LocatedUnder(home)
+
 	// when
 	resp, err := KeyRotate(t, append(cmd,
-		"--pubkey", generateKeyResp1.Key.PublicKey,
+		"--current-pubkey", generateKeyResp1.Key.PublicKey,
+		"--new-pubkey", generateKeyResp2.Key.PublicKey,
 		"--tx-height", "20",
 		"--target-height", "25",
 	))
@@ -65,21 +78,33 @@ func TestRotateKeyFailsOnTainedPublicKey(t *testing.T) {
 	}
 
 	// when
-	generateKeyResp, err := KeyGenerate(t, append(cmd,
+	generateKeyResp1, err := KeyGenerate(t, append(cmd,
 		"--meta", "name:key-1,role:validation",
 	))
 
 	// then
 	require.NoError(t, err)
-	AssertGenerateKey(t, generateKeyResp).
+	AssertGenerateKey(t, generateKeyResp1).
 		WithWalletCreation().
 		WithName(walletName).
 		WithMeta(map[string]string{"name": "key-1", "role": "validation"}).
 		LocatedUnder(home)
 
+	// when
+	generateKeyResp2, err := KeyGenerate(t, append(cmd,
+		"--meta", "name:key-2,role:validation",
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertGenerateKey(t, generateKeyResp2).
+		WithName(walletName).
+		WithMeta(map[string]string{"name": "key-2", "role": "validation"}).
+		LocatedUnder(home)
+
 	// when
 	err = KeyTaint(t, append(cmd,
-		"--pubkey", generateKeyResp.Key.PublicKey,
+		"--pubkey", generateKeyResp2.Key.PublicKey,
 	))
 
 	// then
@@ -87,7 +112,8 @@ func TestRotateKeyFailsOnTainedPublicKey(t *testing.T) {
 
 	// when
 	resp, err := KeyRotate(t, append(cmd,
-		"--pubkey", generateKeyResp.Key.PublicKey,
+		"--current-pubkey", generateKeyResp1.Key.PublicKey,
+		"--new-pubkey", generateKeyResp2.Key.PublicKey,
 		"--tx-height", "20",
 		"--target-height", "25",
 	))
@@ -146,7 +172,8 @@ func TestRotateKeyFailsInIsolatedWallet(t *testing.T) {
 		"--output", "json",
 		"--wallet", isolateKeyResp.Wallet,
 		"--passphrase-file", passphraseFilePath,
-		"--pubkey", generateKeyResp.Key.PublicKey,
+		"--new-pubkey", generateKeyResp.Key.PublicKey,
+		"--current-pubkey", "current-public-key",
 		"--tx-height", "20",
 		"--target-height", "25",
 	})
@@ -156,7 +183,48 @@ func TestRotateKeyFailsInIsolatedWallet(t *testing.T) {
 	require.ErrorIs(t, err, wallet.ErrCantRotateKeyInIsolatedWallet)
 }
 
-func TestRotateKeyFailsOnNonExitingKey(t *testing.T) {
+func TestRotateKeyFailsOnNonExitingNewPublicKey(t *testing.T) {
+	// given
+	home := t.TempDir()
+
+	_, 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
+	resp, err := KeyRotate(t, append(cmd,
+		"--current-pubkey", "current-public-key",
+		"--new-pubkey", "nonexisting",
+		"--tx-height", "20",
+		"--target-height", "25",
+	))
+
+	// then
+	require.Nil(t, resp)
+	require.ErrorIs(t, err, wallet.ErrPubKeyDoesNotExist)
+}
+
+func TestRotateKeyFailsOnNonExitingCurrentPublicKey(t *testing.T) {
 	// given
 	home := t.TempDir()
 
@@ -186,7 +254,8 @@ func TestRotateKeyFailsOnNonExitingKey(t *testing.T) {
 
 	// when
 	resp, err := KeyRotate(t, append(cmd,
-		"--pubkey", "nonexisting",
+		"--current-pubkey", "nonexisting",
+		"--new-pubkey", generateKeyResp.Key.PublicKey,
 		"--tx-height", "20",
 		"--target-height", "25",
 	))
@@ -213,7 +282,8 @@ func TestRotateKeyFailsOnNonExitingWallet(t *testing.T) {
 
 	// when
 	resp, err := KeyRotate(t, append(cmd,
-		"--pubkey", "nonexisting",
+		"--new-pubkey", "nonexisting",
+		"--current-pubkey", "nonexisting",
 		"--tx-height", "20",
 		"--target-height", "25",
 	))
@@ -253,7 +323,8 @@ func TestRotateKeyFailsWhenTargetHeighIsLessnThanTxHeight(t *testing.T) {
 
 	// when
 	resp, err := KeyRotate(t, append(cmd,
-		"--pubkey", "nonexisting",
+		"--new-pubkey", "nonexisting",
+		"--current-pubkey", "nonexisting",
 		"--tx-height", "20",
 		"--target-height", "19",
 	))
diff --git a/wallet/handler_test.go b/wallet/handler_test.go
index 4655010..de10632 100644
--- a/wallet/handler_test.go
+++ b/wallet/handler_test.go
@@ -550,14 +550,17 @@ func testSignMessageWithNonExistingWalletFails(t *testing.T) {
 func TestRotateKey(t *testing.T) {
 	t.Run("Rotate key succeeds", testRotateKeySucceeds)
 	t.Run("Rotate key with non existing wallet fails", testRotateWithNonExistingWalletFails)
-	t.Run("Rotate key with non existing public key fails", testRotateKeyWithNonExistingPublicKeyFails)
+	t.Run("Rotate key with non existing new public key fails", testRotateKeyWithNonExistingNewPublicKeyFails)
+	t.Run("Rotate key with non existing current public key fails", testRotateKeyWithNonExistingCurrentPublicKeyFails)
 	t.Run("Rotate key tained public key fails", testRotateKeyWithTaintedPublicKeyFails)
 }
 
 func testRotateKeySucceeds(t *testing.T) {
 	// given
-	w := importWalletWithKey(t)
-	kp := w.ListKeyPairs()[0]
+	w := importWalletWithTwoKeys(t)
+
+	currentPubKey := w.ListPublicKeys()[0]
+	newPubKey := w.ListPublicKeys()[1]
 
 	masterKeyPair, err := w.GetMasterKeyPair()
 	require.NoError(t, err)
@@ -565,7 +568,8 @@ func testRotateKeySucceeds(t *testing.T) {
 	req := &wallet.RotateKeyRequest{
 		Wallet:            w.Name(),
 		Passphrase:        "passphrase",
-		PublicKey:         kp.PublicKey(),
+		NewPublicKey:      newPubKey.Key(),
+		CurrentPublicKey:  currentPubKey.Key(),
 		TxBlockHeight:     20,
 		TargetBlockHeight: 25,
 	}
@@ -599,9 +603,9 @@ func testRotateKeySucceeds(t *testing.T) {
 	require.NotNil(t, keyRotate)
 
 	require.Equal(t, req.TxBlockHeight, inputData.BlockHeight)
-	require.Equal(t, kp.Index(), keyRotate.KeyRotateSubmission.KeyNumber)
+	require.Equal(t, newPubKey.Index(), keyRotate.KeyRotateSubmission.KeyNumber)
 	require.Equal(t, req.TargetBlockHeight, keyRotate.KeyRotateSubmission.TargetBlock)
-	require.Equal(t, req.PublicKey, keyRotate.KeyRotateSubmission.NewPubKey)
+	require.Equal(t, req.NewPublicKey, keyRotate.KeyRotateSubmission.NewPubKey)
 }
 
 func testRotateWithNonExistingWalletFails(t *testing.T) {
@@ -609,7 +613,7 @@ func testRotateWithNonExistingWalletFails(t *testing.T) {
 	req := &wallet.RotateKeyRequest{
 		Wallet:            vgrand.RandomStr(5),
 		Passphrase:        "passphrase",
-		PublicKey:         "nonexisting",
+		NewPublicKey:      "nonexisting",
 		TxBlockHeight:     20,
 		TargetBlockHeight: 25,
 	}
@@ -627,14 +631,14 @@ func testRotateWithNonExistingWalletFails(t *testing.T) {
 	assert.Nil(t, resp)
 }
 
-func testRotateKeyWithNonExistingPublicKeyFails(t *testing.T) {
+func testRotateKeyWithNonExistingNewPublicKeyFails(t *testing.T) {
 	// given
 	w := importWalletWithKey(t)
 
 	req := &wallet.RotateKeyRequest{
 		Wallet:            w.Name(),
 		Passphrase:        "passphrase",
-		PublicKey:         "nonexisting",
+		NewPublicKey:      "nonexisting",
 		TxBlockHeight:     20,
 		TargetBlockHeight: 25,
 	}
@@ -652,18 +656,49 @@ func testRotateKeyWithNonExistingPublicKeyFails(t *testing.T) {
 	require.Error(t, err)
 }
 
-func testRotateKeyWithTaintedPublicKeyFails(t *testing.T) {
+func testRotateKeyWithNonExistingCurrentPublicKeyFails(t *testing.T) {
 	// given
 	w := importWalletWithKey(t)
-	kp := w.ListKeyPairs()[0]
 
-	err := w.TaintKey(kp.PublicKey())
+	newPubKey := w.ListPublicKeys()[0]
+
+	req := &wallet.RotateKeyRequest{
+		Wallet:            w.Name(),
+		Passphrase:        "passphrase",
+		NewPublicKey:      newPubKey.Key(),
+		CurrentPublicKey:  "non-existing",
+		TxBlockHeight:     20,
+		TargetBlockHeight: 25,
+	}
+
+	// setup
+	store := handlerMocks(t)
+	store.EXPECT().WalletExists(req.Wallet).Times(1).Return(true)
+	store.EXPECT().GetWallet(req.Wallet, req.Passphrase).Times(1).Return(w, nil)
+
+	// when
+	resp, err := wallet.RotateKey(store, req)
+
+	// then
+	require.Nil(t, resp)
+	require.Error(t, err)
+}
+
+func testRotateKeyWithTaintedPublicKeyFails(t *testing.T) {
+	// given
+	w := importWalletWithTwoKeys(t)
+
+	currentPubKey := w.ListPublicKeys()[0]
+	newPubKey := w.ListPublicKeys()[1]
+
+	err := w.TaintKey(newPubKey.Key())
 	require.NoError(t, err)
 
 	req := &wallet.RotateKeyRequest{
 		Wallet:            w.Name(),
 		Passphrase:        "passphrase",
-		PublicKey:         kp.PublicKey(),
+		NewPublicKey:      newPubKey.Key(),
+		CurrentPublicKey:  currentPubKey.Key(),
 		TxBlockHeight:     20,
 		TargetBlockHeight: 25,
 	}
@@ -698,6 +733,16 @@ func newWalletWithKeys(t *testing.T, n int) *wallet.HDWallet {
 	return w
 }
 
+func importWalletWithTwoKeys(t *testing.T) *wallet.HDWallet {
+	t.Helper()
+	w := importWalletWithKey(t)
+	if _, err := w.GenerateKeyPair(nil); err != nil {
+		t.Fatalf("couldn't generate second key: %v", err)
+	}
+
+	return w
+}
+
 func importWalletWithKey(t *testing.T) *wallet.HDWallet {
 	t.Helper()
 	w, err := wallet.ImportHDWallet(
@@ -712,6 +757,7 @@ func importWalletWithKey(t *testing.T) *wallet.HDWallet {
 	if _, err := w.GenerateKeyPair(nil); err != nil {
 		t.Fatalf("couldn't generate key: %v", err)
 	}
+
 	return w
 }