diff options
Diffstat (limited to 'packages/utils')
-rw-r--r-- | packages/utils/src/index.ts | 1 | ||||
-rw-r--r-- | packages/utils/src/sign_typed_data_utils.ts | 81 | ||||
-rw-r--r-- | packages/utils/test/sign_typed_data_utils_test.ts | 107 |
3 files changed, 189 insertions, 0 deletions
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); + }); + }); +}); |