mirror of https://github.com/embarklabs/embark.git
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:
parent
67581ce482
commit
b29998e1ec
|
@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue