Add missing EIP191 prefix for EIP712
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
63
packages/contracts/src/utils/eip712_utils.ts
Normal file
63
packages/contracts/src/utils/eip712_utils.ts
Normal 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;
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user