aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/order-utils/src/constants.ts12
-rw-r--r--packages/order-utils/src/eip712_utils.ts109
-rw-r--r--packages/order-utils/src/index.ts4
-rw-r--r--packages/order-utils/src/order_hash.ts52
-rw-r--r--packages/order-utils/src/signature_utils.ts2
-rw-r--r--packages/order-utils/src/types.ts18
-rw-r--r--packages/utils/src/index.ts1
-rw-r--r--packages/utils/src/sign_typed_data_utils.ts81
-rw-r--r--packages/utils/test/sign_typed_data_utils_test.ts107
9 files changed, 234 insertions, 152 deletions
diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts
index c23578c20..cc03755c3 100644
--- a/packages/order-utils/src/constants.ts
+++ b/packages/order-utils/src/constants.ts
@@ -14,3 +14,15 @@ export const constants = {
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
ZERO_AMOUNT: new BigNumber(0),
};
+
+export const EIP712_DOMAIN_NAME = '0x Protocol';
+export const EIP712_DOMAIN_VERSION = '2';
+
+export const EIP712_DOMAIN_SCHEMA = {
+ name: 'EIP712Domain',
+ parameters: [
+ { name: 'name', type: 'string' },
+ { name: 'version', type: 'string ' },
+ { name: 'verifyingContract', type: 'address' },
+ ],
+};
diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts
deleted file mode 100644
index 01f1e930e..000000000
--- a/packages/order-utils/src/eip712_utils.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import ethUtil = require('ethereumjs-util');
-import * as _ from 'lodash';
-
-import { crypto } from './crypto';
-import { EIP712Schema, EIP712Types } from './types';
-
-const EIP191_PREFIX = '\x19\x01';
-const EIP712_VALUE_LENGTH = 32;
-export const EIP712_DOMAIN_NAME = '0x Protocol';
-export const EIP712_DOMAIN_VERSION = '2';
-
-export const EIP712_DOMAIN_SCHEMA: EIP712Schema = {
- name: 'EIP712Domain',
- parameters: [
- { name: 'name', type: EIP712Types.String },
- { name: 'version', type: EIP712Types.String },
- { name: 'verifyingContract', type: EIP712Types.Address },
- ],
-};
-
-export const eip712Utils = {
- /**
- * Compiles the EIP712Schema and returns the hash of the schema.
- * @param schema The EIP712 schema.
- * @return The hash of the compiled schema
- */
- compileSchema(schema: EIP712Schema): Buffer {
- const eip712Schema = eip712Utils._encodeType(schema);
- const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]);
- return eip712SchemaHashBuffer;
- },
- /**
- * Merges the EIP712 hash of a struct with the DomainSeparator for 0x v2.
- * @param hashStruct the EIP712 hash of a struct
- * @param contractAddress the exchange contract address
- * @return The hash of an EIP712 message with domain separator prefixed
- */
- createEIP712Message(hashStruct: Buffer, contractAddress: string): Buffer {
- const domainSeparatorHashBuffer = eip712Utils._getDomainSeparatorHashBuffer(contractAddress);
- const messageBuff = crypto.solSHA3([EIP191_PREFIX, domainSeparatorHashBuffer, hashStruct]);
- return messageBuff;
- },
- /**
- * Pad an address to 32 bytes
- * @param address Address to pad
- * @return padded address
- */
- pad32Address(address: string): Buffer {
- const addressBuffer = ethUtil.toBuffer(address);
- const addressPadded = eip712Utils.pad32Buffer(addressBuffer);
- return addressPadded;
- },
- /**
- * Pad an buffer to 32 bytes
- * @param buffer Address to pad
- * @return padded buffer
- */
- pad32Buffer(buffer: Buffer): Buffer {
- const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH);
- return bufferPadded;
- },
- /**
- * Hash together a EIP712 schema with the corresponding data
- * @param schema EIP712-compliant schema
- * @param data Data the complies to the schema
- * @return A buffer containing the SHA256 hash of the schema and encoded data
- */
- structHash(schema: EIP712Schema, data: { [key: string]: any }): Buffer {
- const encodedData = eip712Utils._encodeData(schema, data);
- const schemaHash = eip712Utils.compileSchema(schema);
- const hashBuffer = crypto.solSHA3([schemaHash, ...encodedData]);
- return hashBuffer;
- },
- _getDomainSeparatorSchemaBuffer(): Buffer {
- return eip712Utils.compileSchema(EIP712_DOMAIN_SCHEMA);
- },
- _getDomainSeparatorHashBuffer(exchangeAddress: string): Buffer {
- const domainSeparatorSchemaBuffer = eip712Utils._getDomainSeparatorSchemaBuffer();
- const encodedData = eip712Utils._encodeData(EIP712_DOMAIN_SCHEMA, {
- name: EIP712_DOMAIN_NAME,
- version: EIP712_DOMAIN_VERSION,
- verifyingContract: exchangeAddress,
- });
- const domainSeparatorHashBuff2 = crypto.solSHA3([domainSeparatorSchemaBuffer, ...encodedData]);
- return domainSeparatorHashBuff2;
- },
- _encodeType(schema: EIP712Schema): string {
- const namedTypes = _.map(schema.parameters, ({ name, type }) => `${type} ${name}`);
- const namedTypesJoined = namedTypes.join(',');
- const encodedType = `${schema.name}(${namedTypesJoined})`;
- return encodedType;
- },
- _encodeData(schema: EIP712Schema, data: { [key: string]: any }): any {
- const encodedValues = [];
- for (const parameter of schema.parameters) {
- const value = data[parameter.name];
- if (parameter.type === EIP712Types.String || parameter.type === EIP712Types.Bytes) {
- encodedValues.push(crypto.solSHA3([ethUtil.toBuffer(value)]));
- } else if (parameter.type === EIP712Types.Uint256) {
- encodedValues.push(value);
- } else if (parameter.type === EIP712Types.Address) {
- encodedValues.push(eip712Utils.pad32Address(value));
- } else {
- throw new Error(`Unable to encode ${parameter.type}`);
- }
- }
- return encodedValues;
- },
-};
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index e7a23682c..7194b9780 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -2,7 +2,6 @@ export { orderHashUtils } from './order_hash';
export { signatureUtils } from './signature_utils';
export { generatePseudoRandomSalt } from './salt';
export { assetDataUtils } from './asset_data_utils';
-export { eip712Utils } from './eip712_utils';
export { marketUtils } from './market_utils';
export { rateUtils } from './rate_utils';
export { sortingUtils } from './sorting_utils';
@@ -36,9 +35,6 @@ export {
} from '@0xproject/types';
export {
OrderError,
- EIP712Parameter,
- EIP712Schema,
- EIP712Types,
TradeSide,
TransferType,
FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts
index 151c1f801..37b9da811 100644
--- a/packages/order-utils/src/order_hash.ts
+++ b/packages/order-utils/src/order_hash.ts
@@ -1,28 +1,28 @@
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
import { Order, SignedOrder } from '@0xproject/types';
+import { signTypedDataUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import { assert } from './assert';
-import { eip712Utils } from './eip712_utils';
-import { EIP712Schema, EIP712Types } from './types';
+import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants';
const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string';
-export const EIP712_ORDER_SCHEMA: EIP712Schema = {
+export const EIP712_ORDER_SCHEMA = {
name: 'Order',
parameters: [
- { name: 'makerAddress', type: EIP712Types.Address },
- { name: 'takerAddress', type: EIP712Types.Address },
- { name: 'feeRecipientAddress', type: EIP712Types.Address },
- { name: 'senderAddress', type: EIP712Types.Address },
- { name: 'makerAssetAmount', type: EIP712Types.Uint256 },
- { name: 'takerAssetAmount', type: EIP712Types.Uint256 },
- { name: 'makerFee', type: EIP712Types.Uint256 },
- { name: 'takerFee', type: EIP712Types.Uint256 },
- { name: 'expirationTimeSeconds', type: EIP712Types.Uint256 },
- { name: 'salt', type: EIP712Types.Uint256 },
- { name: 'makerAssetData', type: EIP712Types.Bytes },
- { name: 'takerAssetData', type: EIP712Types.Bytes },
+ { name: 'makerAddress', type: 'address' },
+ { name: 'takerAddress', type: 'address' },
+ { name: 'feeRecipientAddress', type: 'address' },
+ { name: 'senderAddress', type: 'address' },
+ { name: 'makerAssetAmount', type: 'uint256' },
+ { name: 'takerAssetAmount', type: 'uint256' },
+ { name: 'makerFee', type: 'uint256' },
+ { name: 'takerFee', type: 'uint256' },
+ { name: 'expirationTimeSeconds', type: 'uint256' },
+ { name: 'salt', type: 'uint256' },
+ { name: 'makerAssetData', type: 'bytes' },
+ { name: 'takerAssetData', type: 'bytes' },
],
};
@@ -69,11 +69,23 @@ export const orderHashUtils = {
* @return The resulting orderHash from hashing the supplied order as a Buffer
*/
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
- const orderParamsHashBuff = eip712Utils.structHash(EIP712_ORDER_SCHEMA, order);
- const orderHashBuff = eip712Utils.createEIP712Message(orderParamsHashBuff, order.exchangeAddress);
+ const normalizedOrder = _.mapValues(order, value => {
+ return _.isObject(value) ? value.toString() : value;
+ });
+ const typedData = {
+ types: {
+ EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters,
+ Order: EIP712_ORDER_SCHEMA.parameters,
+ },
+ domain: {
+ name: EIP712_DOMAIN_NAME,
+ version: EIP712_DOMAIN_VERSION,
+ verifyingContract: order.exchangeAddress,
+ },
+ message: normalizedOrder,
+ primaryType: EIP712_ORDER_SCHEMA.name,
+ };
+ const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData);
return orderHashBuff;
},
- _getOrderSchemaBuffer(): Buffer {
- return eip712Utils.compileSchema(EIP712_ORDER_SCHEMA);
- },
};
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts
index 8e0fd702b..2d7fcfc9e 100644
--- a/packages/order-utils/src/signature_utils.ts
+++ b/packages/order-utils/src/signature_utils.ts
@@ -7,7 +7,7 @@ import * as _ from 'lodash';
import { artifacts } from './artifacts';
import { assert } from './assert';
-import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './eip712_utils';
+import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants';
import { ExchangeContract } from './generated_contract_wrappers/exchange';
import { IValidatorContract } from './generated_contract_wrappers/i_validator';
import { IWalletContract } from './generated_contract_wrappers/i_wallet';
diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts
index a843efaa4..80075270e 100644
--- a/packages/order-utils/src/types.ts
+++ b/packages/order-utils/src/types.ts
@@ -14,24 +14,6 @@ export enum TransferType {
Fee = 'fee',
}
-export interface EIP712Parameter {
- name: string;
- type: EIP712Types;
-}
-
-export interface EIP712Schema {
- name: string;
- parameters: EIP712Parameter[];
-}
-
-export enum EIP712Types {
- Address = 'address',
- Bytes = 'bytes',
- Bytes32 = 'bytes32',
- String = 'string',
- Uint256 = 'uint256',
-}
-
export interface CreateOrderOpts {
takerAddress?: string;
senderAddress?: string;
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 9d01e5bc5..0723e5788 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -9,3 +9,4 @@ export { abiUtils } from './abi_utils';
export { NULL_BYTES } from './constants';
export { errorUtils } from './error_utils';
export { fetchAsync } from './fetch_async';
+export { signTypedDataUtils } from './sign_typed_data_utils';
diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts
new file mode 100644
index 000000000..902d8530c
--- /dev/null
+++ b/packages/utils/src/sign_typed_data_utils.ts
@@ -0,0 +1,81 @@
+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;
+}
+
+export const signTypedDataUtils = {
+ 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)) {
+ if (!found.includes(dep)) {
+ found.push(dep);
+ }
+ }
+ }
+ return found;
+ },
+ 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 = '';
+ for (const dep of deps) {
+ result += `${dep}(${types[dep].map(({ name, type }) => `${type} ${name}`).join(',')})`;
+ }
+ return result;
+ },
+ encodeData(primaryType: string, data: any, types: EIP712Types): string {
+ const encodedTypes = ['bytes32'];
+ const encodedValues = [signTypedDataUtils.typeHash(primaryType, types)];
+ for (const field of types[primaryType]) {
+ let value = data[field.name];
+ if (field.type === 'string' || field.type === 'bytes') {
+ value = ethUtil.sha3(value);
+ encodedTypes.push('bytes32');
+ encodedValues.push(value);
+ } else if (types[field.type] !== undefined) {
+ encodedTypes.push('bytes32');
+ 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');
+ } else {
+ encodedTypes.push(field.type);
+ encodedValues.push(value);
+ }
+ }
+ return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues);
+ },
+ 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),
+ ]),
+ );
+ },
+};
diff --git a/packages/utils/test/sign_typed_data_utils_test.ts b/packages/utils/test/sign_typed_data_utils_test.ts
new file mode 100644
index 000000000..b21ffefa0
--- /dev/null
+++ b/packages/utils/test/sign_typed_data_utils_test.ts
@@ -0,0 +1,107 @@
+import * as chai from 'chai';
+import 'mocha';
+
+import { signTypedDataUtils } from '../src/sign_typed_data_utils';
+
+const expect = chai.expect;
+
+describe('signTypedDataUtils', () => {
+ describe('signTypedDataHash', () => {
+ const signTypedDataHashHex = '0x55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692';
+ const signTypedData = {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Order: [
+ {
+ name: 'makerAddress',
+ type: 'address',
+ },
+ {
+ name: 'takerAddress',
+ type: 'address',
+ },
+ {
+ name: 'feeRecipientAddress',
+ type: 'address',
+ },
+ {
+ name: 'senderAddress',
+ type: 'address',
+ },
+ {
+ name: 'makerAssetAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'takerAssetAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'makerFee',
+ type: 'uint256',
+ },
+ {
+ name: 'takerFee',
+ type: 'uint256',
+ },
+ {
+ name: 'expirationTimeSeconds',
+ type: 'uint256',
+ },
+ {
+ name: 'salt',
+ type: 'uint256',
+ },
+ {
+ name: 'makerAssetData',
+ type: 'bytes',
+ },
+ {
+ name: 'takerAssetData',
+ type: 'bytes',
+ },
+ ],
+ },
+ domain: {
+ name: '0x Protocol',
+ version: '2',
+ verifyingContract: '0x0000000000000000000000000000000000000000',
+ },
+ message: {
+ makerAddress: '0x0000000000000000000000000000000000000000',
+ takerAddress: '0x0000000000000000000000000000000000000000',
+ makerAssetAmount: '1000000000000000000',
+ takerAssetAmount: '1000000000000000000',
+ expirationTimeSeconds: '12345',
+ makerFee: '0',
+ takerFee: '0',
+ feeRecipientAddress: '0x0000000000000000000000000000000000000000',
+ senderAddress: '0x0000000000000000000000000000000000000000',
+ salt: '12345',
+ makerAssetData: '0x0000000000000000000000000000000000000000',
+ takerAssetData: '0x0000000000000000000000000000000000000000',
+ exchangeAddress: '0x0000000000000000000000000000000000000000',
+ },
+ primaryType: 'Order',
+ };
+ it.only('creates a known hash of the sign typed data', () => {
+ const hash = signTypedDataUtils.signTypedDataHash(signTypedData).toString('hex');
+ const hashHex = `0x${hash}`;
+ expect(hashHex).to.be.eq(signTypedDataHashHex);
+ console.log(hash);
+ });
+ });
+});