From 027274e8bf8a2ba19133c3f2c87043e3a0115c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Kokelj?= Date: Thu, 30 Nov 2023 10:49:32 +0100 Subject: [PATCH] remove wallet extension endpoints --- contracts/hardhat.config.ts | 2 +- contracts/tasks/obscuro-deploy.ts | 4 +- contracts/tasks/ten-gateway.ts | 235 +++++++++++ contracts/tasks/wallet-extension.ts | 203 ---------- integration/obscurogateway/tengateway_test.go | 184 +++++++-- tools/walletextension/api/routes.go | 88 +--- tools/walletextension/api/utils.go | 2 +- tools/walletextension/common/constants.go | 4 - tools/walletextension/config/config.go | 6 +- .../container/walletextension_container.go | 47 +-- tools/walletextension/main/cli.go | 6 +- tools/walletextension/main/main.go | 10 +- tools/walletextension/test/apis.go | 183 --------- tools/walletextension/test/utils.go | 378 ------------------ .../test/wallet_extension_test.go | 316 --------------- tools/walletextension/wallet_extension.go | 80 ---- 16 files changed, 410 insertions(+), 1338 deletions(-) create mode 100644 contracts/tasks/ten-gateway.ts delete mode 100644 contracts/tasks/wallet-extension.ts delete mode 100644 tools/walletextension/test/apis.go delete mode 100644 tools/walletextension/test/utils.go delete mode 100644 tools/walletextension/test/wallet_extension_test.go diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 18011680fe..cec259f128 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -9,7 +9,7 @@ import 'hardhat-deploy'; // Hardhat ignore warnings plugin - https://www.npmjs.com/package/hardhat-ignore-warnings import 'hardhat-ignore-warnings'; -import './tasks/wallet-extension'; +import './tasks/ten-gateway'; import * as abigen from './tasks/abigen'; import './tasks/obscuro-deploy'; diff --git a/contracts/tasks/obscuro-deploy.ts b/contracts/tasks/obscuro-deploy.ts index 2be6667279..71c11c2357 100644 --- a/contracts/tasks/obscuro-deploy.ts +++ b/contracts/tasks/obscuro-deploy.ts @@ -16,7 +16,7 @@ task("obscuro:deploy", "Prepares for deploying.") // Start a wallet extension as a child process // This process is auto signaled to terminate when this process dies - await hre.run("obscuro:wallet-extension:start:local", { + await hre.run("ten:gateway:start:local", { rpcUrl : rpcURL, withStdOut: true }); @@ -26,7 +26,7 @@ task("obscuro:deploy", "Prepares for deploying.") // Automatically provision a viewing key for the deployer account const { deployer } = await hre.getNamedAccounts(); - await hre.run('obscuro:wallet-extension:add-key', {address: deployer}); + await hre.run('ten:gateway:join-authenticate', {address: deployer}); // Execute the deploy task provided by the HH deploy plugin. await hre.run('deploy'); diff --git a/contracts/tasks/ten-gateway.ts b/contracts/tasks/ten-gateway.ts new file mode 100644 index 0000000000..ac5630bb98 --- /dev/null +++ b/contracts/tasks/ten-gateway.ts @@ -0,0 +1,235 @@ +import { task } from "hardhat/config"; + +import * as url from 'node:url'; + +import { spawn } from 'node:child_process'; +import * as path from "path"; +// @ts-ignore +import http from 'http'; + + +task("ten:gateway:start:local") + .addFlag('withStdOut') + .addParam('rpcUrl', "Node rpc endpoint where the Ten gateway should connect to.") + .addOptionalParam('port', "Port that the Ten gateway will open for incoming requests.", "3001") + .setAction(async function(args, hre) { + const nodeUrl = url.parse(args.rpcUrl); + const tenGatewayPath = path.resolve(hre.config.paths.root, "../tools/walletextension/bin/wallet_extension_linux"); + const weProcess = spawn(tenGatewayPath, [ + `-portWS`, `${args.port}`, + `-nodeHost`, `${nodeUrl.hostname}`, + `-nodePortWS`, `${nodeUrl.port}` + ]); + + console.log("Waiting for Ten gateway to start"); + await new Promise((resolve, fail)=>{ + const timeoutSchedule = setTimeout(fail, 60_000); + weProcess.stdout.on('data', (data: string) => { + if (args.withStdOut) { + console.log(data.toString()); + } + + if (data.includes("Ten Gateway started")) { + clearTimeout(timeoutSchedule); + resolve(true) + } + }); + + weProcess.stderr.on('data', (data: string) => { + console.log(data.toString()); + }); + }); + + console.log("Ten gateway started successfully"); + return weProcess; + }); + + +// This is not to be used for internal development. It is targeted at external devs when the obscuro hh plugin is finished! +task("ten:gateway:start:docker", "Starts up the Ten gateway docker container.") + .addFlag('wait') + .addParam('dockerImage', + 'The docker image to use for the Ten gateway', + 'testnetobscuronet.azurecr.io/obscuronet/walletextension') // TODO (@ziga) - change after renaming it + .addParam('rpcUrl', "Which network to pick the node connection info from?") + .setAction(async function(args, hre) { + const docker = new dockerApi.Docker({ socketPath: '/var/run/docker.sock' }); + + const parsedUrl = url.parse(args.rpcUrl) + + const container = await docker.container.create({ + Image: args.dockerImage, + Cmd: [ + "--port=3000", + "--portWS=3001", + `--nodeHost=${parsedUrl.hostname}`, + `--nodePortWS=${parsedUrl.port}` + ], + ExposedPorts: { "3000/tcp": {}, "3001/tcp": {}, "3000/udp": {}, "3001/udp": {} }, + PortBindings: { "3000/tcp": [{ "HostPort": "3000" }], "3001/tcp": [{ "HostPort": "3001" }] } + }) + + + process.on('SIGINT', ()=>{ + container.stop(); + }) + + await container.start(); + + const stream: any = await container.logs({ + follow: true, + stdout: true, + stderr: true + }) + + console.log(`\nTen gateway{ ${container.id.slice(0, 5)} } %>\n`); + const startupPromise = new Promise((resolveInner)=> { + stream.on('data', (msg: any)=> { + const message = msg.toString(); + + console.log(message); + if(message.includes("Ten Gateway started")) { + console.log(`Wallet - success!`); + resolveInner(true); + } + }); + + setTimeout(resolveInner, 20_000); + }); + + await startupPromise; + console.log("\n[ . . . ]\n"); + + + if (args.wait) { + await container.wait(); + } + }); + +// This is not to be used for internal development. It is targeted at external devs when the obscuro hh plugin is finished! +task("ten:gateway:stop:docker", "Stops the docker container with matching image name.") + .addParam('dockerImage', + 'The docker image to use for the Ten gateway', + 'testnetobscuronet.azurecr.io/obscuronet/walletextension') // TODO @ziga change when renaming wallet extensions + .setAction(async function(args, hre) { + const docker = new dockerApi.Docker({ socketPath: '/var/run/docker.sock' }); + const containers = await docker.container.list(); + + const container = containers.find((c)=> { + const data : any = c.data; + return data.Image == 'testnetobscuronet.azurecr.io/obscuronet/walletextension' + }) + + await container?.stop() + }); + +const { URL } = require('url'); + +task("ten:gateway:join-authenticate", "Joins and authenticates the gateway for a specific address") + .addParam("address", "The address which to use for authentication") + .setAction(async function(args, hre) { + async function joinGateway(url = 'http://127.0.0.1:3000/v1/join') { + return new Promise((resolve, reject) => { + http.get(url, (response) => { + if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) { + // Resolve the new location against the original URL + const newUrl = new URL(response.headers.location, url).toString(); + return resolve(joinGateway(newUrl)); + } + + if (response.statusCode !== 200) { + return reject(new Error(`Server responded with status code: ${response.statusCode}`)); + } + + let chunks = []; + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks).toString())); + }).on('error', reject); + }); + } + + + // authenticateWithGateway function to authenticate using the token + async function authenticateWithGateway(encryptionToken) { + const typedData = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + ], + Authentication: [ + { name: "Encryption Token", type: "address" }, + ], + }, + primaryType: "Authentication", + domain: { + name: "Ten", + version: "1.0", + chainId: 443, // TODO @ziga - can we get this from some config in typescript? + }, + message: { + "Encryption Token": "0x"+encryptionToken + }, + }; + + const messageData = JSON.stringify(typedData); + const signer = await hre.ethers.getSigner(args.address); + + const signature = await signer.provider.send('eth_signTypedData_v4', [ + signer.address, + messageData + ]); + + const signedData = { "signature": signature, "address": args.address }; + + // Call the authenticate function with the new URL and signed data + const url = `http://127.0.0.1:3000/v1/authenticate?token=${encryptionToken}`; + return authenticate(url, signedData); + } + + // authenticate function to make the POST request + async function authenticate(url, signedData) { + return new Promise((resolve, reject) => { + const makeRequest = (url) => { + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + const req = http.request(url, options, (response) => { + if (response.statusCode === 301 || response.statusCode === 302) { + // Handle redirect + const newUrl = new URL(response.headers.location, url).toString(); + makeRequest(newUrl); // Resend POST request to new URL + } else if (response.statusCode < 200 || response.statusCode >= 300) { + reject(new Error(`Server responded with status code: ${response.statusCode}`)); + } else { + let chunks = []; + response.on('data', (chunk) => chunks.push(chunk)); + response.on('end', () => resolve(Buffer.concat(chunks).toString())); + } + }); + + req.on('error', reject); + req.write(JSON.stringify(signedData)); + req.end(); + }; + + makeRequest(url); + }); + } + + + try { + let encryptionToken = await joinGateway(); + console.log("Encryption token: ", encryptionToken); + + let authenticationResult = await authenticateWithGateway(encryptionToken); + console.log("Authentication result: ", authenticationResult); + } catch (error) { + console.error("Error: ", error); + } + }); diff --git a/contracts/tasks/wallet-extension.ts b/contracts/tasks/wallet-extension.ts deleted file mode 100644 index 82f0b927dc..0000000000 --- a/contracts/tasks/wallet-extension.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { task } from "hardhat/config"; - -import * as dockerApi from 'node-docker-api'; -import { on } from 'process'; - -import * as url from 'node:url'; - -import { spawn } from 'node:child_process'; -import * as path from "path"; -import http from 'http'; - - - - -task("obscuro:wallet-extension:start:local") -.addFlag('withStdOut') -.addParam('rpcUrl', "Node rpc endpoint where the wallet extension should connect to.") -.addOptionalParam('port', "Port that the wallet extension will open for incoming requests.", "3001") -.setAction(async function(args, hre) { - const nodeUrl = url.parse(args.rpcUrl) - -/* if (args.withStdOut) { - console.log(`Node url = ${JSON.stringify(nodeUrl, null, " ")}`); - }*/ - - const walletExtensionPath = path.resolve(hre.config.paths.root, "../tools/walletextension/bin/wallet_extension_linux"); - const weProcess = spawn(walletExtensionPath, [ - `-portWS`, `${args.port}`, - `-nodeHost`, `${nodeUrl.hostname}`, - `-nodePortWS`, `${nodeUrl.port}` - ]); - - console.log("Waiting for Wallet Extension to start"); - await new Promise((resolve, fail)=>{ - const timeoutSchedule = setTimeout(fail, 60_000); - weProcess.stdout.on('data', (data: string) => { - if (args.withStdOut) { - console.log(data.toString()); - } - - if (data.includes("Wallet extension started")) { - clearTimeout(timeoutSchedule); - resolve(true) - } - }); - - weProcess.stderr.on('data', (data: string) => { - console.log(data.toString()); - }); - }); - - console.log("Wallet Exension started successfully"); - return weProcess; -}); - -// This is not to be used for internal development. It is targeted at external devs when the obscuro hh plugin is finished! -task("obscuro:wallet-extension:start:docker", "Starts up the wallet extension docker container.") -.addFlag('wait') -.addParam('dockerImage', - 'The docker image to use for wallet extension', - 'testnetobscuronet.azurecr.io/obscuronet/walletextension') -.addParam('rpcUrl', "Which network to pick the node connection info from?") -.setAction(async function(args, hre) { - const docker = new dockerApi.Docker({ socketPath: '/var/run/docker.sock' }); - - const parsedUrl = url.parse(args.rpcUrl) - - const container = await docker.container.create({ - Image: args.dockerImage, - Cmd: [ - "--port=3000", - "--portWS=3001", - `--nodeHost=${parsedUrl.hostname}`, - `--nodePortWS=${parsedUrl.port}` - ], - ExposedPorts: { "3000/tcp": {}, "3001/tcp": {}, "3000/udp": {}, "3001/udp": {} }, - PortBindings: { "3000/tcp": [{ "HostPort": "3000" }], "3001/tcp": [{ "HostPort": "3001" }] } - }) - - - process.on('SIGINT', ()=>{ - container.stop(); - }) - - await container.start(); - - const stream: any = await container.logs({ - follow: true, - stdout: true, - stderr: true - }) - - console.log(`\nWallet Extension{ ${container.id.slice(0, 5)} } %>\n`); - const startupPromise = new Promise((resolveInner)=> { - stream.on('data', (msg: any)=> { - const message = msg.toString(); - - console.log(message); - if(message.includes("Wallet extension started")) { - console.log(`Wallet - success!`); - resolveInner(true); - } - }); - - setTimeout(resolveInner, 20_000); - }); - - await startupPromise; - console.log("\n[ . . . ]\n"); - - - if (args.wait) { - await container.wait(); - } -}); - -// This is not to be used for internal development. It is targeted at external devs when the obscuro hh plugin is finished! -task("obscuro:wallet-extension:stop:docker", "Stops the docker container with matching image name.") -.addParam('dockerImage', - 'The docker image to use for wallet extension', - 'testnetobscuronet.azurecr.io/obscuronet/walletextension') -.setAction(async function(args, hre) { - const docker = new dockerApi.Docker({ socketPath: '/var/run/docker.sock' }); - const containers = await docker.container.list(); - - const container = containers.find((c)=> { - const data : any = c.data; - return data.Image == 'testnetobscuronet.azurecr.io/obscuronet/walletextension' - }) - - await container?.stop() -}); - -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 901241036e..b8c60b624a 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" + "github.com/ethereum/go-ethereum" wecommon "github.com/ten-protocol/go-ten/tools/walletextension/common" @@ -55,9 +59,9 @@ func TestTenGateway(t *testing.T) { createTenNetwork(t, startPort) tenGatewayConf := config.Config{ - WalletExtensionHost: "127.0.0.1", - WalletExtensionPortHTTP: startPort + integration.DefaultTenGatewayHTTPPortOffset, - WalletExtensionPortWS: startPort + integration.DefaultTenGatewayWSPortOffset, + TenGatewayHost: "127.0.0.1", + TenGatewayPortHTTP: startPort + integration.DefaultTenGatewayHTTPPortOffset, + TenGatewayPortWS: startPort + integration.DefaultTenGatewayWSPortOffset, NodeRPCHTTPAddress: fmt.Sprintf("127.0.0.1:%d", startPort+integration.DefaultHostRPCHTTPOffset), NodeRPCWebsocketAddress: fmt.Sprintf("127.0.0.1:%d", startPort+integration.DefaultHostRPCWSOffset), LogPath: "sys_out", @@ -66,7 +70,7 @@ func TestTenGateway(t *testing.T) { TenChainID: 443, } - tenGwContainer := container.NewWalletExtensionContainerFromConfig(tenGatewayConf, testlog.Logger()) + tenGwContainer := container.NewTenGatewayContainerFromConfig(tenGatewayConf, testlog.Logger()) go func() { err := tenGwContainer.Start() if err != nil { @@ -78,8 +82,8 @@ func TestTenGateway(t *testing.T) { time.Sleep(5 * time.Second) // make sure the server is ready to receive requests - httpURL := fmt.Sprintf("http://%s:%d", tenGatewayConf.WalletExtensionHost, tenGatewayConf.WalletExtensionPortHTTP) - wsURL := fmt.Sprintf("ws://%s:%d", tenGatewayConf.WalletExtensionHost, tenGatewayConf.WalletExtensionPortWS) + httpURL := fmt.Sprintf("http://%s:%d", tenGatewayConf.TenGatewayHost, tenGatewayConf.TenGatewayPortHTTP) + wsURL := fmt.Sprintf("ws://%s:%d", tenGatewayConf.TenGatewayHost, tenGatewayConf.TenGatewayPortWS) // make sure the server is ready to receive requests err := waitServerIsReady(httpURL) @@ -91,11 +95,14 @@ func TestTenGateway(t *testing.T) { // run the tests against the exis for name, test := range map[string]func(*testing.T, string, string, wallet.Wallet){ //"testAreTxsMinted": testAreTxsMinted, this breaks the other tests bc, enable once concurency issues are fixed - "testErrorHandling": testErrorHandling, - "testMultipleAccountsSubscription": testMultipleAccountsSubscription, - "testErrorsRevertedArePassed": testErrorsRevertedArePassed, - "testUnsubscribe": testUnsubscribe, - "testClosingConnectionWhileSubscribed": testClosingConnectionWhileSubscribed, + "testErrorHandling": testErrorHandling, + "testMultipleAccountsSubscription": testMultipleAccountsSubscription, + "testErrorsRevertedArePassed": testErrorsRevertedArePassed, + "testUnsubscribe": testUnsubscribe, + "testClosingConnectionWhileSubscribed": testClosingConnectionWhileSubscribed, + "testInvokingSensitiveMethodsWithAndWithoutViewingKeys": testInvokingSensitiveMethodsWithAndWithoutViewingKeys, + "testInvokeNonSensitiveMethod": testInvokeNonSensitiveMethod, + "testGetStorageAtForReturningUserID": testGetStorageAtForReturningUserID, } { t.Run(name, func(t *testing.T) { test(t, httpURL, wsURL, w) @@ -109,6 +116,127 @@ func TestTenGateway(t *testing.T) { assert.NoError(t, err) } +func testInvokingSensitiveMethodsWithAndWithoutViewingKeys(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { + user, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) + require.NoError(t, err) + unregisteredUser, err := NewUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + require.NoError(t, err) + err = user.RegisterAccounts() + require.NoError(t, err) + + dummyParams := "dummyParams" + for _, method := range rpc.SensitiveMethods { + // Subscriptions are tested separately + if method == rpc.Subscribe { + continue + } + + // handle eth_sendRawTransaction function differently, because it requires different params + if method == "eth_sendRawTransaction" { + respBody := makeHTTPEthJSONReq(httpURL, method, user.tgClient.UserID(), []interface{}{"test"}) + if strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", method)) { + t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", method) + } + + respBody = makeHTTPEthJSONReq(httpURL, method, unregisteredUser.tgClient.UserID(), []interface{}{"test"}) + if !strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", method)) { + t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", method) + } + continue + } + + // Call sensitive method with a client that is registered. + // We expect the response not to contain: "cannot be called with an unauthorised client" + respBody := makeHTTPEthJSONReq(httpURL, method, user.tgClient.UserID(), []interface{}{map[string]interface{}{"params": dummyParams}}) + if strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", method)) { + t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", method) + } + + // Call sensitive method with a client that is not registered. We expect an error message: "cannot be called with an unauthorised client" + respBody = makeHTTPEthJSONReq(httpURL, method, unregisteredUser.tgClient.UserID(), []interface{}{map[string]interface{}{"params": dummyParams}}) + if !strings.Contains(string(respBody), fmt.Sprintf("method %s cannot be called with an unauthorised client - no signed viewing keys found", method)) { + t.Errorf("sensitive method called without authenticating viewingkeys and did fail because of it: %s", method) + } + } +} + +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), "encrypyion token ('token') not found in query parameters or user not found in the database") { + t.Error("eth_getStorageAt did not respond with error: encrypyion token ('token') not found in query parameters or user not found in the database") + } + + // 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 testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { user0, err := NewUser([]wallet.Wallet{w}, httpURL, wsURL) require.NoError(t, err) @@ -246,17 +374,17 @@ func testMultipleAccountsSubscription(t *testing.T, httpURL, wsURL string, w wal func testAreTxsMinted(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { //nolint: unused // set up the tgClient - ogClient := lib.NewTenGatewayLibrary(httpURL, wsURL) + tgClient := lib.NewTenGatewayLibrary(httpURL, wsURL) - // join + register against the og - err := ogClient.Join() + // join + register against the tg + err := tgClient.Join() require.NoError(t, err) - err = ogClient.RegisterAccount(w.PrivateKey(), w.Address()) + err = tgClient.RegisterAccount(w.PrivateKey(), w.Address()) require.NoError(t, err) - // use a standard eth client via the og - ethStdClient, err := ethclient.Dial(ogClient.HTTP()) + // use a standard eth client via the tg + ethStdClient, err := ethclient.Dial(tgClient.HTTP()) require.NoError(t, err) // check the balance @@ -273,14 +401,14 @@ func testAreTxsMinted(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // set up the tgClient - ogClient := lib.NewTenGatewayLibrary(httpURL, wsURL) + tgClient := lib.NewTenGatewayLibrary(httpURL, wsURL) - // join + register against the og - err := ogClient.Join() + // join + register against the tg + err := tgClient.Join() require.NoError(t, err) // register an account - err = ogClient.RegisterAccount(w.PrivateKey(), w.Address()) + err = tgClient.RegisterAccount(w.PrivateKey(), w.Address()) require.NoError(t, err) // make requests to geth for comparison @@ -294,7 +422,7 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { `{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[["0xA58C60cc047592DE97BF1E8d2f225Fc5D959De77", "0x1234"]],"id":1}`, } { // ensure the geth request is issued correctly (should return 200 ok with jsonRPCError) - _, response, err := httputil.PostDataJSON(ogClient.HTTP(), []byte(req)) + _, response, err := httputil.PostDataJSON(tgClient.HTTP(), []byte(req)) require.NoError(t, err) // unmarshall the response to JSONRPCMessage @@ -315,17 +443,17 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { func testErrorsRevertedArePassed(t *testing.T, httpURL, wsURL string, w wallet.Wallet) { // set up the tgClient - ogClient := lib.NewTenGatewayLibrary(httpURL, wsURL) + tgClient := lib.NewTenGatewayLibrary(httpURL, wsURL) - // join + register against the og - err := ogClient.Join() + // join + register against the tg + err := tgClient.Join() require.NoError(t, err) - err = ogClient.RegisterAccount(w.PrivateKey(), w.Address()) + err = tgClient.RegisterAccount(w.PrivateKey(), w.Address()) require.NoError(t, err) - // use a standard eth client via the og - ethStdClient, err := ethclient.Dial(ogClient.HTTP()) + // use a standard eth client via the tg + ethStdClient, err := ethclient.Dial(tgClient.HTTP()) require.NoError(t, err) // check the balance diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index 2489511b12..e00aded1f8 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -13,8 +13,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" ) // Route defines the path plus handler for a given path @@ -34,15 +32,6 @@ func NewHTTPRoutes(walletExt *walletextension.WalletExtension) []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), @@ -102,15 +91,6 @@ func NewWSRoutes(walletExt *walletextension.WalletExtension) []Route { Name: common.PathReady, Func: wsHandler(walletExt, readyRequestHandler), }, - { - Name: common.PathGenerateViewingKey, - Func: wsHandler(walletExt, generateViewingKeyRequestHandler), - }, - - { - Name: common.PathSubmitViewingKey, - Func: wsHandler(walletExt, submitViewingKeyRequestHandler), - }, } } @@ -163,8 +143,8 @@ func ethRequestHandler(walletExt *walletextension.WalletExtension, conn userconn // TODO: @ziga - after removing old wallet extension endpoints we should prevent users doing anything without valid encryption token hexUserID, err := getUserID(conn, 1) if err != nil || !walletExt.UserExists(hexUserID) { - walletExt.Logger().Info("user not found in the query params: %w. Using the default user", log.ErrKey, err) - hexUserID = hex.EncodeToString([]byte(common.DefaultUser)) // todo (@ziga) - this can be removed once old WE endpoints are removed + walletExt.Logger().Info("user not found in the query params or user not found in the database: %w.", log.ErrKey, err) + handleEthError(request, conn, walletExt.Logger(), fmt.Errorf("encrypyion token ('token') not found in query parameters or user not found in the database")) } // todo (@pedro) remove this conn dependency @@ -189,70 +169,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/api/utils.go b/tools/walletextension/api/utils.go index fd9883843d..7679e55bb3 100644 --- a/tools/walletextension/api/utils.go +++ b/tools/walletextension/api/utils.go @@ -18,7 +18,7 @@ func parseRequest(body []byte) (*common.RPCRequest, error) { err := json.Unmarshal(body, &reqJSONMap) if err != nil { return nil, fmt.Errorf("could not unmarshal JSON-RPC request body to JSON: %s. "+ - "If you're trying to generate a viewing key, visit %s", err, common.PathViewingKeys) + "If you're trying to join Ten gateway, visit %s", err, common.PathRoot) } reqID := reqJSONMap[common.JSONKeyID] diff --git a/tools/walletextension/common/constants.go b/tools/walletextension/common/constants.go index 6cca9175e0..8cc2c59f6f 100644 --- a/tools/walletextension/common/constants.go +++ b/tools/walletextension/common/constants.go @@ -26,9 +26,6 @@ const ( const ( PathRoot = "/" PathReady = "/ready/" - PathViewingKeys = "/viewingkeys/" - PathGenerateViewingKey = "/generateviewingkey/" - PathSubmitViewingKey = "/submitviewingkey/" PathJoin = "/join/" PathAuthenticate = "/authenticate/" PathQuery = "/query/" @@ -36,7 +33,6 @@ const ( PathObscuroGateway = "/" PathHealth = "/health/" WSProtocol = "ws://" - DefaultUser = "defaultUser" UserQueryParameter = "u" EncryptedTokenQueryParameter = "token" AddressQueryParameter = "a" diff --git a/tools/walletextension/config/config.go b/tools/walletextension/config/config.go index 0914a93fe4..9cdb125f1a 100644 --- a/tools/walletextension/config/config.go +++ b/tools/walletextension/config/config.go @@ -2,9 +2,9 @@ package config // Config contains the configuration required by the WalletExtension. type Config struct { - WalletExtensionHost string - WalletExtensionPortHTTP int - WalletExtensionPortWS int + TenGatewayHost string + TenGatewayPortHTTP int + TenGatewayPortWS int NodeRPCHTTPAddress string NodeRPCWebsocketAddress string LogPath string diff --git a/tools/walletextension/container/walletextension_container.go b/tools/walletextension/container/walletextension_container.go index 9725e13545..89bac708ac 100644 --- a/tools/walletextension/container/walletextension_container.go +++ b/tools/walletextension/container/walletextension_container.go @@ -1,17 +1,12 @@ package container import ( - "bytes" "encoding/hex" "errors" "fmt" "net/http" "os" - "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ten-protocol/go-ten/go/common/log" "github.com/ten-protocol/go-ten/go/common/stopcontrol" "github.com/ten-protocol/go-ten/go/rpc" @@ -36,7 +31,7 @@ type WalletExtensionContainer struct { wsServer *api.Server } -func NewWalletExtensionContainerFromConfig(config config.Config, logger gethlog.Logger) *WalletExtensionContainer { +func NewTenGatewayContainerFromConfig(config config.Config, logger gethlog.Logger) *WalletExtensionContainer { // create the account manager with a single unauthenticated connection hostRPCBindAddr := wecommon.WSProtocol + config.NodeRPCWebsocketAddress unAuthedClient, err := rpc.NewNetworkClient(hostRPCBindAddr) @@ -53,16 +48,6 @@ func NewWalletExtensionContainerFromConfig(config config.Config, logger gethlog. } userAccountManager := useraccountmanager.NewUserAccountManager(unAuthedClient, logger, databaseStorage, hostRPCBindAddr) - // add default user (when no UserID is provided in the query parameter - for WE endpoints) - defaultUserAccountManager := userAccountManager.AddAndReturnAccountManager(hex.EncodeToString([]byte(wecommon.DefaultUser))) - - // add default user to the database (temporary fix before removing wallet extension endpoints) - accountPrivateKey, err := crypto.GenerateKey() - if err != nil { - logger.Error("Unable to generate key pair for default user", log.ErrKey, err) - os.Exit(1) - } - // get all users and their private keys from the database allUsers, err := databaseStorage.GetAllUsers() if err != nil { @@ -74,32 +59,6 @@ func NewWalletExtensionContainerFromConfig(config config.Config, logger gethlog. for _, user := range allUsers { userAccountManager.AddAndReturnAccountManager(hex.EncodeToString(user.UserID)) logger.Info(fmt.Sprintf("account manager added for user: %s", hex.EncodeToString(user.UserID))) - - // to ensure backwards compatibility we want to load clients for the default user - // TODO @ziga - this code needs to be removed when removing old wallet extension endpoints - if bytes.Equal(user.UserID, []byte(wecommon.DefaultUser)) { - accounts, err := databaseStorage.GetAccounts(user.UserID) - if err != nil { - logger.Error(fmt.Errorf("error getting accounts for user: %s, %w", hex.EncodeToString(user.UserID), err).Error()) - os.Exit(1) - } - for _, account := range accounts { - encClient, err := wecommon.CreateEncClient(hostRPCBindAddr, account.AccountAddress, user.PrivateKey, account.Signature, logger) - if err != nil { - logger.Error(fmt.Errorf("error creating new client, %w", err).Error()) - os.Exit(1) - } - - // add a client to default user - defaultUserAccountManager.AddClient(common.BytesToAddress(account.AccountAddress), encClient) - } - } - } - // TODO @ziga - remove this when removing wallet extension endpoints - err = databaseStorage.AddUser([]byte(wecommon.DefaultUser), crypto.FromECDSA(accountPrivateKey)) - if err != nil { - logger.Error("Unable to save default user to the database", log.ErrKey, err) - os.Exit(1) } // captures version in the env vars @@ -111,10 +70,10 @@ func NewWalletExtensionContainerFromConfig(config config.Config, logger gethlog. stopControl := stopcontrol.New() walletExt := walletextension.New(hostRPCBindAddr, &userAccountManager, databaseStorage, stopControl, version, logger, &config) httpRoutes := api.NewHTTPRoutes(walletExt) - httpServer := api.NewHTTPServer(fmt.Sprintf("%s:%d", config.WalletExtensionHost, config.WalletExtensionPortHTTP), httpRoutes) + httpServer := api.NewHTTPServer(fmt.Sprintf("%s:%d", config.TenGatewayHost, config.TenGatewayPortHTTP), httpRoutes) wsRoutes := api.NewWSRoutes(walletExt) - wsServer := api.NewWSServer(fmt.Sprintf("%s:%d", config.WalletExtensionHost, config.WalletExtensionPortWS), wsRoutes) + wsServer := api.NewWSServer(fmt.Sprintf("%s:%d", config.TenGatewayHost, config.TenGatewayPortWS), wsRoutes) return NewWalletExtensionContainer( hostRPCBindAddr, walletExt, diff --git a/tools/walletextension/main/cli.go b/tools/walletextension/main/cli.go index 119a4b9289..fdb130e17a 100644 --- a/tools/walletextension/main/cli.go +++ b/tools/walletextension/main/cli.go @@ -73,9 +73,9 @@ func parseCLIArgs() config.Config { flag.Parse() return config.Config{ - WalletExtensionHost: *walletExtensionHost, - WalletExtensionPortHTTP: *walletExtensionPort, - WalletExtensionPortWS: *walletExtensionPortWS, + TenGatewayHost: *walletExtensionHost, + TenGatewayPortHTTP: *walletExtensionPort, + TenGatewayPortWS: *walletExtensionPortWS, NodeRPCHTTPAddress: fmt.Sprintf("%s:%d", *nodeHost, *nodeHTTPPort), NodeRPCWebsocketAddress: fmt.Sprintf("%s:%d", *nodeHost, *nodeWebsocketPort), LogPath: *logPath, diff --git a/tools/walletextension/main/main.go b/tools/walletextension/main/main.go index b909f43368..2f1427535a 100644 --- a/tools/walletextension/main/main.go +++ b/tools/walletextension/main/main.go @@ -21,7 +21,7 @@ const ( func main() { config := parseCLIArgs() jsonConfig, _ := json.MarshalIndent(config, "", " ") - fmt.Printf("Welcome to the Obscuro wallet extension. \n\n") + fmt.Printf("Welcome to the Ten Gateway. \n\n") fmt.Printf("Starting with following config: \n%s\n", string(jsonConfig)) // We wait thirty seconds for a connection to the node. If we cannot establish one, we exit the program. @@ -57,8 +57,7 @@ func main() { logLvl = gethlog.LvlDebug } logger := log.New(log.WalletExtCmp, int(logLvl), config.LogPath) - - walletExtContainer := container.NewWalletExtensionContainerFromConfig(config, logger) + walletExtContainer := container.NewTenGatewayContainerFromConfig(config, logger) // Start the wallet extension. err := walletExtContainer.Start() @@ -66,9 +65,8 @@ func main() { fmt.Printf("error in WE - %s", err) } - walletExtensionAddr := fmt.Sprintf("%s:%d", common.Localhost, config.WalletExtensionPortHTTP) - fmt.Printf("💡 Wallet extension started \n") // Some tests rely on seeing this message. Removed in next PR. - fmt.Printf("💡 Obscuro Gateway started - visit http://%s to use it.\n", walletExtensionAddr) + tenGatewayAddr := fmt.Sprintf("%s:%d", common.Localhost, config.TenGatewayPortHTTP) + fmt.Printf("💡 Ten Gateway started - visit http://%s to use it.\n", tenGatewayAddr) select {} } diff --git a/tools/walletextension/test/apis.go b/tools/walletextension/test/apis.go deleted file mode 100644 index 746563a937..0000000000 --- a/tools/walletextension/test/apis.go +++ /dev/null @@ -1,183 +0,0 @@ -package test - -import ( - "context" - "crypto/rand" - "encoding/json" - "fmt" - "math/big" - "time" - - "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.New(api.address, api.viewingKey, api.signature, 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 8d766252fc..0000000000 --- a/tools/walletextension/test/wallet_extension_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package test - -import ( - "fmt" - "strings" - "testing" - - "github.com/ten-protocol/go-ten/go/enclave/vkhandler" - - "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)) - } - } -} - -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), vkhandler.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 55ee12fe98..e9dff217b7 100644 --- a/tools/walletextension/wallet_extension.go +++ b/tools/walletextension/wallet_extension.go @@ -6,8 +6,6 @@ import ( "errors" "fmt" - "github.com/ten-protocol/go-ten/tools/walletextension/accountmanager" - "github.com/ten-protocol/go-ten/tools/walletextension/config" "github.com/ten-protocol/go-ten/go/common/log" @@ -32,7 +30,6 @@ import ( type WalletExtension struct { hostAddr string // The address on which the Obscuro 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 stopControl *stopcontrol.StopControl @@ -52,7 +49,6 @@ func New( return &WalletExtension{ hostAddr: hostAddr, userAccountManager: userAccountManager, - unsignedVKs: map[gethcommon.Address]*viewingkey.ViewingKey{}, storage: storage, logger: logger, stopControl: stopControl, @@ -115,73 +111,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) { - 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, - Signature: nil, // we await a signature from the user before we can set up the EncRPCClient - } - - // 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 { - 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.Signature = 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.hostAddr, 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.Signature) - 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) { // generate new key-pair @@ -342,15 +271,6 @@ func (w *WalletExtension) getStorageAtInterceptor(request *common.RPCRequest, he return nil } - // check if we have default user (we don't want to send userID of it out) - if hexUserID == hex.EncodeToString([]byte(common.DefaultUser)) { - response := map[string]interface{}{} - response[common.JSONKeyRPCVersion] = jsonrpc.Version - response[common.JSONKeyID] = request.ID - response[common.JSONKeyResult] = fmt.Sprintf(accountmanager.ErrNoViewingKey, "eth_getStorageAt") - return response - } - _, err = w.storage.GetUserPrivateKey(userID) if err != nil { w.logger.Info("Trying to get userID, but it is not present in our database: ", log.ErrKey, err)