fix(@embark/rpc-manager): fix eth_signTypedData method + tests

The signTypedData rpc method was broken, because it didn't check for
the node accounts, meaning that if you wanted to sign with a node
account that was unlocked, like in the tests, it would throw
This commit is contained in:
Jonathan Rainville 2020-02-19 10:47:59 -05:00 committed by Eric Mastro
parent 67581ce482
commit b29998e1ec
5 changed files with 148 additions and 31 deletions

View File

@ -1,4 +1,4 @@
/*global artifacts, contract, config, it, web3*/ /*global artifacts, contract, config, it, web3, evmMethod*/
const assert = require('assert'); const assert = require('assert');
const AnotherStorage = artifacts.require('AnotherStorage'); const AnotherStorage = artifacts.require('AnotherStorage');
const SimpleStorage = artifacts.require('SimpleStorage'); const SimpleStorage = artifacts.require('SimpleStorage');
@ -63,4 +63,51 @@ contract("AnotherStorage", function() {
let result = await AnotherStorage.simpleStorageAddress(); let result = await AnotherStorage.simpleStorageAddress();
assert.equal(result.toString(), SimpleStorage.options.address); assert.equal(result.toString(), SimpleStorage.options.address);
}); });
it("can sign using eth_signTypedData with a custom account", async function() {
const chainId = await web3.eth.net.getId();
const domain = [
{name: "name", type: "string"},
{name: "version", type: "string"},
{name: "chainId", type: "uint256"},
{name: "verifyingContract", type: "address"}
];
const redeem = [
{name: "keycard", type: "address"},
{name: "receiver", type: "address"},
{name: "code", type: "bytes32"}
];
const domainData = {
name: "KeycardGift",
version: "1",
chainId,
verifyingContract: SimpleStorage.options.address
};
const message = {
keycard: accounts[1],
receiver: accounts[2],
code: web3.utils.sha3("hello world")
};
const data = {
types: {
EIP712Domain: domain,
Redeem: redeem
},
primaryType: "Redeem",
domain: domainData,
message
};
const signature = await evmMethod("eth_signTypedData", [
accounts[0],
data
]);
assert.strictEqual(signature, '0x5dcbab53809985222a62807dd2f23551902fa4471377e319d5d682e1458646714cc71' +
'faa76cf6de3e0d871edbfa85628db552619d681594d5af2f34be2c33cdd1b');
});
}); });

View File

@ -1,8 +1,9 @@
/*global artifacts, contract, config, it, web3*/ /*global artifacts, contract, config, it, web3, evmMethod*/
const assert = require('assert'); const assert = require('assert');
const SomeContract = artifacts.require('SomeContract'); const SomeContract = artifacts.require('SomeContract');
const MyToken2 = artifacts.require('MyToken2'); const MyToken2 = artifacts.require('MyToken2');
let accounts;
config({ config({
contracts: { contracts: {
deploy: { deploy: {
@ -22,6 +23,8 @@ config({
} }
} }
} }
}, (err, theAccounts) => {
accounts = theAccounts;
}); });
contract("SomeContract", function() { contract("SomeContract", function() {
@ -37,4 +40,50 @@ contract("SomeContract", function() {
assert.strictEqual(address, web3.eth.defaultAccount); assert.strictEqual(address, web3.eth.defaultAccount);
}); });
it("can sign using eth_signTypedData with a node account", async function() {
const chainId = await web3.eth.net.getId();
const domain = [
{name: "name", type: "string"},
{name: "version", type: "string"},
{name: "chainId", type: "uint256"},
{name: "verifyingContract", type: "address"}
];
const redeem = [
{name: "keycard", type: "address"},
{name: "receiver", type: "address"},
{name: "code", type: "bytes32"}
];
const domainData = {
name: "KeycardGift",
version: "1",
chainId,
verifyingContract: SomeContract.options.address
};
const message = {
keycard: accounts[1],
receiver: accounts[2],
code: web3.utils.sha3("hello world")
};
const data = {
types: {
EIP712Domain: domain,
Redeem: redeem
},
primaryType: "Redeem",
domain: domainData,
message
};
const signature = await evmMethod("eth_signTypedData", [
accounts[0],
data
]);
// Impossible to tell what the signature will be because the account is not deterministic
assert.ok(signature);
});
}); });

View File

@ -2,6 +2,7 @@ import { Callback, Embark, EmbarkEvents } from "embark-core";
import { __ } from "embark-i18n"; import { __ } from "embark-i18n";
import Web3 from "web3"; import Web3 from "web3";
import RpcModifier from "./rpcModifier"; import RpcModifier from "./rpcModifier";
import {handleSignRequest} from './utils/signUtils';
export default class EthSignData extends RpcModifier { export default class EthSignData extends RpcModifier {
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) { constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
@ -16,21 +17,7 @@ export default class EthSignData extends RpcModifier {
return callback(null, params); return callback(null, params);
} }
try { handleSignRequest(this.nodeAccounts, params, callback);
const [fromAddr] = params.request.params;
const account = this.nodeAccounts.find(acc => (
Web3.utils.toChecksumAddress(acc) ===
Web3.utils.toChecksumAddress(fromAddr)
));
if (!account) {
params.sendToNode = false;
}
} catch (err) {
return callback(err);
}
callback(null, params);
} }
private async ethSignDataResponse(params: any, callback: Callback<any>) { private async ethSignDataResponse(params: any, callback: Callback<any>) {

View File

@ -3,6 +3,7 @@ import { Callback, Embark, EmbarkEvents } from "embark-core";
import {__} from "embark-i18n"; import {__} from "embark-i18n";
import Web3 from "web3"; import Web3 from "web3";
import RpcModifier from "./rpcModifier"; import RpcModifier from "./rpcModifier";
import {handleSignRequest, isNodeAccount} from './utils/signUtils';
export default class EthSignTypedData extends RpcModifier { export default class EthSignTypedData extends RpcModifier {
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) { constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
@ -18,15 +19,14 @@ export default class EthSignTypedData extends RpcModifier {
// - eth_signTypedData_v3 // - eth_signTypedData_v3
// - eth_signTypedData_v4 // - eth_signTypedData_v4
// - personal_signTypedData (parity) // - personal_signTypedData (parity)
if (params.request.method.includes("signTypedData")) { if (!params.request.method.includes("signTypedData")) {
// indicate that we do not want this call to go to the node
params.sendToNode = false;
return callback(null, params); return callback(null, params);
} }
callback(null, params);
}
private async ethSignTypedDataResponse(params: any, callback: Callback<any>) {
handleSignRequest(this.nodeAccounts, params, callback);
}
private async ethSignTypedDataResponse(params: any, callback: Callback<any>) {
// check for: // check for:
// - eth_signTypedData // - eth_signTypedData
// - eth_signTypedData_v3 // - eth_signTypedData_v3
@ -35,12 +35,20 @@ export default class EthSignTypedData extends RpcModifier {
if (!params.request.method.includes("signTypedData")) { if (!params.request.method.includes("signTypedData")) {
return callback(null, params); return callback(null, params);
} }
this.logger.trace(__(`Modifying blockchain '${params.request.method}' response:`));
this.logger.trace(__(`Original request/response data: ${JSON.stringify({ request: params.request, response: params.response })}`));
try { try {
const [fromAddr, typedData] = params.request.params; const [fromAddr, typedData] = params.request.params;
if (isNodeAccount(this.nodeAccounts, fromAddr)) {
// If it's a node account, we send the result because it should already be signed
return callback(null, params);
}
this.logger.trace(__(`Modifying blockchain '${params.request.method}' response:`));
this.logger.trace(__(`Original request/response data: ${JSON.stringify({
request: params.request,
response: params.response
})}`));
const account = this.accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(fromAddr)); const account = this.accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(fromAddr));
if (!(account && account.privateKey)) { if (!(account && account.privateKey)) {
return callback( return callback(
@ -51,7 +59,10 @@ export default class EthSignTypedData extends RpcModifier {
const signature = sign(toSign, [account.privateKey]); const signature = sign(toSign, [account.privateKey]);
params.response.result = signature[0]; params.response.result = signature[0];
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({ request: params.request, response: params.response })}`)); this.logger.trace(__(`Modified request/response data: ${JSON.stringify({
request: params.request,
response: params.response
})}`));
} catch (err) { } catch (err) {
return callback(err); return callback(err);
} }

View File

@ -0,0 +1,23 @@
import Web3 from "web3";
export function isNodeAccount(nodeAccounts, fromAddr) {
const account = nodeAccounts.find(acc => (
Web3.utils.toChecksumAddress(acc) ===
Web3.utils.toChecksumAddress(fromAddr)
));
return !!account;
}
export function handleSignRequest(nodeAccounts, params, callback) {
try {
const [fromAddr] = params.request.params;
// If it's not a node account, we don't send it to the Node as it won't understand it
if (!isNodeAccount(nodeAccounts, fromAddr)) {
params.sendToNode = false;
}
} catch (err) {
return callback(err);
}
callback(null, params);
}