diff options
10 files changed, 279 insertions, 50 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol index 30b0102fd..e3f6b2b2b 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol @@ -20,8 +20,11 @@ pragma solidity ^0.4.24; import "./libs/LibExchangeErrors.sol"; import "./mixins/MSignatureValidator.sol"; import "./mixins/MTransactions.sol"; +import "./libs/LibExchangeErrors.sol"; +import "./libs/LibEIP712.sol"; contract MixinTransactions is + LibEIP712, LibExchangeErrors, MSignatureValidator, MTransactions @@ -34,6 +37,30 @@ contract MixinTransactions is // Address of current transaction signer address public currentContextAddress; + bytes32 constant EXECUTE_TRANSACTION_SCHEMA_HASH = keccak256( + "ExecuteTransaction(", + "uint256 salt,", + "address signer,", + "bytes data", + ")" + ); + + function getExecuteTransactionHash(uint256 salt, address signer, bytes data) + internal + view + returns (bytes32 executeTransactionHash) + { + executeTransactionHash = createEIP712Message( + keccak256( + EXECUTE_TRANSACTION_SCHEMA_HASH, + salt, + bytes32(signer), + keccak256(data) + ) + ); + return executeTransactionHash; + } + /// @dev Executes an exchange method call in the context of signer. /// @param salt Arbitrary number to ensure uniqueness of transaction hash. /// @param signer Address of transaction signer. @@ -53,13 +80,7 @@ contract MixinTransactions is REENTRANCY_ILLEGAL ); - // Calculate transaction hash - bytes32 transactionHash = keccak256(abi.encodePacked( - address(this), - signer, - salt, - data - )); + bytes32 transactionHash = getExecuteTransactionHash(salt, signer, data); // Validate transaction has not been executed require( diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol new file mode 100644 index 000000000..d28ef876f --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibEIP712.sol @@ -0,0 +1,55 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +contract LibEIP712 { + string public constant EIP191_HEADER = "\x19\x01"; + bytes32 public constant EIP712_DOMAIN_SEPARATOR_NAME_HASH = keccak256("0x Protocol"); + + bytes32 public constant EIP712_DOMAIN_SEPARATOR_VERSION_HASH = keccak256("1"); + + bytes32 public constant EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256( + "DomainSeparator(", + "string name,", + "string version,", + "address contract", + ")" + ); + + function createEIP712Message(bytes32 hashStruct) + internal + view + returns (bytes32 message) + { + // TODO: EIP712 is not finalized yet + // Source: https://github.com/ethereum/EIPs/pull/712 + // TODO: Cache the Domain Separator + message = keccak256( + EIP191_HEADER, + keccak256( + EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, + EIP712_DOMAIN_SEPARATOR_NAME_HASH, + EIP712_DOMAIN_SEPARATOR_VERSION_HASH, + bytes32(address(this)) + ), + hashStruct + ); + return message; + } +}
\ No newline at end of file diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol index 05ea27ffc..6888918e2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol @@ -18,27 +18,28 @@ pragma solidity ^0.4.24; -contract LibOrder { +import "./LibEIP712.sol"; - bytes32 constant DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked( - "DomainSeparator(address contract)" - )); +contract LibOrder is + LibEIP712 +{ - bytes32 constant ORDER_SCHEMA_HASH = keccak256(abi.encodePacked( - "Order(", - "address makerAddress,", - "address takerAddress,", - "address feeRecipientAddress,", - "address senderAddress,", - "uint256 makerAssetAmount,", - "uint256 takerAssetAmount,", - "uint256 makerFee,", - "uint256 takerFee,", - "uint256 expirationTimeSeconds,", - "uint256 salt,", - "bytes makerAssetData,", - "bytes takerAssetData,", - ")" + bytes32 constant EIP712_ORDER_SCHEMA_HASH = keccak256( + abi.encodePacked( + "Order(", + "address makerAddress,", + "address takerAddress,", + "address feeRecipientAddress,", + "address senderAddress,", + "uint256 makerAssetAmount,", + "uint256 takerAssetAmount,", + "uint256 makerFee,", + "uint256 takerFee,", + "uint256 expirationTimeSeconds,", + "uint256 salt,", + "bytes makerAssetData,", + "bytes takerAssetData", + ")" )); // A valid order remains fillable until it is expired, fully filled, or cancelled. @@ -85,17 +86,14 @@ contract LibOrder { view returns (bytes32 orderHash) { - // TODO: EIP712 is not finalized yet - // Source: https://github.com/ethereum/EIPs/pull/712 - orderHash = keccak256(abi.encodePacked( - DOMAIN_SEPARATOR_SCHEMA_HASH, - keccak256(abi.encodePacked(address(this))), - ORDER_SCHEMA_HASH, - keccak256(abi.encodePacked( - order.makerAddress, - order.takerAddress, - order.feeRecipientAddress, - order.senderAddress, + orderHash = createEIP712Message( + keccak256( + abi.encodePacked( + EIP712_ORDER_SCHEMA_HASH, + bytes32(order.makerAddress), + bytes32(order.takerAddress), + bytes32(order.feeRecipientAddress), + bytes32(order.senderAddress), order.makerAssetAmount, order.takerAssetAmount, order.makerFee, @@ -104,8 +102,7 @@ contract LibOrder { order.salt, keccak256(abi.encodePacked(order.makerAssetData)), keccak256(abi.encodePacked(order.takerAssetData)) - )) - )); + ))); return orderHash; } } diff --git a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol index 47ce0dcf3..010080703 100644 --- a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol +++ b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol @@ -74,7 +74,7 @@ contract TestLibs is pure returns (bytes32) { - return ORDER_SCHEMA_HASH; + return EIP712_ORDER_SCHEMA_HASH; } function getDomainSeparatorSchemaHash() @@ -82,7 +82,7 @@ contract TestLibs is pure returns (bytes32) { - return DOMAIN_SEPARATOR_SCHEMA_HASH; + return EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH; } function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) diff --git a/packages/contracts/src/utils/eip712_utils.ts b/packages/contracts/src/utils/eip712_utils.ts new file mode 100644 index 000000000..07b45c8af --- /dev/null +++ b/packages/contracts/src/utils/eip712_utils.ts @@ -0,0 +1,63 @@ +import { BigNumber } from '@0xproject/utils'; +import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; + +import { crypto } from './crypto'; +import { EIP712Schema } from './types'; + +const EIP191_PREFIX = '\x19\x01'; +const EIP712_DOMAIN_NAME = '0x Protocol'; +const EIP712_DOMAIN_VERSION = '1'; + +const EIP712_DOMAIN_SCHEMA: EIP712Schema = { + name: 'DomainSeparator', + parameters: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'contract', type: 'address' }, + ], +}; + +export const EIP712Utils = { + compileSchema(schema: EIP712Schema): Buffer { + const namedTypes = _.map(schema.parameters, parameter => `${parameter.type} ${parameter.name}`); + const namedTypesJoined = namedTypes.join(','); + const eip712Schema = `${schema.name}(${namedTypesJoined})`; + const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]); + return eip712SchemaHashBuffer; + }, + createEIP712Message(hashStruct: string, contractAddress: string): Buffer { + const domainSeparatorHashHex = EIP712Utils.getDomainSeparatorHashHex(contractAddress); + const messageBuff = crypto.solSHA3([ + EIP191_PREFIX, + new BigNumber(domainSeparatorHashHex), + new BigNumber(hashStruct), + ]); + return messageBuff; + }, + getDomainSeparatorSchemaBuffer(): Buffer { + return EIP712Utils.compileSchema(EIP712_DOMAIN_SCHEMA); + }, + getDomainSeparatorHashHex(exchangeAddress: string): string { + const domainSeparatorSchemaBuffer = EIP712Utils.getDomainSeparatorSchemaBuffer(); + const nameHash = crypto.solSHA3([EIP712_DOMAIN_NAME]); + const versionHash = crypto.solSHA3([EIP712_DOMAIN_VERSION]); + const domainSeparatorHashBuff = crypto.solSHA3([ + domainSeparatorSchemaBuffer, + nameHash, + versionHash, + EIP712Utils.pad32Address(exchangeAddress), + ]); + const domainSeparatorHashHex = `0x${domainSeparatorHashBuff.toString('hex')}`; + return domainSeparatorHashHex; + }, + pad32Address(address: string): Buffer { + const addressBuffer = ethUtil.toBuffer(address); + const addressPadded = EIP712Utils.pad32Buffer(addressBuffer); + return addressPadded; + }, + pad32Buffer(buffer: Buffer): Buffer { + const bufferPadded = ethUtil.setLengthLeft(buffer, 32); + return bufferPadded; + }, +}; diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts index af411c01f..bfe5013e8 100644 --- a/packages/contracts/src/utils/order_factory.ts +++ b/packages/contracts/src/utils/order_factory.ts @@ -1,5 +1,5 @@ import { generatePseudoRandomSalt, orderHashUtils } from '@0xproject/order-utils'; -import { Order, SignatureType, SignedOrder } from '@0xproject/types'; +import { Order, SignatureType, SignedOrder, UnsignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -26,8 +26,8 @@ export class OrderFactory { takerAddress: constants.NULL_ADDRESS, ...this._defaultOrderParams, ...customOrderParams, - } as any) as Order; - const orderHashBuff = orderHashUtils.getOrderHashBuff(order); + } as any) as UnsignedOrder; + const orderHashBuff = orderHashUtils.getOrderHashBuffer(order); const signature = signingUtils.signMessage(orderHashBuff, this._privateKey, signatureType); const signedOrder = { ...order, diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 0d0329aa1..316350ffb 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -1,8 +1,29 @@ -import { Order, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; +import { Order, OrderWithoutExchangeAddress, SignedOrder, UnsignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; -import { CancelOrder, MatchOrder } from './types'; +import { crypto } from './crypto'; +import { EIP712Utils } from './eip712_utils'; +import { CancelOrder, EIP712Schema, MatchOrder } from './types'; + +const EIP712_ORDER_SCHEMA: EIP712Schema = { + name: 'Order', + parameters: [ + { 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' }, + ], +}; export const orderUtils = { createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => { @@ -37,6 +58,37 @@ export const orderUtils = { }; return orderStruct; }, + getOrderSchemaBuffer(): Buffer { + return EIP712Utils.compileSchema(EIP712_ORDER_SCHEMA); + }, + getOrderHashBuffer(order: SignedOrder | UnsignedOrder): Buffer { + const makerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.makerAssetData)]); + const takerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.takerAssetData)]); + + const orderParamsHashBuff = crypto.solSHA3([ + orderUtils.getOrderSchemaBuffer(), + EIP712Utils.pad32Address(order.makerAddress), + EIP712Utils.pad32Address(order.takerAddress), + EIP712Utils.pad32Address(order.feeRecipientAddress), + EIP712Utils.pad32Address(order.senderAddress), + order.makerAssetAmount, + order.takerAssetAmount, + order.makerFee, + order.takerFee, + order.expirationTimeSeconds, + order.salt, + makerAssetDataHash, + takerAssetDataHash, + ]); + const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`; + const orderHashBuff = EIP712Utils.createEIP712Message(orderParamsHashHex, order.exchangeAddress); + return orderHashBuff; + }, + getOrderHashHex(order: SignedOrder | UnsignedOrder): string { + const orderHashBuff = orderUtils.getOrderHashBuffer(order); + const orderHashHex = `0x${orderHashBuff.toString('hex')}`; + return orderHashHex; + }, createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder): MatchOrder { const fill = { left: orderUtils.getOrderWithoutExchangeAddress(signedOrderLeft), diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts index 434611908..1c1029d62 100644 --- a/packages/contracts/src/utils/transaction_factory.ts +++ b/packages/contracts/src/utils/transaction_factory.ts @@ -2,9 +2,22 @@ import { crypto, generatePseudoRandomSalt } from '@0xproject/order-utils'; import { SignatureType } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import { crypto } from './crypto'; +import { EIP712Utils } from './eip712_utils'; +import { orderUtils } from './order_utils'; import { signingUtils } from './signing_utils'; -import { SignedTransaction } from './types'; +import { EIP712Schema, SignatureType, SignedTransaction } from './types'; + +const EIP712_EXECUTE_TRANSACTION_SCHEMA: EIP712Schema = { + name: 'ExecuteTransaction', + parameters: [ + { name: 'salt', type: 'uint256' }, + { name: 'signer', type: 'address' }, + { name: 'data', type: 'bytes' }, + ], +}; export class TransactionFactory { private _signerBuff: Buffer; @@ -16,8 +29,21 @@ export class TransactionFactory { this._signerBuff = ethUtil.privateToAddress(this._privateKey); } public newSignedTransaction(data: string, signatureType: SignatureType = SignatureType.EthSign): SignedTransaction { + const executeTransactionSchemaHashBuff = EIP712Utils.compileSchema(EIP712_EXECUTE_TRANSACTION_SCHEMA); + const salt = generatePseudoRandomSalt(); - const txHash = crypto.solSHA3([this._exchangeAddress, this._signerBuff, salt, ethUtil.toBuffer(data)]); + const dataHash = crypto.solSHA3([ethUtil.toBuffer(data)]); + + const executeTransactionDataHash = crypto.solSHA3([ + executeTransactionSchemaHashBuff, + salt, + EIP712Utils.pad32Buffer(this._signerBuff), + dataHash, + ]); + + const executeTransactionMessageHex = `0x${executeTransactionDataHash.toString('hex')}`; + + const txHash = EIP712Utils.createEIP712Message(executeTransactionMessageHex, this._exchangeAddress); const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType); const signedTx = { exchangeAddress: this._exchangeAddress, diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 360e1fdbc..5b7f1bcee 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -147,3 +147,13 @@ export interface MatchOrder { leftSignature: string; rightSignature: string; } + +export interface EIP712Parameter { + name: string; + type: string; +} + +export interface EIP712Schema { + name: string; + parameters: EIP712Parameter[]; +} diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts index 10cb8b34e..b6de4d3b0 100644 --- a/packages/contracts/test/exchange/libs.ts +++ b/packages/contracts/test/exchange/libs.ts @@ -10,6 +10,7 @@ import { addressUtils } from '../../src/utils/address_utils'; import { artifacts } from '../../src/utils/artifacts'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; +import { EIP712Utils } from '../../src/utils/eip712_utils'; import { OrderFactory } from '../../src/utils/order_factory'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; @@ -57,13 +58,17 @@ describe('Exchange libs', () => { describe('getOrderSchema', () => { it('should output the correct order schema hash', async () => { const orderSchema = await libs.getOrderSchemaHash.callAsync(); - expect(orderHashUtils._getOrderSchemaHex()).to.be.equal(orderSchema); + const orderSchemaBuffer = orderHashUtils._getOrderSchemaHex(); + const schemaHashHex = `0x${orderSchemaBuffer.toString('hex')}`; + expect(schemaHashHex).to.be.equal(orderSchema); }); }); describe('getDomainSeparatorSchema', () => { it('should output the correct domain separator schema hash', async () => { const domainSeparatorSchema = await libs.getDomainSeparatorSchemaHash.callAsync(); - expect(orderHashUtils._getDomainSeparatorSchemaHex()).to.be.equal(domainSeparatorSchema); + const domainSchemaBuffer = EIP712Utils.getDomainSeparatorSchemaBuffer(); + const schemaHashHex = `0x${domainSchemaBuffer.toString('hex')}`; + expect(schemaHashHex).to.be.equal(domainSeparatorSchema); }); }); describe('getOrderHash', () => { |