Repository: vegaprotocol/go-wallet
Pull request #213 (2021-08-31 18:49:35)

Allow verification of message from which you don't have the private key


Patch:
diff --git a/cmd/README.md b/cmd/README.md
index f1abb13..a5e2185 100644
--- a/cmd/README.md
+++ b/cmd/README.md
@@ -64,12 +64,9 @@ able to retrieve it with specific commands.
 If you want to restore your wallet, use the following command:
 
 ```sh
-vegawallet import --name "YOUR_USERNAME" --mnemonic-file "PATH_TO_YOUR_MNEMONIC"
+vegawallet import --name "YOUR_USERNAME" --mnemonic "YOUR_MNEMONIC"
 ```
 
-The flag `--mnemonic-file` is used to locate the file that contains the
-mnemonic.
-
 It will then prompt you to input a passphrase, and then confirm that passphrase.
 You'll use this username and passphrase to login to Vega Console. You can also
 specify the passphrase with the `--passphrase-file` flag.
@@ -115,8 +112,7 @@ An item of metadata is represented as a key-value property.
 
 ### Give an alias to a key
 
-You can give to each key pair a nickname/alias with a metadata `name`. For
-example:
+You can give to each key pair a nickname/alias with a metadata `name`. For example:
 
 ```sh
 vegawallet key annotate --name "YOUR_USERNAME" --meta "name:my-alias" --pubkey "YOUR_HEX_PUBLIC_KEY"
@@ -154,7 +150,7 @@ passphrase with the `--passphrase-file` flag.
 
 ### Important
 
-If you tainted a key for security reasons, you should not untaint it.
+If you tainted a key for security reasons, you should not untaint it. 
 
 ## List the key pairs
 
@@ -179,7 +175,7 @@ commands:
 
 ```sh
 vegawallet sign --name "YOUR_NAME" --pubkey "YOUR_HEX_PUBIC_KEY" --message "c3BpY2Ugb2YgZHVuZQo="
-vegawallet verify --name "YOUR_NAME" --pubkey "YOUR_HEX_PUBIC_KEY" --message "c3BpY2Ugb2YgZHVuZQo=" --signature "76f978asd6fa8s76f"
+vegawallet verify --pubkey "YOUR_HEX_PUBIC_KEY" --message "c3BpY2Ugb2YgZHVuZQo=" --signature "76f978asd6fa8s76f"
 ```
 
 It will then prompt you to input a passphrase. You can also specify the
diff --git a/cmd/import.go b/cmd/import.go
index 03e031b..0a88fef 100644
--- a/cmd/import.go
+++ b/cmd/import.go
@@ -3,10 +3,7 @@ package cmd
 import (
 	"errors"
 	"fmt"
-	"io/fs"
 	"os"
-	"path/filepath"
-	"strings"
 
 	"code.vegaprotocol.io/go-wallet/cmd/printer"
 	"code.vegaprotocol.io/go-wallet/wallet"
@@ -17,7 +14,7 @@ var (
 	importArgs struct {
 		name           string
 		passphraseFile string
-		mnemonicFile   string
+		mnemonic       string
 	}
 
 	importCmd = &cobra.Command{
@@ -32,7 +29,7 @@ func init() {
 	rootCmd.AddCommand(importCmd)
 	importCmd.Flags().StringVarP(&importArgs.name, "name", "n", "", "Name of the wallet to use")
 	importCmd.Flags().StringVarP(&importArgs.passphraseFile, "passphrase-file", "p", "", "Path of the file containing the passphrase to access the wallet")
-	importCmd.Flags().StringVarP(&importArgs.mnemonicFile, "mnemonic-file", "m", "", `Path of the file containing the mnemonic of the wallet "swing ceiling chaos..."`)
+	importCmd.Flags().StringVarP(&importArgs.mnemonic, "mnemonic", "m", "", `Mnemonic of the wallet "swing ceiling chaos..."`)
 }
 
 func runImport(_ *cobra.Command, _ []string) error {
@@ -50,8 +47,8 @@ func runImport(_ *cobra.Command, _ []string) error {
 		return errors.New("wallet name is required")
 	}
 
-	if len(importArgs.mnemonicFile) == 0 {
-		return errors.New("path to wallet mnemonic is required")
+	if len(importArgs.mnemonic) == 0 {
+		return errors.New("wallet mnemonic is required")
 	}
 
 	passphrase, err := getPassphrase(importArgs.passphraseFile, true)
@@ -59,20 +56,15 @@ func runImport(_ *cobra.Command, _ []string) error {
 		return err
 	}
 
-	mnemonicDir, mnemonicFileName := filepath.Split(importArgs.mnemonicFile)
-	rawMnemonic, err := fs.ReadFile(os.DirFS(mnemonicDir), mnemonicFileName)
-	if err != nil {
-		return fmt.Errorf("couldn't read mnemonic file: %w", err)
-	}
-	mnemonic := strings.Trim(string(rawMnemonic), "\n")
-
-	err = handler.ImportWallet(importArgs.name, passphrase, mnemonic)
+	err = handler.ImportWallet(importArgs.name, passphrase, importArgs.mnemonic)
 	if err != nil {
 		return fmt.Errorf("couldn't import wallet: %w", err)
 	}
 
 	if rootArgs.output == "human" {
 		p := printer.NewHumanPrinter()
+		p.CheckMark().Text("Service configuration created at: ").SuccessText(rootArgs.rootPath).Jump()
+		p.CheckMark().Text("Service RSA keys created at: ").SuccessText(rootArgs.rootPath).Jump()
 		p.CheckMark().SuccessText("Importing the wallet succeeded").NJump(2)
 
 		p.BlueArrow().InfoText("Generate a key pair").Jump()
diff --git a/cmd/root.go b/cmd/root.go
index 9072785..e0a0e44 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -3,7 +3,6 @@ package cmd
 import (
 	"errors"
 	"fmt"
-	"io/fs"
 	"os"
 	"path/filepath"
 	"strings"
@@ -117,8 +116,7 @@ func getPassphrase(flaggedPassphraseFile string, confirmInput bool) (string, err
 	hasPassphraseFileFlag := len(flaggedPassphraseFile) != 0
 
 	if hasPassphraseFileFlag {
-		passphraseDir, passphraseFileName := filepath.Split(flaggedPassphraseFile)
-		rawPassphrase, err := fs.ReadFile(os.DirFS(passphraseDir), passphraseFileName)
+		rawPassphrase, err := os.ReadFile(flaggedPassphraseFile)
 		if err != nil {
 			return "", err
 		}
diff --git a/cmd/sign.go b/cmd/sign.go
index 92758b2..8214611 100644
--- a/cmd/sign.go
+++ b/cmd/sign.go
@@ -80,7 +80,7 @@ func runSign(_ *cobra.Command, _ []string) error {
 
 		p.BlueArrow().InfoText("Verify a signature").Jump()
 		p.Text("To verify a base-64 encoded message, use the following commands:").NJump(2)
-		p.Code(fmt.Sprintf("%s verify --name \"%s\" --pubkey %s --message \"%s\" --signature %s", os.Args[0], signArgs.name, signArgs.pubkey, signArgs.message, encodedSig)).NJump(2)
+		p.Code(fmt.Sprintf("%s verify --pubkey %s --message \"%s\" --signature %s", os.Args[0], signArgs.pubkey, signArgs.message, encodedSig)).NJump(2)
 		p.Text("For more information, use ").Bold("--help").Text(" flag.").Jump()
 	} else if rootArgs.output == "json" {
 		return printSignJson(encodedSig)
diff --git a/cmd/verify.go b/cmd/verify.go
index 56da3f6..d1f5962 100644
--- a/cmd/verify.go
+++ b/cmd/verify.go
@@ -13,11 +13,9 @@ import (
 
 var (
 	verifyArgs struct {
-		name           string
-		passphraseFile string
-		sig            string
-		message        string
-		pubkey         string
+		sig     string
+		message string
+		pubkey  string
 	}
 
 	verifyCmd = &cobra.Command{
@@ -30,8 +28,6 @@ var (
 
 func init() {
 	rootCmd.AddCommand(verifyCmd)
-	verifyCmd.Flags().StringVarP(&verifyArgs.name, "name", "n", "", "Name of the wallet to use")
-	verifyCmd.Flags().StringVarP(&verifyArgs.passphraseFile, "passphrase-file", "p", "", "Path of the file containing the passphrase to access the wallet")
 	verifyCmd.Flags().StringVarP(&verifyArgs.message, "message", "m", "", "Message to be verified (base64)")
 	verifyCmd.Flags().StringVarP(&verifyArgs.sig, "signature", "s", "", "Signature to be verified (base64)")
 	verifyCmd.Flags().StringVarP(&verifyArgs.pubkey, "pubkey", "k", "", "Public key to be used (hex)")
@@ -43,9 +39,6 @@ func runVerify(_ *cobra.Command, _ []string) error {
 		return err
 	}
 
-	if len(verifyArgs.name) == 0 {
-		return errors.New("wallet name is required")
-	}
 	if len(verifyArgs.pubkey) == 0 {
 		return errors.New("pubkey is required")
 	}
@@ -64,17 +57,7 @@ func runVerify(_ *cobra.Command, _ []string) error {
 		return errors.New("signature should be encoded into base64")
 	}
 
-	passphrase, err := getPassphrase(verifyArgs.passphraseFile, false)
-	if err != nil {
-		return err
-	}
-
-	err = handler.LoginWallet(verifyArgs.name, passphrase)
-	if err != nil {
-		return fmt.Errorf("could not login to the wallet: %w", err)
-	}
-
-	isValid, err := handler.VerifyAny(verifyArgs.name, decodedMessage, decodedSig, verifyArgs.pubkey)
+	isValid, err := handler.VerifyAny(decodedMessage, decodedSig, verifyArgs.pubkey)
 	if err != nil {
 		return fmt.Errorf("could not verify the message: %w", err)
 	}
@@ -89,7 +72,7 @@ func runVerify(_ *cobra.Command, _ []string) error {
 
 		p.BlueArrow().InfoText("Sign a message").Jump()
 		p.Text("To sign a base-64 encoded message, use the following commands:").NJump(2)
-		p.Code(fmt.Sprintf("%s sign --name \"%s\" --pubkey %s --message \"YOUR_MESSAGE\"", os.Args[0], verifyArgs.name, verifyArgs.pubkey)).NJump(2)
+		p.Code(fmt.Sprintf("%s sign --name \"YOUR_NAME\" --pubkey %s --message \"YOUR_MESSAGE\"", os.Args[0], verifyArgs.pubkey)).NJump(2)
 		p.Text("For more information, use ").Bold("--help").Text(" flag.").Jump()
 	} else if rootArgs.output == "json" {
 		return printVerifyJson(isValid)
diff --git a/service/mocks/wallet_handler_mock.go b/service/mocks/wallet_handler_mock.go
index 757fdb9..e13be57 100644
--- a/service/mocks/wallet_handler_mock.go
+++ b/service/mocks/wallet_handler_mock.go
@@ -209,16 +209,16 @@ func (mr *MockWalletHandlerMockRecorder) UpdateMeta(arg0, arg1, arg2, arg3 inter
 }
 
 // VerifyAny mocks base method
-func (m *MockWalletHandler) VerifyAny(arg0 string, arg1, arg2 []byte, arg3 string) (bool, error) {
+func (m *MockWalletHandler) VerifyAny(arg0, arg1 []byte, arg2 string) (bool, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "VerifyAny", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "VerifyAny", arg0, arg1, arg2)
 	ret0, _ := ret[0].(bool)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // VerifyAny indicates an expected call of VerifyAny
-func (mr *MockWalletHandlerMockRecorder) VerifyAny(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+func (mr *MockWalletHandlerMockRecorder) VerifyAny(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAny", reflect.TypeOf((*MockWalletHandler)(nil).VerifyAny), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAny", reflect.TypeOf((*MockWalletHandler)(nil).VerifyAny), arg0, arg1, arg2)
 }
diff --git a/service/service.go b/service/service.go
index 6f3a9ed..e718a78 100644
--- a/service/service.go
+++ b/service/service.go
@@ -373,7 +373,7 @@ type WalletHandler interface {
 	ListPublicKeys(name string) ([]wallet.PublicKey, error)
 	SignTxV2(name string, req *walletpb.SubmitTransactionRequest, height uint64) (*commandspb.Transaction, error)
 	SignAny(name string, inputData []byte, pubKey string) ([]byte, error)
-	VerifyAny(name string, inputData, sig []byte, pubKey string) (bool, error)
+	VerifyAny(inputData, sig []byte, pubKey string) (bool, error)
 	TaintKey(name, pubKey, passphrase string) error
 	UpdateMeta(name, pubKey, passphrase string, meta []wallet.Meta) error
 	GetWalletPath(name string) (string, error)
@@ -698,13 +698,7 @@ func (s *Service) VerifyAny(t string, w http.ResponseWriter, r *http.Request, _
 		return
 	}
 
-	name, err := s.auth.VerifyToken(t)
-	if err != nil {
-		writeForbiddenError(w, err)
-		return
-	}
-
-	verified, err := s.handler.VerifyAny(name, req.decodedInputData, req.decodedSignature, req.PubKey)
+	verified, err := s.handler.VerifyAny(req.decodedInputData, req.decodedSignature, req.PubKey)
 	if err != nil {
 		writeForbiddenError(w, err)
 		return
diff --git a/wallet/handler.go b/wallet/handler.go
index 8f50425..26b69c5 100644
--- a/wallet/handler.go
+++ b/wallet/handler.go
@@ -1,10 +1,12 @@
 package wallet
 
 import (
+	"encoding/hex"
 	"errors"
 	"fmt"
 	"sync"
 
+	wcrypto "code.vegaprotocol.io/go-wallet/crypto"
 	"code.vegaprotocol.io/protos/commands"
 	commandspb "code.vegaprotocol.io/protos/vega/commands/v1"
 	walletpb "code.vegaprotocol.io/protos/vega/wallet/v1"
@@ -251,16 +253,17 @@ func (h *Handler) SignTxV2(name string, req *walletpb.SubmitTransactionRequest,
 	return commands.NewTransaction(pubKey, marshalledData, signature), nil
 }
 
-func (h *Handler) VerifyAny(name string, inputData, sig []byte, pubKey string) (bool, error) {
+func (h *Handler) VerifyAny(inputData, sig []byte, pubKey string) (bool, error) {
 	h.mu.RLock()
 	defer h.mu.RUnlock()
 
-	w, err := h.getLoggedWallet(name)
+	decodedPubKey, err := hex.DecodeString(pubKey)
 	if err != nil {
 		return false, err
 	}
 
-	return w.VerifyAny(pubKey, inputData, sig)
+	signatureAlgorithm := wcrypto.NewEd25519()
+	return signatureAlgorithm.Verify(decodedPubKey, inputData, sig)
 }
 
 func (h *Handler) TaintKey(name, pubKey, passphrase string) error {


Test patch:
diff --git a/service/service_test.go b/service/service_test.go
index 51cd32a..afd3010 100644
--- a/service/service_test.go
+++ b/service/service_test.go
@@ -12,11 +12,11 @@ import (
 	"testing"
 
 	"code.vegaprotocol.io/go-wallet/crypto"
-	"code.vegaprotocol.io/protos/vega/api"
-	commandspb "code.vegaprotocol.io/protos/vega/commands/v1"
 	"code.vegaprotocol.io/go-wallet/service"
 	"code.vegaprotocol.io/go-wallet/service/mocks"
 	"code.vegaprotocol.io/go-wallet/wallet"
+	"code.vegaprotocol.io/protos/vega/api"
+	commandspb "code.vegaprotocol.io/protos/vega/commands/v1"
 	"github.com/stretchr/testify/require"
 
 	"github.com/golang/mock/gomock"
@@ -849,8 +849,7 @@ func testVerifyingAnythingSucceeds(t *testing.T) {
 	s := getTestService(t)
 	defer s.ctrl.Finish()
 
-	s.auth.EXPECT().VerifyToken("eyXXzA").Times(1).Return("jeremy", nil)
-	s.handler.EXPECT().VerifyAny("jeremy", []byte("spice of dune"), []byte("Sietch Tabr"), "asdasasdasd").
+	s.handler.EXPECT().VerifyAny([]byte("spice of dune"), []byte("Sietch Tabr"), "asdasasdasd").
 		Times(1).Return(true, nil)
 	payload := `{"inputData": "c3BpY2Ugb2YgZHVuZQ==", "pubKey": "asdasasdasd", "signature": "U2lldGNoIFRhYnI="}`
 	r := httptest.NewRequest("POST", "scheme://host/path", bytes.NewBufferString(payload))
@@ -871,8 +870,7 @@ func testVerifyingAnythingFails(t *testing.T) {
 	s := getTestService(t)
 	defer s.ctrl.Finish()
 
-	s.auth.EXPECT().VerifyToken("eyXXzA").Times(1).Return("jeremy", nil)
-	s.handler.EXPECT().VerifyAny("jeremy", []byte("spice of dune"), []byte("Sietch Tabr"), "asdasasdasd").
+	s.handler.EXPECT().VerifyAny([]byte("spice of dune"), []byte("Sietch Tabr"), "asdasasdasd").
 		Times(1).Return(false, nil)
 	payload := `{"inputData":"c3BpY2Ugb2YgZHVuZQ==", "pubKey": "asdasasdasd", "signature": "U2lldGNoIFRhYnI="}`
 	r := httptest.NewRequest("POST", "scheme://host/path", bytes.NewBufferString(payload))
diff --git a/wallet/handler_test.go b/wallet/handler_test.go
index d860451..fa84fb2 100644
--- a/wallet/handler_test.go
+++ b/wallet/handler_test.go
@@ -3,9 +3,9 @@ package wallet_test
 import (
 	"testing"
 
+	"code.vegaprotocol.io/go-wallet/wallet"
 	commandspb "code.vegaprotocol.io/protos/vega/commands/v1"
 	walletpb "code.vegaprotocol.io/protos/vega/wallet/v1"
-	"code.vegaprotocol.io/go-wallet/wallet"
 	"github.com/stretchr/testify/require"
 
 	"github.com/golang/mock/gomock"
@@ -80,7 +80,7 @@ func TestHandler(t *testing.T) {
 	t.Run("Signing transaction request (v2) with tainted key fails", testHandlerSigningTxV2WithTaintedKeyFails)
 	t.Run("Signing and verifying a message succeeds", testHandlerSigningAndVerifyingMessageSucceeds)
 	t.Run("Signing a message with logged out wallet fails", testHandlerSigningMessageWithLoggedOutWalletFails)
-	t.Run("Verifying a message with logged out wallet fails", testHandlerVerifyingMessageWithLoggedOutWalletSucceeds)
+	t.Run("Verifying a message with logged out wallet succeeds", testHandlerVerifyingMessageWithLoggedOutWalletSucceeds)
 }
 
 func testHandlerCreatingWalletSucceeds(t *testing.T) {
@@ -1375,7 +1375,7 @@ func testHandlerSigningAndVerifyingMessageSucceeds(t *testing.T) {
 	assert.NotEmpty(t, sig)
 
 	// when
-	verified, err := h.VerifyAny(name, data, sig, pubKey)
+	verified, err := h.VerifyAny(data, sig, pubKey)
 
 	// then
 	require.NoError(t, err)
@@ -1458,9 +1458,9 @@ func testHandlerVerifyingMessageWithLoggedOutWalletSucceeds(t *testing.T) {
 	})
 
 	// when
-	verified, err := h.VerifyAny(name, data, sig, pubKey)
+	verified, err := h.VerifyAny(data, sig, pubKey)
 
 	// then
-	require.EqualError(t, err, wallet.ErrWalletNotLoggedIn.Error())
-	assert.False(t, verified)
+	require.NoError(t, err)
+	assert.True(t, verified)
 }