diff options
author | Brandon Millman <brandon.millman@gmail.com> | 2018-10-10 08:04:54 +0800 |
---|---|---|
committer | Brandon Millman <brandon.millman@gmail.com> | 2018-10-10 08:04:54 +0800 |
commit | 8155d311af04339c105f1a29b74f1ddbced85197 (patch) | |
tree | 9b345f05561e806b6d89cec26b371aadafb109ed /packages/utils | |
parent | cd8e6d9cdf7d6aa1e811bb7d9d7017da342907ed (diff) | |
parent | bd06ebde8d1a5caf138ad3b36f9a5c5f255f2312 (diff) | |
download | dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.tar dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.tar.gz dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.tar.bz2 dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.tar.lz dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.tar.xz dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.tar.zst dexon-sol-tools-8155d311af04339c105f1a29b74f1ddbced85197.zip |
Merge branch 'development' into feature/asset-buyer/api-tweaks
* development:
Define bundlewatch.ci.repoBranchBase
Stop accesing ethers private methods
Fix merge conflicts
Fix ethers build issue
Upgrade ethers.js version
Fix branch name
Add max sizes
Use bundlewatch instead of bundlesize
Move Metamask Error to OrderErrors
Update the exported types for the packages which touch RPC providers
Throw and handle errors from Providers.
Clarifies use of schemas outside of Javascript/TypeScript.
Detect MM on signature validation failure.
Return SignedOrder from signing utils.
Update 0x.js Changelog
Add eth_signTypedData support to our wallet subproviders
Move SignTypedData to utils package
Introduce Metamask Subprovider.
Expose eth_signTypedData functionality for order signing
Diffstat (limited to 'packages/utils')
-rw-r--r-- | packages/utils/package.json | 2 | ||||
-rw-r--r-- | packages/utils/src/abi_decoder.ts | 6 | ||||
-rw-r--r-- | packages/utils/src/index.ts | 1 | ||||
-rw-r--r-- | packages/utils/src/sign_typed_data_utils.ts | 82 | ||||
-rw-r--r-- | packages/utils/test/sign_typed_data_utils_test.ts | 140 |
5 files changed, 227 insertions, 4 deletions
diff --git a/packages/utils/package.json b/packages/utils/package.json index f1017f84d..20b0f0c86 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -50,7 +50,7 @@ "detect-node": "2.0.3", "ethereum-types": "^1.0.11", "ethereumjs-util": "^5.1.1", - "ethers": "4.0.0-beta.14", + "ethers": "~4.0.4", "isomorphic-fetch": "^2.2.1", "js-sha3": "^0.7.0", "lodash": "^4.17.5" diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index ea8c91d10..ac3e54efb 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -9,7 +9,7 @@ import { RawLog, SolidityTypes, } from 'ethereum-types'; -import * as ethers from 'ethers'; +import { ethers } from 'ethers'; import * as _ from 'lodash'; import { addressUtils } from './address_utils'; @@ -41,7 +41,7 @@ export class AbiDecoder { return log; } const event = this._methodIds[methodId][numIndexedArgs]; - const ethersInterface = new ethers.Interface([event]); + const ethersInterface = new ethers.utils.Interface([event]); const decodedParams: DecodedLogArgs = {}; let topicsIndex = 1; @@ -96,7 +96,7 @@ export class AbiDecoder { if (_.isUndefined(abiArray)) { return; } - const ethersInterface = new ethers.Interface(abiArray); + const ethersInterface = new ethers.utils.Interface(abiArray); _.map(abiArray, (abi: AbiDefinition) => { if (abi.type === AbiType.Event) { const topic = ethersInterface.events[abi.name].topic; 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..cd5bcb42f --- /dev/null +++ b/packages/utils/src/sign_typed_data_utils.ts @@ -0,0 +1,82 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as ethers from 'ethers'; +import * as _ from 'lodash'; + +import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0xproject/types'; + +export const signTypedDataUtils = { + /** + * Generates the EIP712 Typed Data hash for signing + * @param typedData An object that conforms to the EIP712TypedData interface + * @return A Buffer containing the hash of the typed data. + */ + generateTypedDataHash(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)) { + 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: EIP712Object, types: EIP712Types): string { + const encodedTypes = ['bytes32']; + const encodedValues: Array<Buffer | EIP712ObjectValue> = [signTypedDataUtils._typeHash(primaryType, types)]; + for (const field of types[primaryType]) { + const value = data[field.name]; + if (field.type === 'string' || field.type === 'bytes') { + const hashValue = ethUtil.sha3(value as string); + encodedTypes.push('bytes32'); + encodedValues.push(hashValue); + } else if (types[field.type] !== undefined) { + encodedTypes.push('bytes32'); + const hashValue = ethUtil.sha3( + // tslint:disable-next-line:no-unnecessary-type-assertion + signTypedDataUtils._encodeData(field.type, value as EIP712Object, types), + ); + encodedValues.push(hashValue); + } else if (field.type.lastIndexOf(']') === field.type.length - 1) { + throw new Error('Arrays currently unimplemented in encodeData'); + } else { + encodedTypes.push(field.type); + const normalizedValue = signTypedDataUtils._normalizeValue(field.type, value); + encodedValues.push(normalizedValue); + } + } + return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues); + }, + _normalizeValue(type: string, value: any): EIP712ObjectValue { + const normalizedValue = type === 'uint256' && _.isObject(value) && value.isBigNumber ? value.toString() : value; + return normalizedValue; + }, + _typeHash(primaryType: string, types: EIP712Types): Buffer { + return ethUtil.sha3(signTypedDataUtils._encodeType(primaryType, types)); + }, + _structHash(primaryType: string, data: EIP712Object, 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 new file mode 100644 index 000000000..dcba08b04 --- /dev/null +++ b/packages/utils/test/sign_typed_data_utils_test.ts @@ -0,0 +1,140 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { signTypedDataUtils } from '../src/sign_typed_data_utils'; + +const expect = chai.expect; + +describe('signTypedDataUtils', () => { + describe('signTypedDataHash', () => { + 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: [ + { + 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('creates a hash of the test sign typed data', () => { + const hash = signTypedDataUtils.generateTypedDataHash(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.generateTypedDataHash(orderSignTypedData).toString('hex'); + const hashHex = `0x${hash}`; + expect(hashHex).to.be.eq(orderSignTypedDataHashHex); + }); + }); +}); |