aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacob Evans <jacob@dekz.net>2018-10-04 15:32:54 +0800
committerJacob Evans <jacob@dekz.net>2018-10-05 10:02:09 +0800
commit3e2fe40a11919f09f1f454c71f02aaa147b46b0c (patch)
tree0ff88e480944cfab0cefd211f5623f2870462351
parent2a82ff48c061eacb3b6f9fb36eeae7f515b6d11d (diff)
downloaddexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.tar
dexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.tar.gz
dexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.tar.bz2
dexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.tar.lz
dexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.tar.xz
dexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.tar.zst
dexon-0x-contracts-3e2fe40a11919f09f1f454c71f02aaa147b46b0c.zip
Add eth_signTypedData support to our wallet subproviders
-rw-r--r--packages/contract-wrappers/src/utils/transaction_encoder.ts35
-rw-r--r--packages/contracts/test/exchange/libs.ts18
-rw-r--r--packages/contracts/test/utils/transaction_factory.ts40
-rw-r--r--packages/order-utils/CHANGELOG.json17
-rw-r--r--packages/order-utils/src/constants.ts2
-rw-r--r--packages/order-utils/src/index.ts2
-rw-r--r--packages/subproviders/CHANGELOG.json7
-rw-r--r--packages/subproviders/src/subproviders/base_wallet_subprovider.ts14
-rw-r--r--packages/subproviders/src/subproviders/eth_lightwallet_subprovider.ts18
-rw-r--r--packages/subproviders/src/subproviders/ledger.ts10
-rw-r--r--packages/subproviders/src/subproviders/mnemonic_wallet.ts19
-rw-r--r--packages/subproviders/src/subproviders/private_key_wallet.ts26
-rw-r--r--packages/subproviders/src/types.ts2
-rw-r--r--packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts21
-rw-r--r--packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts21
-rw-r--r--packages/subproviders/test/utils/fixture_data.ts31
-rw-r--r--packages/types/CHANGELOG.json9
-rw-r--r--packages/types/src/index.ts15
-rw-r--r--packages/utils/src/sign_typed_data_utils.ts60
-rw-r--r--packages/utils/test/sign_typed_data_utils_test.ts45
20 files changed, 326 insertions, 86 deletions
diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts
index 87cbb43fd..1800f49ad 100644
--- a/packages/contract-wrappers/src/utils/transaction_encoder.ts
+++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts
@@ -1,19 +1,19 @@
import { schemas } from '@0xproject/json-schemas';
-import { EIP712Schema, EIP712Types, eip712Utils } from '@0xproject/order-utils';
+import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from '@0xproject/order-utils';
import { Order, SignedOrder } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
+import { BigNumber, signTypedDataUtils } from '@0xproject/utils';
import _ = require('lodash');
import { ExchangeContract } from '../contract_wrappers/generated/exchange';
import { assert } from './assert';
-const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = {
+const EIP712_ZEROEX_TRANSACTION_SCHEMA = {
name: 'ZeroExTransaction',
parameters: [
- { name: 'salt', type: EIP712Types.Uint256 },
- { name: 'signerAddress', type: EIP712Types.Address },
- { name: 'data', type: EIP712Types.Bytes },
+ { name: 'salt', type: 'uint256' },
+ { name: 'signerAddress', type: 'address' },
+ { name: 'data', type: 'bytes' },
],
};
@@ -37,16 +37,25 @@ export class TransactionEncoder {
public getTransactionHex(data: string, salt: BigNumber, signerAddress: string): string {
const exchangeAddress = this._getExchangeContract().address;
const executeTransactionData = {
- salt,
+ salt: salt.toString(),
signerAddress,
data,
};
- const executeTransactionHashBuff = eip712Utils.structHash(
- EIP712_ZEROEX_TRANSACTION_SCHEMA,
- executeTransactionData,
- );
- const eip721MessageBuffer = eip712Utils.createEIP712Message(executeTransactionHashBuff, exchangeAddress);
- const messageHex = `0x${eip721MessageBuffer.toString('hex')}`;
+ const typedData = {
+ types: {
+ EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters,
+ ZeroExTransaction: EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters,
+ },
+ domain: {
+ name: EIP712_DOMAIN_NAME,
+ version: EIP712_DOMAIN_VERSION,
+ verifyingContract: exchangeAddress,
+ },
+ message: executeTransactionData,
+ primaryType: EIP712_ZEROEX_TRANSACTION_SCHEMA.name,
+ };
+ const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
+ const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
return messageHex;
}
/**
diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts
index 37234489e..049b7f37a 100644
--- a/packages/contracts/test/exchange/libs.ts
+++ b/packages/contracts/test/exchange/libs.ts
@@ -1,5 +1,5 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
-import { assetDataUtils, eip712Utils, orderHashUtils } from '@0xproject/order-utils';
+import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils';
import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@@ -126,22 +126,6 @@ describe('Exchange libs', () => {
});
describe('LibOrder', () => {
- describe('getOrderSchema', () => {
- it('should output the correct order schema hash', async () => {
- const orderSchema = await libs.getOrderSchemaHash.callAsync();
- const schemaHashBuffer = orderHashUtils._getOrderSchemaBuffer();
- const schemaHashHex = `0x${schemaHashBuffer.toString('hex')}`;
- expect(schemaHashHex).to.be.equal(orderSchema);
- });
- });
- describe('getDomainSeparatorSchema', () => {
- it('should output the correct domain separator schema hash', async () => {
- const domainSeparatorSchema = await libs.getDomainSeparatorSchemaHash.callAsync();
- const domainSchemaBuffer = eip712Utils._getDomainSeparatorSchemaBuffer();
- const schemaHashHex = `0x${domainSchemaBuffer.toString('hex')}`;
- expect(schemaHashHex).to.be.equal(domainSeparatorSchema);
- });
- });
describe('getOrderHash', () => {
it('should output the correct orderHash', async () => {
signedOrder = await orderFactory.newSignedOrderAsync();
diff --git a/packages/contracts/test/utils/transaction_factory.ts b/packages/contracts/test/utils/transaction_factory.ts
index 8465a6a30..47880cca5 100644
--- a/packages/contracts/test/utils/transaction_factory.ts
+++ b/packages/contracts/test/utils/transaction_factory.ts
@@ -1,16 +1,22 @@
-import { EIP712Schema, EIP712Types, eip712Utils, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import {
+ EIP712_DOMAIN_NAME,
+ EIP712_DOMAIN_SCHEMA,
+ EIP712_DOMAIN_VERSION,
+ generatePseudoRandomSalt,
+} from '@0xproject/order-utils';
import { SignatureType } from '@0xproject/types';
+import { signTypedDataUtils } from '@0xproject/utils';
import * as ethUtil from 'ethereumjs-util';
import { signingUtils } from './signing_utils';
import { SignedTransaction } from './types';
-const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = {
+const EIP712_ZEROEX_TRANSACTION_SCHEMA = {
name: 'ZeroExTransaction',
parameters: [
- { name: 'salt', type: EIP712Types.Uint256 },
- { name: 'signerAddress', type: EIP712Types.Address },
- { name: 'data', type: EIP712Types.Bytes },
+ { name: 'salt', type: 'uint256' },
+ { name: 'signerAddress', type: 'address' },
+ { name: 'data', type: 'bytes' },
],
};
@@ -27,20 +33,30 @@ export class TransactionFactory {
const salt = generatePseudoRandomSalt();
const signerAddress = `0x${this._signerBuff.toString('hex')}`;
const executeTransactionData = {
- salt,
+ salt: salt.toString(),
signerAddress,
data,
};
- const executeTransactionHashBuff = eip712Utils.structHash(
- EIP712_ZEROEX_TRANSACTION_SCHEMA,
- executeTransactionData,
- );
- const txHash = eip712Utils.createEIP712Message(executeTransactionHashBuff, this._exchangeAddress);
- const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType);
+ const typedData = {
+ types: {
+ EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters,
+ ZeroExTransaction: EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters,
+ },
+ domain: {
+ name: EIP712_DOMAIN_NAME,
+ version: EIP712_DOMAIN_VERSION,
+ verifyingContract: this._exchangeAddress,
+ },
+ message: executeTransactionData,
+ primaryType: EIP712_ZEROEX_TRANSACTION_SCHEMA.name,
+ };
+ const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
+ const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
const signedTx = {
exchangeAddress: this._exchangeAddress,
signature: `0x${signature.toString('hex')}`,
...executeTransactionData,
+ salt,
};
return signedTx;
}
diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json
index 3e841c43c..a9d2fde8b 100644
--- a/packages/order-utils/CHANGELOG.json
+++ b/packages/order-utils/CHANGELOG.json
@@ -1,5 +1,22 @@
[
{
+ "version": "2.0.0",
+ "changes": [
+ {
+ "note": "Added ecSignOrderAsync to first sign an order as EIP712 and fallback to EthSign",
+ "pr": 1102
+ },
+ {
+ "note": "Added ecSignTypedDataOrderAsync to sign an order exclusively as EIP712",
+ "pr": 1102
+ },
+ {
+ "note": "Rename ecSignOrderHashAsync to ecSignHashAsync removing SignerType parameter",
+ "pr": 1102
+ }
+ ]
+ },
+ {
"version": "1.0.7",
"changes": [
{
diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts
index cc03755c3..5403606c3 100644
--- a/packages/order-utils/src/constants.ts
+++ b/packages/order-utils/src/constants.ts
@@ -22,7 +22,7 @@ export const EIP712_DOMAIN_SCHEMA = {
name: 'EIP712Domain',
parameters: [
{ name: 'name', type: 'string' },
- { name: 'version', type: 'string ' },
+ { name: 'version', type: 'string' },
{ name: 'verifyingContract', type: 'address' },
],
};
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index 7194b9780..89a843d8f 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -18,6 +18,8 @@ export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store';
+export { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants';
+
export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types';
export {
SignedOrder,
diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json
index 30887c6fe..6a6f7848b 100644
--- a/packages/subproviders/CHANGELOG.json
+++ b/packages/subproviders/CHANGELOG.json
@@ -3,7 +3,12 @@
"version": "2.1.0",
"changes": [
{
- "note": "Add Metamask Subprovider to handle inconsistent JSON RPC behaviour"
+ "note": "Add Metamask Subprovider to handle inconsistent JSON RPC behaviour",
+ "pr": 1102
+ },
+ {
+ "note": "Add support for eth_signTypedData in Mnemonic, Private and EthLightWallet",
+ "pr": 1102
}
]
},
diff --git a/packages/subproviders/src/subproviders/base_wallet_subprovider.ts b/packages/subproviders/src/subproviders/base_wallet_subprovider.ts
index 4342e47e9..409a0d330 100644
--- a/packages/subproviders/src/subproviders/base_wallet_subprovider.ts
+++ b/packages/subproviders/src/subproviders/base_wallet_subprovider.ts
@@ -23,6 +23,7 @@ export abstract class BaseWalletSubprovider extends Subprovider {
public abstract async getAccountsAsync(): Promise<string[]>;
public abstract async signTransactionAsync(txParams: PartialTxParams): Promise<string>;
public abstract async signPersonalMessageAsync(data: string, address: string): Promise<string>;
+ public abstract async signTypedDataAsync(address: string, typedData: any): Promise<string>;
/**
* This method conforms to the web3-provider-engine interface.
@@ -36,6 +37,8 @@ export abstract class BaseWalletSubprovider extends Subprovider {
public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise<void> {
let accounts;
let txParams;
+ let address;
+ let typedData;
switch (payload.method) {
case 'eth_coinbase':
try {
@@ -86,7 +89,7 @@ export abstract class BaseWalletSubprovider extends Subprovider {
case 'eth_sign':
case 'personal_sign':
const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0];
- const address = payload.method === 'eth_sign' ? payload.params[0] : payload.params[1];
+ address = payload.method === 'eth_sign' ? payload.params[0] : payload.params[1];
try {
const ecSignatureHex = await this.signPersonalMessageAsync(data, address);
end(null, ecSignatureHex);
@@ -94,6 +97,15 @@ export abstract class BaseWalletSubprovider extends Subprovider {
end(err);
}
return;
+ case 'eth_signTypedData':
+ [address, typedData] = payload.params;
+ try {
+ const signature = await this.signTypedDataAsync(address, typedData);
+ end(null, signature);
+ } catch (err) {
+ end(err);
+ }
+ return;
default:
next();
diff --git a/packages/subproviders/src/subproviders/eth_lightwallet_subprovider.ts b/packages/subproviders/src/subproviders/eth_lightwallet_subprovider.ts
index 6afd71422..e3afeff1b 100644
--- a/packages/subproviders/src/subproviders/eth_lightwallet_subprovider.ts
+++ b/packages/subproviders/src/subproviders/eth_lightwallet_subprovider.ts
@@ -57,7 +57,7 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
/**
* Sign a personal Ethereum signed message. The signing account will be the account
* associated with the provided address.
- * If you've added the MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_sign`
+ * If you've added the this Subprovider to your app's provider, you can simply send an `eth_sign`
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param data Hex string message to sign
@@ -71,4 +71,20 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
const result = privKeyWallet.signPersonalMessageAsync(data, address);
return result;
}
+ /**
+ * Sign an EIP712 Typed Data message. The signing address will associated with the provided address.
+ * If you've added this Subprovider to your app's provider, you can simply send an `eth_signTypedData`
+ * JSON RPC request, and this method will be called auto-magically.
+ * If you are not using this via a ProviderEngine instance, you can call it directly.
+ * @param address Address of the account to sign with
+ * @param data the typed data object
+ * @return Signature hex string (order: rsv)
+ */
+ public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
+ let privKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
+ const privKeyWallet = new PrivateKeyWalletSubprovider(privKey);
+ privKey = '';
+ const result = privKeyWallet.signTypedDataAsync(address, typedData);
+ return result;
+ }
}
diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts
index 6ad5de2e2..ee8edde92 100644
--- a/packages/subproviders/src/subproviders/ledger.ts
+++ b/packages/subproviders/src/subproviders/ledger.ts
@@ -187,6 +187,16 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
throw err;
}
}
+ /**
+ * eth_signTypedData is currently not supported on Ledger devices.
+ * @param address Address of the account to sign with
+ * @param data the typed data object
+ * @return Signature hex string (order: rsv)
+ */
+ // tslint:disable-next-line:prefer-function-over-method
+ public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
+ throw new Error(WalletSubproviderErrors.MethodNotSupported);
+ }
private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> {
await this._connectionLock.acquire();
if (!_.isUndefined(this._ledgerClientIfExists)) {
diff --git a/packages/subproviders/src/subproviders/mnemonic_wallet.ts b/packages/subproviders/src/subproviders/mnemonic_wallet.ts
index 1495112b6..de99b632a 100644
--- a/packages/subproviders/src/subproviders/mnemonic_wallet.ts
+++ b/packages/subproviders/src/subproviders/mnemonic_wallet.ts
@@ -108,6 +108,25 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
const sig = await privateKeyWallet.signPersonalMessageAsync(data, address);
return sig;
}
+ /**
+ * Sign an EIP712 Typed Data message. The signing account will be the account
+ * associated with the provided address.
+ * If you've added this MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_signTypedData`
+ * JSON RPC request, and this method will be called auto-magically.
+ * If you are not using this via a ProviderEngine instance, you can call it directly.
+ * @param address Address of the account to sign with
+ * @param data the typed data object
+ * @return Signature hex string (order: rsv)
+ */
+ public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
+ if (_.isUndefined(typedData)) {
+ throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
+ }
+ assert.isETHAddressHex('address', address);
+ const privateKeyWallet = this._privateKeyWalletForAddress(address);
+ const sig = await privateKeyWallet.signTypedDataAsync(address, typedData);
+ return sig;
+ }
private _privateKeyWalletForAddress(address: string): PrivateKeyWalletSubprovider {
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(address);
const privateKeyHex = derivedKeyInfo.hdKey.privateKey.toString('hex');
diff --git a/packages/subproviders/src/subproviders/private_key_wallet.ts b/packages/subproviders/src/subproviders/private_key_wallet.ts
index dbd51e8d7..51409077d 100644
--- a/packages/subproviders/src/subproviders/private_key_wallet.ts
+++ b/packages/subproviders/src/subproviders/private_key_wallet.ts
@@ -1,4 +1,5 @@
import { assert } from '@0xproject/assert';
+import { signTypedDataUtils } from '@0xproject/utils';
import EthereumTx = require('ethereumjs-tx');
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
@@ -84,4 +85,29 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
return rpcSig;
}
+ /**
+ * Sign an EIP712 Typed Data message. The signing address will be calculated from the private key.
+ * The address must be provided it must match the address calculated from the private key.
+ * If you've added this Subprovider to your app's provider, you can simply send an `eth_signTypedData`
+ * JSON RPC request, and this method will be called auto-magically.
+ * If you are not using this via a ProviderEngine instance, you can call it directly.
+ * @param address Address of the account to sign with
+ * @param data the typed data object
+ * @return Signature hex string (order: rsv)
+ */
+ public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
+ if (_.isUndefined(typedData)) {
+ throw new Error(WalletSubproviderErrors.DataMissingForSignTypedData);
+ }
+ assert.isETHAddressHex('address', address);
+ if (address !== this._address) {
+ throw new Error(
+ `Requested to sign message with address: ${address}, instantiated with address: ${this._address}`,
+ );
+ }
+ const dataBuff = signTypedDataUtils.signTypedDataHash(typedData);
+ const sig = ethUtil.ecsign(dataBuff, this._privateKeyBuffer);
+ const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
+ return rpcSig;
+ }
}
diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts
index fe58bffa5..e8a47ad34 100644
--- a/packages/subproviders/src/types.ts
+++ b/packages/subproviders/src/types.ts
@@ -107,8 +107,10 @@ export interface ResponseWithTxParams {
export enum WalletSubproviderErrors {
AddressNotFound = 'ADDRESS_NOT_FOUND',
DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE',
+ DataMissingForSignTypedData = 'DATA_MISSING_FOR_SIGN_TYPED_DATA',
SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED',
FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID',
+ MethodNotSupported = 'METHOD_NOT_SUPPORTED',
}
export enum LedgerSubproviderErrors {
TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE',
diff --git a/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts b/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts
index f2bdda3cd..61dcbf6da 100644
--- a/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts
+++ b/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts
@@ -47,6 +47,13 @@ describe('MnemonicWalletSubprovider', () => {
const txHex = await subprovider.signTransactionAsync(txData);
expect(txHex).to.be.equal(fixtureData.TX_DATA_ACCOUNT_1_SIGNED_RESULT);
});
+ it('signs an EIP712 sign typed data message', async () => {
+ const signature = await subprovider.signTypedDataAsync(
+ fixtureData.TEST_RPC_ACCOUNT_0,
+ fixtureData.EIP712_TEST_TYPED_DATA,
+ );
+ expect(signature).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
+ });
});
describe('failure cases', () => {
it('throws an error if address is invalid ', async () => {
@@ -118,6 +125,20 @@ describe('MnemonicWalletSubprovider', () => {
});
provider.sendAsync(payload, callback);
});
+ it('signs an EIP712 sign typed data message with eth_signTypedData', (done: DoneCallback) => {
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_signTypedData',
+ params: [fixtureData.TEST_RPC_ACCOUNT_0, fixtureData.EIP712_TEST_TYPED_DATA],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
});
describe('failure cases', () => {
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
diff --git a/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts b/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts
index 95773145f..4cd70e5ed 100644
--- a/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts
+++ b/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts
@@ -32,6 +32,13 @@ describe('PrivateKeyWalletSubprovider', () => {
const txHex = await subprovider.signTransactionAsync(fixtureData.TX_DATA);
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
});
+ it('signs an EIP712 sign typed data message', async () => {
+ const signature = await subprovider.signTypedDataAsync(
+ fixtureData.TEST_RPC_ACCOUNT_0,
+ fixtureData.EIP712_TEST_TYPED_DATA,
+ );
+ expect(signature).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
+ });
});
});
describe('calls through a provider', () => {
@@ -103,6 +110,20 @@ describe('PrivateKeyWalletSubprovider', () => {
});
provider.sendAsync(payload, callback);
});
+ it('signs an EIP712 sign typed data message with eth_signTypedData', (done: DoneCallback) => {
+ const payload = {
+ jsonrpc: '2.0',
+ method: 'eth_signTypedData',
+ params: [fixtureData.TEST_RPC_ACCOUNT_0, fixtureData.EIP712_TEST_TYPED_DATA],
+ id: 1,
+ };
+ const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
+ expect(err).to.be.a('null');
+ expect(response.result).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
+ done();
+ });
+ provider.sendAsync(payload, callback);
+ });
});
describe('failure cases', () => {
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
diff --git a/packages/subproviders/test/utils/fixture_data.ts b/packages/subproviders/test/utils/fixture_data.ts
index 7cf502c97..3eb4493b5 100644
--- a/packages/subproviders/test/utils/fixture_data.ts
+++ b/packages/subproviders/test/utils/fixture_data.ts
@@ -30,4 +30,35 @@ export const fixtureData = {
'0xf85f8080822710940000000000000000000000000000000000000000808078a0712854c73c69445cc1b22a7c3d7312ff9a97fe4ffba35fd636e8236b211b6e7ca0647cee031615e52d916c7c707025bc64ad525d8f1b9876c3435a863b42743178',
TX_DATA_ACCOUNT_1_SIGNED_RESULT:
'0xf85f8080822710940000000000000000000000000000000000000000808078a04b02af7ff3f18ce114b601542cc8ebdc50921354f75dd510d31793453a0710e6a0540082a01e475465801b8186a2edc79ec1a2dcf169b9781c25a58a417023c9ca',
+ EIP712_TEST_TYPED_DATA: {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ ],
+ Test: [
+ {
+ name: 'testAddress',
+ type: 'address',
+ },
+ {
+ name: 'testNumber',
+ type: 'uint256',
+ },
+ ],
+ },
+ domain: {
+ name: 'Test',
+ },
+ message: {
+ testAddress: '0x0000000000000000000000000000000000000000',
+ testNumber: '12345',
+ },
+ primaryType: 'Test',
+ },
+ EIP712_TEST_TYPED_DATA_HASH: '0xb460d69ca60383293877cd765c0f97bd832d66bca720f7e32222ce1118832493',
+ EIP712_TEST_TYPED_DATA_SIGNED_RESULT:
+ '0x20af5b6bfc3658942198d6eeda159b4ed589f90cee6eac3ba117818ffba5fd7e354a353aad93faabd6eb6c66e17921c92bd1cd09c92a770f554470dc3e254ce701',
};
diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json
index 6bb6ced70..65dd75101 100644
--- a/packages/types/CHANGELOG.json
+++ b/packages/types/CHANGELOG.json
@@ -1,5 +1,14 @@
[
{
+ "version": "1.2.0",
+ "changes": [
+ {
+ "note": "Added `EIP712Parameter` `EIP712Types` `EIP712TypedData` for EIP712 signing",
+ "pr": 1102
+ }
+ ]
+ },
+ {
"timestamp": 1538693146,
"version": "1.1.4",
"changes": [
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 2f148f0e6..d57bdfb6f 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -589,3 +589,18 @@ export interface Metadata {
externalTypeToLink: ExternalTypeToLink;
externalExportToLink: ExternalExportToLink;
}
+
+export interface EIP712Parameter {
+ name: string;
+ type: string;
+}
+
+export interface EIP712Types {
+ [key: string]: EIP712Parameter[];
+}
+export interface EIP712TypedData {
+ types: EIP712Types;
+ domain: any;
+ message: any;
+ primaryType: string;
+}
diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts
index 902d8530c..b72fd099b 100644
--- a/packages/utils/src/sign_typed_data_utils.ts
+++ b/packages/utils/src/sign_typed_data_utils.ts
@@ -1,29 +1,30 @@
import * as ethUtil from 'ethereumjs-util';
import * as ethers from 'ethers';
-export interface EIP712Parameter {
- name: string;
- type: string;
-}
-
-export interface EIP712Types {
- [key: string]: EIP712Parameter[];
-}
-export interface EIP712TypedData {
- types: EIP712Types;
- domain: any;
- message: any;
- primaryType: string;
-}
+import { EIP712TypedData, EIP712Types } from '@0xproject/types';
export const signTypedDataUtils = {
- findDependencies(primaryType: string, types: EIP712Types, found: string[] = []): string[] {
+ /**
+ * Computes the Sign Typed Data hash
+ * @param typedData An object that conforms to the EIP712TypedData interface
+ * @return A Buffer containing the hash of the sign typed data.
+ */
+ signTypedDataHash(typedData: EIP712TypedData): Buffer {
+ return ethUtil.sha3(
+ Buffer.concat([
+ Buffer.from('1901', 'hex'),
+ signTypedDataUtils._structHash('EIP712Domain', typedData.domain, typedData.types),
+ signTypedDataUtils._structHash(typedData.primaryType, typedData.message, typedData.types),
+ ]),
+ );
+ },
+ _findDependencies(primaryType: string, types: EIP712Types, found: string[] = []): string[] {
if (found.includes(primaryType) || types[primaryType] === undefined) {
return found;
}
found.push(primaryType);
for (const field of types[primaryType]) {
- for (const dep of signTypedDataUtils.findDependencies(field.type, types, found)) {
+ for (const dep of signTypedDataUtils._findDependencies(field.type, types, found)) {
if (!found.includes(dep)) {
found.push(dep);
}
@@ -31,8 +32,8 @@ export const signTypedDataUtils = {
}
return found;
},
- encodeType(primaryType: string, types: EIP712Types): string {
- let deps = signTypedDataUtils.findDependencies(primaryType, types);
+ _encodeType(primaryType: string, types: EIP712Types): string {
+ let deps = signTypedDataUtils._findDependencies(primaryType, types);
deps = deps.filter(d => d !== primaryType);
deps = [primaryType].concat(deps.sort());
let result = '';
@@ -41,9 +42,9 @@ export const signTypedDataUtils = {
}
return result;
},
- encodeData(primaryType: string, data: any, types: EIP712Types): string {
+ _encodeData(primaryType: string, data: any, types: EIP712Types): string {
const encodedTypes = ['bytes32'];
- const encodedValues = [signTypedDataUtils.typeHash(primaryType, types)];
+ const encodedValues = [signTypedDataUtils._typeHash(primaryType, types)];
for (const field of types[primaryType]) {
let value = data[field.name];
if (field.type === 'string' || field.type === 'bytes') {
@@ -52,7 +53,7 @@ export const signTypedDataUtils = {
encodedValues.push(value);
} else if (types[field.type] !== undefined) {
encodedTypes.push('bytes32');
- value = ethUtil.sha3(signTypedDataUtils.encodeData(field.type, value, types));
+ value = ethUtil.sha3(signTypedDataUtils._encodeData(field.type, value, types));
encodedValues.push(value);
} else if (field.type.lastIndexOf(']') === field.type.length - 1) {
throw new Error('Arrays currently unimplemented in encodeData');
@@ -63,19 +64,10 @@ export const signTypedDataUtils = {
}
return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues);
},
- typeHash(primaryType: string, types: EIP712Types): Buffer {
- return ethUtil.sha3(signTypedDataUtils.encodeType(primaryType, types));
+ _typeHash(primaryType: string, types: EIP712Types): Buffer {
+ return ethUtil.sha3(signTypedDataUtils._encodeType(primaryType, types));
},
- structHash(primaryType: string, data: any, types: EIP712Types): Buffer {
- return ethUtil.sha3(signTypedDataUtils.encodeData(primaryType, data, types));
- },
- signTypedDataHash(typedData: EIP712TypedData): Buffer {
- return ethUtil.sha3(
- Buffer.concat([
- Buffer.from('1901', 'hex'),
- signTypedDataUtils.structHash('EIP712Domain', typedData.domain, typedData.types),
- signTypedDataUtils.structHash(typedData.primaryType, typedData.message, typedData.types),
- ]),
- );
+ _structHash(primaryType: string, data: any, types: EIP712Types): Buffer {
+ return ethUtil.sha3(signTypedDataUtils._encodeData(primaryType, data, types));
},
};
diff --git a/packages/utils/test/sign_typed_data_utils_test.ts b/packages/utils/test/sign_typed_data_utils_test.ts
index b21ffefa0..e1cb4f6e1 100644
--- a/packages/utils/test/sign_typed_data_utils_test.ts
+++ b/packages/utils/test/sign_typed_data_utils_test.ts
@@ -7,8 +7,37 @@ const expect = chai.expect;
describe('signTypedDataUtils', () => {
describe('signTypedDataHash', () => {
- const signTypedDataHashHex = '0x55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692';
- const signTypedData = {
+ const simpleSignTypedDataHashHex = '0xb460d69ca60383293877cd765c0f97bd832d66bca720f7e32222ce1118832493';
+ const simpleSignTypedData = {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ ],
+ Test: [
+ {
+ name: 'testAddress',
+ type: 'address',
+ },
+ {
+ name: 'testNumber',
+ type: 'uint256',
+ },
+ ],
+ },
+ domain: {
+ name: 'Test',
+ },
+ message: {
+ testAddress: '0x0000000000000000000000000000000000000000',
+ testNumber: '12345',
+ },
+ primaryType: 'Test',
+ };
+ const orderSignTypedDataHashHex = '0x55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692';
+ const orderSignTypedData = {
types: {
EIP712Domain: [
{
@@ -97,11 +126,15 @@ describe('signTypedDataUtils', () => {
},
primaryType: 'Order',
};
- it.only('creates a known hash of the sign typed data', () => {
- const hash = signTypedDataUtils.signTypedDataHash(signTypedData).toString('hex');
+ it('creates a hash of the test sign typed data', () => {
+ const hash = signTypedDataUtils.signTypedDataHash(simpleSignTypedData).toString('hex');
+ const hashHex = `0x${hash}`;
+ expect(hashHex).to.be.eq(simpleSignTypedDataHashHex);
+ });
+ it('creates a hash of the order sign typed data', () => {
+ const hash = signTypedDataUtils.signTypedDataHash(orderSignTypedData).toString('hex');
const hashHex = `0x${hash}`;
- expect(hashHex).to.be.eq(signTypedDataHashHex);
- console.log(hash);
+ expect(hashHex).to.be.eq(orderSignTypedDataHashHex);
});
});
});