diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 46cc61991b..29423f3970 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -10,7 +10,6 @@ import 'hardhat-deploy'; // Hardhat ignore warnings plugin - https://www.npmjs.com/package/hardhat-ignore-warnings import 'hardhat-ignore-warnings'; -import './tasks/wallet-extension'; import * as abigen from './tasks/abigen'; import './tasks/obscuro-deploy'; diff --git a/contracts/tasks/wallet-extension.ts b/contracts/tasks/wallet-extension.ts deleted file mode 100644 index c79d1c7e50..0000000000 --- a/contracts/tasks/wallet-extension.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { task } from "hardhat/config"; -import http from 'http'; - - -task("obscuro:wallet-extension:add-key", "Creates a viewing key for a specifiec address") -.addParam("address", "The address for which to add key") -.setAction(async function(args, hre) { - async function viewingKeyForAddress(address: string) : Promise { - return new Promise((resolve, fail)=> { - - const data = {"address": address} - - const req = http.request({ - host: '127.0.0.1', - port: 3000, - path: '/generateviewingkey/', - method: 'post', - headers: { - 'Content-Type': 'application/json' - } - }, (response)=>{ - if (response.statusCode != 200) { - console.error(response); - fail(response.statusCode); - return; - } - - let chunks : string[] = [] - response.on('data', (chunk)=>{ - chunks.push(chunk); - }); - - response.on('end', ()=> { - resolve(chunks.join('')); - }); - }); - req.write(JSON.stringify(data)); - req.end() - setTimeout(resolve, 15_000); - }); - } - - interface SignedData { signature: string, address: string } - - async function submitKey(signedData: SignedData) : Promise { - return await new Promise(async (resolve, fail)=>{ - const req = http.request({ - host: '127.0.0.1', - port: 3000, - path: '/submitviewingkey/', - method: 'post', - headers: { - 'Content-Type': 'application/json' - } - }, (response)=>{ - if (response.statusCode == 200) { - resolve(response.statusCode); - } else { - console.log(response.statusMessage) - fail(response.statusCode); - } - }); - - req.write(JSON.stringify(signedData)); - req.end() - }); - } - - const key = await viewingKeyForAddress(args.address); - - const signaturePromise = (await hre.ethers.getSigner(args.address)).signMessage(`vk${key}`); - const signedData = { 'signature': await signaturePromise, 'address': args.address }; - await submitKey(signedData) -}); \ No newline at end of file diff --git a/integration/obscurogateway/tengateway_test.go b/integration/obscurogateway/tengateway_test.go index 774a2a4d19..1dd7fc7d63 100644 --- a/integration/obscurogateway/tengateway_test.go +++ b/integration/obscurogateway/tengateway_test.go @@ -5,12 +5,16 @@ import ( "context" "encoding/json" "fmt" + "io" "math/big" "net/http" "strings" "testing" "time" + "github.com/go-kit/kit/transport/http/jsonrpc" + "github.com/ten-protocol/go-ten/go/rpc" + log2 "github.com/ten-protocol/go-ten/go/common/log" "github.com/ethereum/go-ethereum" @@ -102,6 +106,8 @@ func TestTenGateway(t *testing.T) { "testClosingConnectionWhileSubscribed": testClosingConnectionWhileSubscribed, "testSubscriptionTopics": testSubscriptionTopics, "testDifferentMessagesOnRegister": testDifferentMessagesOnRegister, + "testInvokeNonSensitiveMethod": testInvokeNonSensitiveMethod, + "testGetStorageAtForReturningUserID": testGetStorageAtForReturningUserID, } { t.Run(name, func(t *testing.T) { test(t, httpURL, wsURL, w) @@ -620,6 +626,83 @@ func testDifferentMessagesOnRegister(t *testing.T, httpURL, wsURL string, w wall require.NoError(t, err) } +func testInvokeNonSensitiveMethod(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { + user, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + require.NoError(t, err) + + // call one of the non-sensitive methods with unauthenticated user + // and make sure gateway is not complaining about not having viewing keys + respBody := makeHTTPEthJSONReq(httpURL, rpc.ChainID, user.tgClient.UserID(), nil) + if strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", rpc.ChainID)) { + t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", rpc.ChainID) + } +} + +func testGetStorageAtForReturningUserID(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { + user, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + require.NoError(t, err) + + type JSONResponse struct { + Result string `json:"result"` + } + var response JSONResponse + + // make a request to GetStorageAt with correct parameters to get userID that exists in the database + respBody := makeHTTPEthJSONReq(httpURL, rpc.GetStorageAt, user.tgClient.UserID(), []interface{}{"getUserID", "0", nil}) + if err = json.Unmarshal(respBody, &response); err != nil { + t.Error("Unable to unmarshal response") + } + if response.Result != user.tgClient.UserID() { + t.Errorf("Wrong UserID returned. Expected: %s, received: %s", user.tgClient.UserID(), response.Result) + } + + // make a request to GetStorageAt with correct parameters to get userID, but with wrong userID + respBody2 := makeHTTPEthJSONReq(httpURL, rpc.GetStorageAt, "invalid_user_id", []interface{}{"getUserID", "0", nil}) + if !strings.Contains(string(respBody2), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") { + t.Error("eth_getStorageAt did not respond with error: method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") + } + + // make a request to GetStorageAt with wrong parameters to get userID, but correct userID + respBody3 := makeHTTPEthJSONReq(httpURL, rpc.GetStorageAt, user.tgClient.UserID(), []interface{}{"abc", "0", nil}) + if !strings.Contains(string(respBody3), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") { + t.Error("eth_getStorageAt did not respond with error: no signed viewing keys found") + } +} + +func makeRequestHTTP(url string, body []byte) []byte { + generateViewingKeyBody := bytes.NewBuffer(body) + resp, err := http.Post(url, "application/json", generateViewingKeyBody) //nolint:noctx,gosec + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + if err != nil { + panic(err) + } + viewingKey, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + return viewingKey +} + +func makeHTTPEthJSONReq(url string, method string, userID string, params interface{}) []byte { + reqBody := prepareRequestBody(method, params) + return makeRequestHTTP(fmt.Sprintf("%s/v1/?token=%s", url, userID), reqBody) +} + +func prepareRequestBody(method string, params interface{}) []byte { + reqBodyBytes, err := json.Marshal(map[string]interface{}{ + wecommon.JSONKeyRPCVersion: jsonrpc.Version, + wecommon.JSONKeyMethod: method, + wecommon.JSONKeyParams: params, + wecommon.JSONKeyID: "1", + }) + if err != nil { + panic(fmt.Errorf("failed to prepare request body. Cause: %w", err)) + } + return reqBodyBytes +} + func transferRandomAddr(t *testing.T, client *ethclient.Client, w wallet.Wallet) common.TxHash { //nolint: unused ctx := context.Background() toAddr := datagenerator.RandomAddress() diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index 6d38b70feb..dad70da1a5 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -16,8 +16,6 @@ import ( "github.com/ten-protocol/go-ten/tools/walletextension" "github.com/ten-protocol/go-ten/tools/walletextension/common" "github.com/ten-protocol/go-ten/tools/walletextension/userconn" - - gethcommon "github.com/ethereum/go-ethereum/common" ) // NewHTTPRoutes returns the http specific routes @@ -31,14 +29,6 @@ func NewHTTPRoutes(walletExt *walletextension.WalletExtension) []node.Route { Name: common.PathReady, Func: httpHandler(walletExt, readyRequestHandler), }, - { - Name: common.PathGenerateViewingKey, - Func: httpHandler(walletExt, generateViewingKeyRequestHandler), - }, - { - Name: common.PathSubmitViewingKey, - Func: httpHandler(walletExt, submitViewingKeyRequestHandler), - }, { Name: common.APIVersion1 + common.PathJoin, Func: httpHandler(walletExt, joinRequestHandler), @@ -106,15 +96,6 @@ func NewWSRoutes(walletExt *walletextension.WalletExtension) []node.Route { Name: common.PathReady, Func: wsHandler(walletExt, readyRequestHandler), }, - { - Name: common.PathGenerateViewingKey, - Func: wsHandler(walletExt, generateViewingKeyRequestHandler), - }, - - { - Name: common.PathSubmitViewingKey, - Func: wsHandler(walletExt, submitViewingKeyRequestHandler), - }, } } @@ -198,70 +179,6 @@ func ethRequestHandler(walletExt *walletextension.WalletExtension, conn userconn // readyRequestHandler is used to check whether the server is ready func readyRequestHandler(_ *walletextension.WalletExtension, _ userconn.UserConn) {} -// generateViewingKeyRequestHandler parses the gen vk request -func generateViewingKeyRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { - body, err := conn.ReadRequest() - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) - return - } - - var reqJSONMap map[string]string - err = json.Unmarshal(body, &reqJSONMap) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal address request - %w", err)) - return - } - - address := gethcommon.HexToAddress(reqJSONMap[common.JSONKeyAddress]) - - pubViewingKey, err := walletExt.GenerateViewingKey(address) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("unable to generate vieweing key - %w", err)) - return - } - - err = conn.WriteResponse([]byte(pubViewingKey)) - if err != nil { - walletExt.Logger().Error("error writing success response", log.ErrKey, err) - } -} - -// submitViewingKeyRequestHandler submits the viewing key and signed bytes to the WE -func submitViewingKeyRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { - body, err := conn.ReadRequest() - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("error reading request: %w", err)) - return - } - - var reqJSONMap map[string]string - err = json.Unmarshal(body, &reqJSONMap) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not unmarshal address request - %w", err)) - return - } - accAddress := gethcommon.HexToAddress(reqJSONMap[common.JSONKeyAddress]) - - signature, err := hex.DecodeString(reqJSONMap[common.JSONKeySignature][2:]) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not decode signature from client to hex - %w", err)) - return - } - - err = walletExt.SubmitViewingKey(accAddress, signature) - if err != nil { - handleError(conn, walletExt.Logger(), fmt.Errorf("could not submit viewing key - %w", err)) - return - } - - err = conn.WriteResponse([]byte(common.SuccessMsg)) - if err != nil { - walletExt.Logger().Error("error writing success response", log.ErrKey, err) - return - } -} - // This function handles request to /join endpoint. It is responsible to create new user (new key-pair) and store it to the db func joinRequestHandler(walletExt *walletextension.WalletExtension, conn userconn.UserConn) { // todo (@ziga) add protection against DDOS attacks diff --git a/tools/walletextension/test/apis.go b/tools/walletextension/test/apis.go deleted file mode 100644 index b21fa499a9..0000000000 --- a/tools/walletextension/test/apis.go +++ /dev/null @@ -1,189 +0,0 @@ -package test //nolint:typecheck - -import ( - "context" - "crypto/rand" - "encoding/json" - "fmt" - "math/big" - "time" - - "github.com/ten-protocol/go-ten/go/common/viewingkey" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ten-protocol/go-ten/go/common" - "github.com/ten-protocol/go-ten/go/enclave/vkhandler" - "github.com/ten-protocol/go-ten/go/responses" - "github.com/ten-protocol/go-ten/lib/gethfork/rpc" - - gethcommon "github.com/ethereum/go-ethereum/common" -) - -const ( - l2ChainIDHex = "0x309" - l2ChainIDDecimal = 443 - enclavePrivateKeyHex = "81acce9620f0adf1728cb8df7f6b8b8df857955eb9e8b7aed6ef8390c09fc207" -) - -// DummyAPI provides dummies for the RPC operations defined in the `eth_` namespace. For each sensitive RPC -// operation, it decrypts the parameters using the enclave's private key, then echoes them back to the caller encrypted -// with the viewing key set using the `setViewingKey` method, mimicking the privacy behaviour of the host. -type DummyAPI struct { - enclavePrivateKey *ecies.PrivateKey - viewingKey []byte - signature []byte - address *gethcommon.Address -} - -func NewDummyAPI() *DummyAPI { - enclavePrivateKey, err := crypto.HexToECDSA(enclavePrivateKeyHex) - if err != nil { - panic(fmt.Errorf("failed to create enclave private key. Cause: %w", err)) - } - - return &DummyAPI{ - enclavePrivateKey: ecies.ImportECDSA(enclavePrivateKey), - } -} - -// Determines which key the API will encrypt responses with. -func (api *DummyAPI) setViewingKey(address *gethcommon.Address, compressedVKKeyHexBytes, signature []byte) { - api.viewingKey = compressedVKKeyHexBytes - api.address = address - api.signature = signature -} - -func (api *DummyAPI) ChainId() (*hexutil.Big, error) { //nolint:stylecheck,revive - chainID, err := hexutil.DecodeBig(l2ChainIDHex) - return (*hexutil.Big)(chainID), err -} - -func (api *DummyAPI) Call(_ context.Context, encryptedParams common.EncryptedParamsCall) (*responses.EnclaveResponse, error) { - return api.reEncryptParams(encryptedParams) -} - -func (api *DummyAPI) GetBalance(_ context.Context, encryptedParams common.EncryptedParamsGetBalance) (*responses.EnclaveResponse, error) { - return api.reEncryptParams(encryptedParams) -} - -func (api *DummyAPI) GetTransactionByHash(_ context.Context, encryptedParams common.EncryptedParamsGetTxByHash) (*responses.EnclaveResponse, error) { - reEncryptParams, err := api.reEncryptParams(encryptedParams) - return reEncryptParams, err -} - -func (api *DummyAPI) GetTransactionCount(_ context.Context, encryptedParams common.EncryptedParamsGetTxCount) (*responses.EnclaveResponse, error) { - return api.reEncryptParams(encryptedParams) -} - -func (api *DummyAPI) GetTransactionReceipt(_ context.Context, encryptedParams common.EncryptedParamsGetTxReceipt) (*responses.EnclaveResponse, error) { - reEncryptParams, err := api.reEncryptParams(encryptedParams) - return reEncryptParams, err -} - -func (api *DummyAPI) SendRawTransaction(_ context.Context, encryptedParams common.EncryptedParamsSendRawTx) (*responses.EnclaveResponse, error) { - return api.reEncryptParams(encryptedParams) -} - -func (api *DummyAPI) EstimateGas(_ context.Context, encryptedParams common.EncryptedParamsEstimateGas, _ *rpc.BlockNumberOrHash) (*responses.EnclaveResponse, error) { - reEncryptParams, err := api.reEncryptParams(encryptedParams) - return reEncryptParams, err -} - -func (api *DummyAPI) Logs(ctx context.Context, encryptedParams common.EncryptedParamsLogSubscription) (*rpc.Subscription, error) { - // We decrypt and decode the params. - encodedParams, err := api.enclavePrivateKey.Decrypt(encryptedParams, nil, nil) - if err != nil { - return nil, fmt.Errorf("could not decrypt params with enclave private key. Cause: %w", err) - } - var params common.LogSubscription - if err = rlp.DecodeBytes(encodedParams, ¶ms); err != nil { - return nil, fmt.Errorf("could not decocde log subscription request from RLP. Cause: %w", err) - } - - // We set up the subscription. - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return nil, fmt.Errorf("creation of subscriptions is not supported") - } - subscription := notifier.CreateSubscription() - err = notifier.Notify(subscription.ID, common.IDAndEncLog{ - SubID: subscription.ID, - }) - if err != nil { - return nil, fmt.Errorf("could not send subscription ID to client on subscription %s", subscription.ID) - } - - // We emit a unique log every ten milliseconds. - go func() { - idx := big.NewInt(0) - for { - // We create the logs - logs := []*types.Log{{Topics: []gethcommon.Hash{ - // We set the topic from the filter as a topic in the response logs, so that we can check in the tests - // that we are a) decrypting the params correctly, and b) returning the logs with the correct contents - // via the wallet extension. - params.Filter.Topics[0][0], - // We also add an incrementing integer as a topic, so we can detect duplicate logs. - gethcommon.BigToHash(idx), - }}} - jsonLogs, err := json.Marshal(logs) - if err != nil { - panic("could not marshal log to JSON") - } - - // We send the encrypted log via the subscription. - pubkey, err := crypto.DecompressPubkey(api.viewingKey) - if err != nil { - panic("could not decompress Pub key") - } - - encryptedBytes, err := ecies.Encrypt(rand.Reader, ecies.ImportECDSAPublic(pubkey), jsonLogs, nil, nil) - if err != nil { - panic("could not encrypt logs with viewing key") - } - idAndEncLog := common.IDAndEncLog{ - SubID: subscription.ID, - EncLog: encryptedBytes, - } - notifier.Notify(subscription.ID, idAndEncLog) //nolint:errcheck - - time.Sleep(10 * time.Millisecond) - idx = idx.Add(idx, big.NewInt(1)) - } - }() - return subscription, nil -} - -func (api *DummyAPI) GetLogs(_ context.Context, encryptedParams common.EncryptedParamsGetLogs) (*responses.EnclaveResponse, error) { - reEncryptParams, err := api.reEncryptParams(encryptedParams) - return reEncryptParams, err -} - -func (api *DummyAPI) GetStorageAt(_ context.Context, encryptedParams common.EncryptedParamsSendRawTx) (*responses.EnclaveResponse, error) { - return api.reEncryptParams(encryptedParams) -} - -// Decrypts the params with the enclave key, and returns them encrypted with the viewing key set via `setViewingKey`. -func (api *DummyAPI) reEncryptParams(encryptedParams []byte) (*responses.EnclaveResponse, error) { - params, err := api.enclavePrivateKey.Decrypt(encryptedParams, nil, nil) - if err != nil { - return responses.AsEmptyResponse(), fmt.Errorf("could not decrypt params with enclave private key. Cause: %w", err) - } - - encryptor, err := vkhandler.VerifyViewingKey(&viewingkey.RPCSignedViewingKey{ - PublicKey: api.viewingKey, - SignatureWithAccountKey: api.signature, - SignatureType: viewingkey.Legacy, // todo - is this correct - }, l2ChainIDDecimal) - if err != nil { - return nil, fmt.Errorf("unable to create vk encryption for request - %w", err) - } - - strParams := string(params) - - return responses.AsEncryptedResponse(&strParams, encryptor), nil -} diff --git a/tools/walletextension/test/utils.go b/tools/walletextension/test/utils.go deleted file mode 100644 index 8e0f3001c6..0000000000 --- a/tools/walletextension/test/utils.go +++ /dev/null @@ -1,379 +0,0 @@ -package test - -import ( - "bytes" - "crypto/ecdsa" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "testing" - "time" - - "github.com/ethereum/go-ethereum/rpc" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/crypto" - "github.com/go-kit/kit/transport/http/jsonrpc" - "github.com/gorilla/websocket" - "github.com/ten-protocol/go-ten/go/common/log" - "github.com/ten-protocol/go-ten/go/common/viewingkey" - "github.com/ten-protocol/go-ten/tools/walletextension/common" - "github.com/ten-protocol/go-ten/tools/walletextension/config" - "github.com/ten-protocol/go-ten/tools/walletextension/container" - - gethcommon "github.com/ethereum/go-ethereum/common" - gethlog "github.com/ethereum/go-ethereum/log" - gethnode "github.com/ethereum/go-ethereum/node" - hostcontainer "github.com/ten-protocol/go-ten/go/host/container" -) - -const jsonID = "1" - -func createWalExtCfg(connectPort, wallHTTPPort, wallWSPort int) *config.Config { //nolint: unparam - testDBPath, err := os.CreateTemp("", "") - if err != nil { - panic("could not create persistence file for wallet extension tests") - } - return &config.Config{ - NodeRPCWebsocketAddress: fmt.Sprintf("localhost:%d", connectPort), - DBPathOverride: testDBPath.Name(), - WalletExtensionPortHTTP: wallHTTPPort, - WalletExtensionPortWS: wallWSPort, - DBType: "sqlite", - } -} - -func createWalExt(t *testing.T, walExtCfg *config.Config) func() error { - // todo (@ziga) - log somewhere else? - logger := log.New(log.WalletExtCmp, int(gethlog.LvlInfo), log.SysOut) - - wallExtContainer := container.NewWalletExtensionContainerFromConfig(*walExtCfg, logger) - go wallExtContainer.Start() //nolint: errcheck - - err := waitForEndpoint(fmt.Sprintf("http://%s:%d%s", walExtCfg.WalletExtensionHost, walExtCfg.WalletExtensionPortHTTP, common.PathReady)) - if err != nil { - t.Fatalf(err.Error()) - } - - return wallExtContainer.Stop -} - -// Creates an RPC layer that the wallet extension can connect to. Returns a handle to shut down the host. -func createDummyHost(t *testing.T, wsRPCPort int) (*DummyAPI, func() error) { //nolint: unparam - dummyAPI := NewDummyAPI() - cfg := gethnode.Config{ - WSHost: common.Localhost, - WSPort: wsRPCPort, - WSOrigins: []string{"*"}, - } - rpcServerNode, err := gethnode.New(&cfg) - rpcServerNode.RegisterAPIs([]rpc.API{ - { - Namespace: hostcontainer.APINamespaceObscuro, - Version: hostcontainer.APIVersion1, - Service: dummyAPI, - Public: true, - }, - { - Namespace: hostcontainer.APINamespaceEth, - Version: hostcontainer.APIVersion1, - Service: dummyAPI, - Public: true, - }, - }) - if err != nil { - t.Fatalf(fmt.Sprintf("could not create new client server. Cause: %s", err)) - } - t.Cleanup(func() { rpcServerNode.Close() }) - - err = rpcServerNode.Start() - if err != nil { - t.Fatalf(fmt.Sprintf("could not create new client server. Cause: %s", err)) - } - return dummyAPI, rpcServerNode.Close -} - -// Waits for the endpoint to be available. Times out after three seconds. -func waitForEndpoint(addr string) error { - retries := 30 - for i := 0; i < retries; i++ { - resp, err := http.Get(addr) //nolint:noctx,gosec - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - if err == nil { - return nil - } - time.Sleep(300 * time.Millisecond) - } - return fmt.Errorf("could not establish connection to wallet extension") -} - -// Makes an Ethereum JSON RPC request over HTTP and returns the response body. -func makeHTTPEthJSONReq(port int, method string, params interface{}) []byte { - reqBody := prepareRequestBody(method, params) - return makeRequestHTTP(fmt.Sprintf("http://%s:%d/v1/", common.Localhost, port), reqBody) -} - -// Makes an Ethereum JSON RPC request over HTTP to specific endpoint and returns the response body. -func makeHTTPEthJSONReqWithPath(port int, path string) []byte { - reqBody := prepareRequestBody("", "") - return makeRequestHTTP(fmt.Sprintf("http://%s:%d/%s", common.Localhost, port, path), reqBody) -} - -// Makes an Ethereum JSON RPC request over HTTP and returns the response body with userID query paremeter. -func makeHTTPEthJSONReqWithUserID(port int, method string, params interface{}, userID string) []byte { //nolint: unparam - reqBody := prepareRequestBody(method, params) - return makeRequestHTTP(fmt.Sprintf("http://%s:%d/v1/?token=%s", common.Localhost, port, userID), reqBody) -} - -// Makes an Ethereum JSON RPC request over websockets and returns the response body. -func makeWSEthJSONReq(port int, method string, params interface{}) ([]byte, *websocket.Conn) { - reqBody := prepareRequestBody(method, params) - return makeRequestWS(fmt.Sprintf("ws://%s:%d", common.Localhost, port), reqBody) -} - -func makeWSEthJSONReqWithConn(conn *websocket.Conn, method string, params interface{}) []byte { - reqBody := prepareRequestBody(method, params) - return issueRequestWS(conn, reqBody) -} - -func openWSConn(port int) (*websocket.Conn, error) { - conn, dialResp, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://%s:%d", common.Localhost, port), nil) - if dialResp != nil && dialResp.Body != nil { - defer dialResp.Body.Close() - } - if err != nil { - if conn != nil { - conn.Close() - } - panic(fmt.Errorf("received error response from wallet extension: %w", err)) - } - return conn, err -} - -// Formats a method and its parameters as a Ethereum JSON RPC request. -func prepareRequestBody(method string, params interface{}) []byte { - reqBodyBytes, err := json.Marshal(map[string]interface{}{ - common.JSONKeyRPCVersion: jsonrpc.Version, - common.JSONKeyMethod: method, - common.JSONKeyParams: params, - common.JSONKeyID: jsonID, - }) - if err != nil { - panic(fmt.Errorf("failed to prepare request body. Cause: %w", err)) - } - return reqBodyBytes -} - -// Generates a new account and registers it with the node. -func simulateViewingKeyRegister(t *testing.T, walletHTTPPort, walletWSPort int, useWS bool) (*gethcommon.Address, []byte, []byte) { - accountPrivateKey, err := crypto.GenerateKey() - if err != nil { - t.Fatalf(err.Error()) - } - accountAddr := crypto.PubkeyToAddress(accountPrivateKey.PublicKey) - - compressedHexVKBytes := generateViewingKey(walletHTTPPort, walletWSPort, accountAddr.String(), useWS) - mmSignature := signViewingKey(accountPrivateKey, compressedHexVKBytes) - submitViewingKey(accountAddr.String(), walletHTTPPort, walletWSPort, mmSignature, useWS) - - // transform the metamask signature to the geth compatible one - sigStr := hex.EncodeToString(mmSignature) - // and then we extract the signature bytes in the same way as the wallet extension - outputSig, err := hex.DecodeString(sigStr[2:]) - if err != nil { - panic(fmt.Errorf("failed to decode signature string: %w", err)) - } - // This same change is made in geth internals, for legacy reasons to be able to recover the address: - // https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - outputSig[64] -= 27 - - // keys are expected to be a []byte of hex string - vkPubKeyBytes, err := hex.DecodeString(string(compressedHexVKBytes)) - if err != nil { - panic(fmt.Errorf("unexpected hex string")) - } - - return &accountAddr, vkPubKeyBytes, outputSig -} - -// Generates a viewing key. -func generateViewingKey(wallHTTPPort, wallWSPort int, accountAddress string, useWS bool) []byte { - generateViewingKeyBodyBytes, err := json.Marshal(map[string]interface{}{ - common.JSONKeyAddress: accountAddress, - }) - if err != nil { - panic(err) - } - - if useWS { - viewingKeyBytes, _ := makeRequestWS(fmt.Sprintf("ws://%s:%d%s", common.Localhost, wallWSPort, common.PathGenerateViewingKey), generateViewingKeyBodyBytes) - return viewingKeyBytes - } - return makeRequestHTTP(fmt.Sprintf("http://%s:%d%s", common.Localhost, wallHTTPPort, common.PathGenerateViewingKey), generateViewingKeyBodyBytes) -} - -// Signs a viewing key like metamask -func signViewingKey(privateKey *ecdsa.PrivateKey, compressedHexVKBytes []byte) []byte { - // compressedHexVKBytes already has the key in the hex format - // it should be decoded back into raw bytes - viewingKey, err := hex.DecodeString(string(compressedHexVKBytes)) - if err != nil { - panic(err) - } - msgToSign := viewingkey.GenerateSignMessage(viewingKey) - signature, err := crypto.Sign(accounts.TextHash([]byte(msgToSign)), privateKey) - if err != nil { - panic(err) - } - - // We have to transform the V from 0/1 to 27/28, and add the leading "0". - signature[64] += 27 - signatureWithLeadBytes := append([]byte("0"), signature...) - - return signatureWithLeadBytes -} - -// Submits a viewing key. -func submitViewingKey(accountAddr string, wallHTTPPort, wallWSPort int, signature []byte, useWS bool) { - submitViewingKeyBodyBytes, err := json.Marshal(map[string]interface{}{ - common.JSONKeySignature: hex.EncodeToString(signature), - common.JSONKeyAddress: accountAddr, - }) - if err != nil { - panic(err) - } - - if useWS { - makeRequestWS(fmt.Sprintf("ws://%s:%d%s", common.Localhost, wallWSPort, common.PathSubmitViewingKey), submitViewingKeyBodyBytes) - } else { - makeRequestHTTP(fmt.Sprintf("http://%s:%d%s", common.Localhost, wallHTTPPort, common.PathSubmitViewingKey), submitViewingKeyBodyBytes) - } -} - -// Sends the body to the URL over HTTP, and returns the result. -func makeRequestHTTP(url string, body []byte) []byte { - generateViewingKeyBody := bytes.NewBuffer(body) - resp, err := http.Post(url, "application/json", generateViewingKeyBody) //nolint:noctx,gosec - if resp != nil && resp.Body != nil { - defer resp.Body.Close() - } - if err != nil { - panic(err) - } - viewingKey, err := io.ReadAll(resp.Body) - if err != nil { - panic(err) - } - return viewingKey -} - -// Sends the body to the URL over a websocket connection, and returns the result. -func makeRequestWS(url string, body []byte) ([]byte, *websocket.Conn) { - conn, dialResp, err := websocket.DefaultDialer.Dial(url, nil) - if dialResp != nil && dialResp.Body != nil { - defer dialResp.Body.Close() - } - if err != nil { - if conn != nil { - conn.Close() - } - panic(fmt.Errorf("received error response from wallet extension: %w", err)) - } - - return issueRequestWS(conn, body), conn -} - -// issues request on an existing ws connection -func issueRequestWS(conn *websocket.Conn, body []byte) []byte { - err := conn.WriteMessage(websocket.TextMessage, body) - if err != nil { - panic(err) - } - - _, reqResp, err := conn.ReadMessage() - if err != nil { - panic(err) - } - return reqResp -} - -// Reads messages from the connection for the provided duration, and returns the read messages. -//func readMessagesForDuration(t *testing.T, conn *websocket.Conn, duration time.Duration) [][]byte { -// // We set a timeout to kill the test, in case we never receive a log. -// timeout := time.AfterFunc(duration*3, func() { -// t.Fatalf("timed out waiting to receive a log via the subscription") -// }) -// defer timeout.Stop() -// -// var msgs [][]byte -// endTime := time.Now().Add(duration) -// for { -// _, msg, err := conn.ReadMessage() -// if err != nil { -// t.Fatalf("could not read message from websocket. Cause: %s", err) -// } -// msgs = append(msgs, msg) -// if time.Now().After(endTime) { -// return msgs -// } -// } -//} - -// Asserts that there are no duplicate logs in the provided list. -//func assertNoDupeLogs(t *testing.T, logsJSON [][]byte) { -// logCount := make(map[string]int) -// -// for _, logJSON := range logsJSON { -// // Check if the log is already in the logCount map. -// _, exist := logCount[string(logJSON)] -// if exist { -// logCount[string(logJSON)]++ // If it is, increase the count for that log by one. -// } else { -// logCount[string(logJSON)] = 1 // Otherwise, start a count for that log starting at one. -// } -// } -// -// for logJSON, count := range logCount { -// if count > 1 { -// t.Errorf("received duplicate log with body %s", logJSON) -// } -// } -//} - -// Checks that the response to a request is correctly formatted, and returns the result field. -func validateJSONResponse(t *testing.T, resp []byte) { - var respJSON map[string]interface{} - err := json.Unmarshal(resp, &respJSON) - if err != nil { - t.Fatalf("could not unmarshal response to JSON") - } - - id := respJSON[common.JSONKeyID] - jsonRPCVersion := respJSON[common.JSONKeyRPCVersion] - result := respJSON[common.JSONKeyResult] - - if id != jsonID { - t.Fatalf("response did not contain expected ID. Expected 1, got %s", id) - } - if jsonRPCVersion != jsonrpc.Version { - t.Fatalf("response did not contain expected RPC version. Expected 2.0, got %s", jsonRPCVersion) - } - if result == nil { - t.Fatalf("response did not contain `result` field") - } -} - -// Checks that the response to a subscription request is correctly formatted. -//func validateSubscriptionResponse(t *testing.T, resp []byte) { -// result := validateJSONResponse(t, resp) -// pattern := "0x.*" -// resultString, ok := result.(string) -// if !ok || !regexp.MustCompile(pattern).MatchString(resultString) { -// t.Fatalf("subscription response did not contain expected result. Expected pattern matching %s, got %s", pattern, resultString) -// } -//} diff --git a/tools/walletextension/test/wallet_extension_test.go b/tools/walletextension/test/wallet_extension_test.go deleted file mode 100644 index 1591c7fb31..0000000000 --- a/tools/walletextension/test/wallet_extension_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package test - -import ( - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/ten-protocol/go-ten/go/rpc" - "github.com/ten-protocol/go-ten/integration" - "github.com/ten-protocol/go-ten/tools/walletextension/accountmanager" - - gethcommon "github.com/ethereum/go-ethereum/common" -) - -const ( - errFailedDecrypt = "could not decrypt bytes with viewing key" - dummyParams = "dummyParams" - _hostWSPort = integration.StartPortWalletExtensionUnitTest -) - -type testHelper struct { - hostPort int - walletHTTPPort int - walletWSPort int - hostAPI *DummyAPI -} - -func TestWalletExtension(t *testing.T) { - t.Skip("Skipping because it is too flaky") - i := 0 - for name, test := range map[string]func(t *testing.T, testHelper *testHelper){ - "canInvokeSensitiveMethodsWithViewingKey": canInvokeSensitiveMethodsWithViewingKey, - "canInvokeNonSensitiveMethodsWithoutViewingKey": canInvokeNonSensitiveMethodsWithoutViewingKey, - "cannotInvokeSensitiveMethodsWithViewingKeyForAnotherAccount": cannotInvokeSensitiveMethodsWithViewingKeyForAnotherAccount, - "canInvokeSensitiveMethodsAfterSubmittingMultipleViewingKeys": canInvokeSensitiveMethodsAfterSubmittingMultipleViewingKeys, - "cannotSubscribeOverHTTP": cannotSubscribeOverHTTP, - "canRegisterViewingKeyAndMakeRequestsOverWebsockets": canRegisterViewingKeyAndMakeRequestsOverWebsockets, - } { - t.Run(name, func(t *testing.T) { - dummyAPI, shutDownHost := createDummyHost(t, _hostWSPort) - shutdownWallet := createWalExt(t, createWalExtCfg(_hostWSPort, _hostWSPort+1, _hostWSPort+2)) - - h := &testHelper{ - hostPort: _hostWSPort, - walletHTTPPort: _hostWSPort + 1, - walletWSPort: _hostWSPort + 2, - hostAPI: dummyAPI, - } - - test(t, h) - - assert.NoError(t, shutdownWallet()) - assert.NoError(t, shutDownHost()) - }) - i++ - } -} - -func canInvokeNonSensitiveMethodsWithoutViewingKey(t *testing.T, testHelper *testHelper) { - respBody, wsConnWE := makeWSEthJSONReq(testHelper.hostPort, rpc.ChainID, []interface{}{}) - defer wsConnWE.Close() - - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), l2ChainIDHex) { - t.Fatalf("expected response containing '%s', got '%s'", l2ChainIDHex, string(respBody)) - } -} - -func canInvokeSensitiveMethodsWithViewingKey(t *testing.T, testHelper *testHelper) { - address, vkPubKeyBytes, signature := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - testHelper.hostAPI.setViewingKey(address, vkPubKeyBytes, signature) - - for _, method := range rpc.SensitiveMethods { - // Subscriptions have to be tested separately, as they return results differently. - if method == rpc.Subscribe { - continue - } - - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, method, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } - } -} - -var ErrInvalidAddressSignature = fmt.Errorf("invalid viewing key signature for requested address") - -func cannotInvokeSensitiveMethodsWithViewingKeyForAnotherAccount(t *testing.T, testHelper *testHelper) { - addr1, _, _ := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - - _, hexVKPubKeyBytes2, signature2 := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - - // We set the API to decrypt with a key different to the viewing key we just submitted. - testHelper.hostAPI.setViewingKey(addr1, hexVKPubKeyBytes2, signature2) - - for _, method := range rpc.SensitiveMethods { - // Subscriptions have to be tested separately, as they return results differently. - if method == rpc.Subscribe { - continue - } - - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, method, []interface{}{map[string]interface{}{}}) - if !strings.Contains(string(respBody), ErrInvalidAddressSignature.Error()) { - t.Fatalf("expected response containing '%s', got '%s'", errFailedDecrypt, string(respBody)) - } - } -} - -func canInvokeSensitiveMethodsAfterSubmittingMultipleViewingKeys(t *testing.T, testHelper *testHelper) { - type tempVKHolder struct { - address *gethcommon.Address - hexVKPubKey []byte - signature []byte - } - // We submit viewing keys for ten arbitrary accounts. - var viewingKeys []tempVKHolder - - for i := 0; i < 10; i++ { - address, hexVKPubKeyBytes, signature := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, false) - viewingKeys = append(viewingKeys, tempVKHolder{ - address: address, - hexVKPubKey: hexVKPubKeyBytes, - signature: signature, - }) - } - - // We set the API to decrypt with an arbitrary key from the list we just generated. - arbitraryViewingKey := viewingKeys[len(viewingKeys)/2] - testHelper.hostAPI.setViewingKey(arbitraryViewingKey.address, arbitraryViewingKey.hexVKPubKey, arbitraryViewingKey.signature) - - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, rpc.GetBalance, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } -} - -func cannotSubscribeOverHTTP(t *testing.T, testHelper *testHelper) { - respBody := makeHTTPEthJSONReq(testHelper.walletHTTPPort, rpc.Subscribe, []interface{}{rpc.SubscriptionTypeLogs}) - - if string(respBody) != "received an eth_subscribe request but the connection does not support subscriptions" { - t.Fatalf("unexpected response %s", string(respBody)) - } -} - -func canRegisterViewingKeyAndMakeRequestsOverWebsockets(t *testing.T, testHelper *testHelper) { - address, hexVKPubKeyBytes, signature := simulateViewingKeyRegister(t, testHelper.walletHTTPPort, testHelper.walletWSPort, true) - testHelper.hostAPI.setViewingKey(address, hexVKPubKeyBytes, signature) - - conn, err := openWSConn(testHelper.walletWSPort) - if err != nil { - t.Fatal(err) - } - - respBody := makeWSEthJSONReqWithConn(conn, rpc.GetTransactionReceipt, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } - - err = conn.Close() - if err != nil { - t.Fatal(err) - } -} - -func TestCannotInvokeSensitiveMethodsWithoutViewingKey(t *testing.T) { - walletHTTPPort := _hostWSPort + 1 - walletWSPort := _hostWSPort + 2 - - _, shutdownHost := createDummyHost(t, _hostWSPort) - defer shutdownHost() //nolint: errcheck - - shutdownWallet := createWalExt(t, createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort)) - defer shutdownWallet() //nolint: errcheck - - conn, err := openWSConn(walletWSPort) - if err != nil { - t.Fatal(err) - } - - for _, method := range rpc.SensitiveMethods { - // We use a websocket request because one of the sensitive methods, eth_subscribe, requires it. - respBody := makeWSEthJSONReqWithConn(conn, method, []interface{}{}) - if !strings.Contains(string(respBody), fmt.Sprintf(accountmanager.ErrNoViewingKey, method)) { - t.Fatalf("expected response containing '%s', got '%s'", fmt.Sprintf(accountmanager.ErrNoViewingKey, method), string(respBody)) - } - } - err = conn.Close() - if err != nil { - t.Fatal(err) - } -} - -func TestKeysAreReloadedWhenWalletExtensionRestarts(t *testing.T) { - walletHTTPPort := _hostWSPort + 1 - walletWSPort := _hostWSPort + 2 - - dummyAPI, shutdownHost := createDummyHost(t, _hostWSPort) - defer shutdownHost() //nolint: errcheck - walExtCfg := createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort) - shutdownWallet := createWalExt(t, walExtCfg) - - addr, viewingKeyBytes, signature := simulateViewingKeyRegister(t, walletHTTPPort, walletWSPort, false) - dummyAPI.setViewingKey(addr, viewingKeyBytes, signature) - - // We shut down the wallet extension and restart it with the same config, forcing the viewing keys to be reloaded. - err := shutdownWallet() - assert.NoError(t, err) - - shutdownWallet = createWalExt(t, walExtCfg) - defer shutdownWallet() //nolint: errcheck - - respBody := makeHTTPEthJSONReq(walletHTTPPort, rpc.GetBalance, []interface{}{map[string]interface{}{"params": dummyParams}}) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), dummyParams) { - t.Fatalf("expected response containing '%s', got '%s'", dummyParams, string(respBody)) - } -} - -// TODO (@ziga) - move those tests to integration Obscuro Gateway tests -// currently this test if failing, because we need proper registration in the test -//func TestCanSubscribeForLogsOverWebsockets(t *testing.T) { -// hostPort := _hostWSPort + _testOffset*9 -// walletHTTPPort := hostPort + 1 -// walletWSPort := hostPort + 2 -// -// dummyHash := gethcommon.BigToHash(big.NewInt(1234)) -// -// dummyAPI, shutdownHost := createDummyHost(t, hostPort) -// defer shutdownHost() //nolint: errcheck -// shutdownWallet := createWalExt(t, createWalExtCfg(hostPort, walletHTTPPort, walletWSPort)) -// defer shutdownWallet() //nolint: errcheck -// -// dummyAPI.setViewingKey(simulateViewingKeyRegister(t, walletHTTPPort, walletWSPort, false)) -// -// filter := common.FilterCriteriaJSON{Topics: []interface{}{dummyHash}} -// resp, conn := makeWSEthJSONReq(walletWSPort, rpc.Subscribe, []interface{}{rpc.SubscriptionTypeLogs, filter}) -// validateSubscriptionResponse(t, resp) -// -// logsJSON := readMessagesForDuration(t, conn, time.Second) -// -// // We check we received enough logs. -// if len(logsJSON) < 50 { -// t.Errorf("expected to receive at least 50 logs, only received %d", len(logsJSON)) -// } -// -// // We check that none of the logs were duplicates (i.e. were sent twice). -// assertNoDupeLogs(t, logsJSON) -// -// // We validate that each log contains the correct topic. -// for _, logJSON := range logsJSON { -// var logResp map[string]interface{} -// err := json.Unmarshal(logJSON, &logResp) -// if err != nil { -// t.Fatalf("could not unmarshal received log from JSON") -// } -// -// // We extract the topic from the received logs. The API should have set this based on the filter we passed when subscribing. -// logMap := logResp[wecommon.JSONKeyParams].(map[string]interface{})[wecommon.JSONKeyResult].(map[string]interface{}) -// firstLogTopic := logMap[jsonKeyTopics].([]interface{})[0].(string) -// -// if firstLogTopic != dummyHash.Hex() { -// t.Errorf("expected first topic to be '%s', got '%s'", dummyHash.Hex(), firstLogTopic) -// } -// } -//} - -func TestGetStorageAtForReturningUserID(t *testing.T) { - walletHTTPPort := _hostWSPort + 1 - walletWSPort := _hostWSPort + 2 - - createDummyHost(t, _hostWSPort) - walExtCfg := createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort) - createWalExtCfg(_hostWSPort, walletHTTPPort, walletWSPort) - createWalExt(t, walExtCfg) - - // create userID - respJoin := makeHTTPEthJSONReqWithPath(walletHTTPPort, "v1/join") - userID := string(respJoin) - - // make a request to GetStorageAt with correct parameters to get userID that exists in the database - respBody := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"getUserID", "0", nil}, userID) - validateJSONResponse(t, respBody) - - if !strings.Contains(string(respBody), userID) { - t.Fatalf("expected response containing '%s', got '%s'", userID, string(respBody)) - } - - // make a request to GetStorageAt with correct parameters, but userID that is not present in the database - invalidUserID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - respBody2 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"getUserID", "0", nil}, invalidUserID) - - if !strings.Contains(string(respBody2), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") { - t.Fatalf("expected method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found, got '%s'", string(respBody2)) - } - - // make a request to GetStorageAt with userID that is in the database, but wrong parameters - respBody3 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"abc", "0", nil}, userID) - if strings.Contains(string(respBody3), userID) { - t.Fatalf("expected response not containing userID as the parameters are wrong ") - } - - // make a request with wrong rpcMethod - respBody4 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetBalance, []interface{}{"getUserID", "0", nil}, userID) - if strings.Contains(string(respBody4), userID) { - t.Fatalf("expected response not containing userID as the parameters are wrong ") - } -} diff --git a/tools/walletextension/wallet_extension.go b/tools/walletextension/wallet_extension.go index ac01209f96..64ceffe2d7 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -37,7 +37,6 @@ type WalletExtension struct { hostAddrHTTP string // The HTTP address on which the Ten host can be reached hostAddrWS string // The WS address on which the Ten host can be reached userAccountManager *useraccountmanager.UserAccountManager - unsignedVKs map[gethcommon.Address]*viewingkey.ViewingKey // Map temporarily holding VKs that have been generated but not yet signed storage storage.Storage logger gethlog.Logger fileLogger gethlog.Logger @@ -75,7 +74,6 @@ func New( hostAddrHTTP: hostAddrHTTP, hostAddrWS: hostAddrWS, userAccountManager: userAccountManager, - unsignedVKs: map[gethcommon.Address]*viewingkey.ViewingKey{}, storage: storage, logger: logger, fileLogger: newFileLogger, @@ -188,76 +186,6 @@ func (w *WalletExtension) ProxyEthRequest(request *common.RPCRequest, conn userc return response, nil } -// GenerateViewingKey generates the user viewing key and waits for signature -func (w *WalletExtension) GenerateViewingKey(addr gethcommon.Address) (string, error) { - w.fileLogger.Info(fmt.Sprintf("Requested to generate viewing key for address(old way): %s", addr.Hex())) - viewingKeyPrivate, err := crypto.GenerateKey() - if err != nil { - return "", fmt.Errorf("unable to generate a new keypair - %w", err) - } - - viewingPublicKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) - viewingPrivateKeyEcies := ecies.ImportECDSA(viewingKeyPrivate) - - w.unsignedVKs[addr] = &viewingkey.ViewingKey{ - Account: &addr, - PrivateKey: viewingPrivateKeyEcies, - PublicKey: viewingPublicKeyBytes, - SignatureWithAccountKey: nil, // we await a signature from the user before we can set up the EncRPCClient - SignatureType: viewingkey.Legacy, - } - - // compress the viewing key and convert it to hex string ( this is what Metamask signs) - viewingKeyBytes := crypto.CompressPubkey(&viewingKeyPrivate.PublicKey) - return hex.EncodeToString(viewingKeyBytes), nil -} - -// SubmitViewingKey checks the signed viewing key and stores it -func (w *WalletExtension) SubmitViewingKey(address gethcommon.Address, signature []byte) error { - w.fileLogger.Info(fmt.Sprintf("Requested to submit a viewing key (old way): %s", address.Hex())) - vk, found := w.unsignedVKs[address] - if !found { - return fmt.Errorf(fmt.Sprintf("no viewing key found to sign for acc=%s, please call %s to generate key before sending signature", address, common.PathGenerateViewingKey)) - } - - // We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able - // to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459 - signature[64] -= 27 - - vk.SignatureWithAccountKey = signature - - err := w.storage.AddUser([]byte(common.DefaultUser), crypto.FromECDSA(vk.PrivateKey.ExportECDSA())) - if err != nil { - return fmt.Errorf("error saving user: %s", common.DefaultUser) - } - // create an encrypted RPC client with the signed VK and register it with the enclave - // todo (@ziga) - Create the clients lazily, to reduce connections to the host. - client, err := rpc.NewEncNetworkClient(w.hostAddrHTTP, vk, w.logger) - if err != nil { - return fmt.Errorf("failed to create encrypted RPC client for account %s - %w", address, err) - } - defaultAccountManager, err := w.userAccountManager.GetUserAccountManager(hex.EncodeToString([]byte(common.DefaultUser))) - if err != nil { - return fmt.Errorf(fmt.Sprintf("error getting default user account manager: %s", err)) - } - - defaultAccountManager.AddClient(address, client) - - err = w.storage.AddAccount([]byte(common.DefaultUser), vk.Account.Bytes(), vk.SignatureWithAccountKey, viewingkey.Legacy) - if err != nil { - return fmt.Errorf("error saving account %s for user %s", vk.Account.Hex(), common.DefaultUser) - } - - if err != nil { - return fmt.Errorf("error saving viewing key to the database: %w", err) - } - - // finally we remove the VK from the pending 'unsigned VKs' map now the client has been created - delete(w.unsignedVKs, address) - - return nil -} - // GenerateAndStoreNewUser generates new key-pair and userID, stores it in the database and returns hex encoded userID and error func (w *WalletExtension) GenerateAndStoreNewUser() (string, error) { requestStartTime := time.Now()