Add missing EIP191 prefix for EIP712

This commit is contained in:
Jacob Evans
2018-05-29 17:11:33 -07:00
parent 70858603ed
commit 4670cc1a5f
10 changed files with 279 additions and 50 deletions

View File

@@ -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(

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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;
},
};

View File

@@ -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,

View File

@@ -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),

View File

@@ -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,

View File

@@ -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[];
}

View File

@@ -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', () => {