diff --git a/tools/walletextension/test/apis.go b/tools/walletextension/test/apis.go deleted file mode 100644 index 8d0a0a5bb4..0000000000 --- a/tools/walletextension/test/apis.go +++ /dev/null @@ -1,189 +0,0 @@ -package test - -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/ethereum/go-ethereum/rpc" - "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" - - 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 7c5d2420cd..0000000000 --- a/tools/walletextension/test/utils.go +++ /dev/null @@ -1,378 +0,0 @@ -package test - -import ( - "bytes" - "crypto/ecdsa" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "testing" - "time" - - "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" - gethrpc "github.com/ethereum/go-ethereum/rpc" - 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([]gethrpc.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 ") - } -}