Move SignTypedData to utils package
This commit is contained in:
parent
07926ded6e
commit
2a82ff48c0
@ -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' },
|
||||
],
|
||||
};
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
@ -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,
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
81
packages/utils/src/sign_typed_data_utils.ts
Normal file
81
packages/utils/src/sign_typed_data_utils.ts
Normal file
@ -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),
|
||||
]),
|
||||
);
|
||||
},
|
||||
};
|
107
packages/utils/test/sign_typed_data_utils_test.ts
Normal file
107
packages/utils/test/sign_typed_data_utils_test.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user