From 2a82ff48c061eacb3b6f9fb36eeae7f515b6d11d Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 4 Oct 2018 17:12:35 +1000 Subject: Move SignTypedData to utils package --- packages/utils/src/index.ts | 1 + packages/utils/src/sign_typed_data_utils.ts | 81 +++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 packages/utils/src/sign_typed_data_utils.ts (limited to 'packages/utils/src') 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), + ]), + ); + }, +}; -- cgit v1.2.3 From 3e2fe40a11919f09f1f454c71f02aaa147b46b0c Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 4 Oct 2018 17:32:54 +1000 Subject: Add eth_signTypedData support to our wallet subproviders --- packages/utils/src/sign_typed_data_utils.ts | 60 +++++++++++++---------------- 1 file changed, 26 insertions(+), 34 deletions(-) (limited to 'packages/utils/src') 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)); }, }; -- cgit v1.2.3 From 75d274f330dc0c18577e764ca77ffb36d5a3f27e Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 5 Oct 2018 11:45:53 +1000 Subject: Return SignedOrder from signing utils. Create a helper back in EIP712Utils for code cleanup. Moved constants in order-utils into the constants object --- packages/utils/src/sign_typed_data_utils.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'packages/utils/src') diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts index b72fd099b..657a59ed0 100644 --- a/packages/utils/src/sign_typed_data_utils.ts +++ b/packages/utils/src/sign_typed_data_utils.ts @@ -1,7 +1,8 @@ import * as ethUtil from 'ethereumjs-util'; import * as ethers from 'ethers'; +import * as _ from 'lodash'; -import { EIP712TypedData, EIP712Types } from '@0xproject/types'; +import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0xproject/types'; export const signTypedDataUtils = { /** @@ -42,32 +43,40 @@ export const signTypedDataUtils = { } return result; }, - _encodeData(primaryType: string, data: any, types: EIP712Types): string { + _encodeData(primaryType: string, data: EIP712Object, types: EIP712Types): string { const encodedTypes = ['bytes32']; - const encodedValues = [signTypedDataUtils._typeHash(primaryType, types)]; + const encodedValues: Array = [signTypedDataUtils._typeHash(primaryType, types)]; for (const field of types[primaryType]) { - let value = data[field.name]; + const value = data[field.name]; if (field.type === 'string' || field.type === 'bytes') { - value = ethUtil.sha3(value); + const hashValue = ethUtil.sha3(value as string); encodedTypes.push('bytes32'); - encodedValues.push(value); + encodedValues.push(hashValue); } else if (types[field.type] !== undefined) { encodedTypes.push('bytes32'); - value = ethUtil.sha3(signTypedDataUtils._encodeData(field.type, value, types)); - encodedValues.push(value); + 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); - encodedValues.push(value); + 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: any, types: EIP712Types): Buffer { + _structHash(primaryType: string, data: EIP712Object, types: EIP712Types): Buffer { return ethUtil.sha3(signTypedDataUtils._encodeData(primaryType, data, types)); }, }; -- cgit v1.2.3 From 9e8031d5e3cf94cabe07685be510397367e90413 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 9 Oct 2018 18:26:13 +1100 Subject: Throw and handle errors from Providers. In web3 wrapper when a response contains an error field we throw this rather than return response.result which is often undefined. In Signature Utils we handle the error thrown when a user rejects the signing dialogue to prevent double signing. Exposed the ZeroExTransaction JSON schema. In Website only use the MetamaskSubprovider if we can detect the provider is Metamask --- packages/utils/src/sign_typed_data_utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/utils/src') diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts index 657a59ed0..cd5bcb42f 100644 --- a/packages/utils/src/sign_typed_data_utils.ts +++ b/packages/utils/src/sign_typed_data_utils.ts @@ -6,11 +6,11 @@ import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@ export const signTypedDataUtils = { /** - * Computes the Sign Typed Data hash + * 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 sign typed data. + * @return A Buffer containing the hash of the typed data. */ - signTypedDataHash(typedData: EIP712TypedData): Buffer { + generateTypedDataHash(typedData: EIP712TypedData): Buffer { return ethUtil.sha3( Buffer.concat([ Buffer.from('1901', 'hex'), -- cgit v1.2.3