Merge pull request #1705 from merklejerk/feature/contracts/coordinator-tx-eip712-mixed-domains

Separate domains for Coordinator transactions and approvals
This commit is contained in:
Amir Bandeali
2019-03-16 16:21:01 -07:00
committed by GitHub
25 changed files with 226 additions and 238 deletions

View File

@@ -4,6 +4,14 @@
"changes": [
{
"note": "Created Coordinator package"
},
{
"note": "Use separate EIP712 domains for transactions and approvals",
"pr": 1705
},
{
"note": "Add `SignatureType.Invalid`",
"pr": 1705
}
]
}

View File

@@ -125,14 +125,14 @@ contract MixinCoordinatorApprovalVerifier is
// Hash approval message and recover signer address
bytes32 approvalHash = getCoordinatorApprovalHash(approval);
address approvalSignerAddress = getSignerAddress(approvalHash, approvalSignatures[i]);
// Add approval signer to list of signers
approvalSignerAddresses = approvalSignerAddresses.append(approvalSignerAddress);
}
// Ethereum transaction signer gives implicit signature of approval
approvalSignerAddresses = approvalSignerAddresses.append(tx.origin);
uint256 ordersLength = orders.length;
for (uint256 i = 0; i != ordersLength; i++) {
// Do not check approval if the order's senderAddress is null

View File

@@ -59,6 +59,17 @@ contract MixinSignatureValidator is
if (signatureType == SignatureType.Illegal) {
revert("SIGNATURE_ILLEGAL");
// Always invalid signature.
// Like Illegal, this is always implicitly available and therefore
// offered explicitly. It can be implicitly created by providing
// a correctly formatted but incorrect signature.
} else if (signatureType == SignatureType.Invalid) {
require(
signature.length == 0,
"LENGTH_0_REQUIRED"
);
revert("SIGNATURE_INVALID");
// Signature using EIP712
} else if (signatureType == SignatureType.EIP712) {
require(

View File

@@ -50,7 +50,7 @@ contract LibCoordinatorApproval is
view
returns (bytes32 approvalHash)
{
approvalHash = hashEIP712Message(hashCoordinatorApproval(approval));
approvalHash = hashEIP712CoordinatorMessage(hashCoordinatorApproval(approval));
return approvalHash;
}
@@ -79,7 +79,7 @@ contract LibCoordinatorApproval is
assembly {
// Compute hash of transaction signature
let transactionSignatureHash := keccak256(add(transactionSignature, 32), mload(transactionSignature))
// Load free memory pointer
let memPtr := mload(64)

View File

@@ -18,17 +18,27 @@
pragma solidity ^0.5.5;
import "./LibConstants.sol";
contract LibEIP712Domain {
contract LibEIP712Domain is
LibConstants
{
// EIP191 header for EIP712 prefix
string constant internal EIP191_HEADER = "\x19\x01";
// EIP712 Domain Name value
string constant internal EIP712_DOMAIN_NAME = "0x Protocol Coordinator";
// EIP712 Domain Name value for the Coordinator
string constant internal EIP712_COORDINATOR_DOMAIN_NAME = "0x Protocol Coordinator";
// EIP712 Domain Version value
string constant internal EIP712_DOMAIN_VERSION = "1.0.0";
// EIP712 Domain Version value for the Coordinator
string constant internal EIP712_COORDINATOR_DOMAIN_VERSION = "1.0.0";
// EIP712 Domain Name value for the Exchange
string constant internal EIP712_EXCHANGE_DOMAIN_NAME = "0x Protocol";
// EIP712 Domain Version value for the Exchange
string constant internal EIP712_EXCHANGE_DOMAIN_VERSION = "2";
// Hash of the EIP712 Domain Separator Schema
bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
@@ -39,36 +49,70 @@ contract LibEIP712Domain {
")"
));
// Hash of the EIP712 Domain Separator data
// Hash of the EIP712 Domain Separator data for the Coordinator
// solhint-disable-next-line var-name-mixedcase
bytes32 public EIP712_DOMAIN_HASH;
bytes32 public EIP712_COORDINATOR_DOMAIN_HASH;
// Hash of the EIP712 Domain Separator data for the Exchange
// solhint-disable-next-line var-name-mixedcase
bytes32 public EIP712_EXCHANGE_DOMAIN_HASH;
constructor ()
public
{
EIP712_DOMAIN_HASH = keccak256(abi.encodePacked(
EIP712_COORDINATOR_DOMAIN_HASH = keccak256(abi.encodePacked(
EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
keccak256(bytes(EIP712_DOMAIN_NAME)),
keccak256(bytes(EIP712_DOMAIN_VERSION)),
keccak256(bytes(EIP712_COORDINATOR_DOMAIN_NAME)),
keccak256(bytes(EIP712_COORDINATOR_DOMAIN_VERSION)),
uint256(address(this))
));
EIP712_EXCHANGE_DOMAIN_HASH = keccak256(abi.encodePacked(
EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
keccak256(bytes(EIP712_EXCHANGE_DOMAIN_NAME)),
keccak256(bytes(EIP712_EXCHANGE_DOMAIN_VERSION)),
uint256(address(EXCHANGE))
));
}
/// @dev Calculates EIP712 encoding for a hash struct in this EIP712 Domain.
/// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain
/// of this contract.
/// @param hashStruct The EIP712 hash struct.
/// @return EIP712 hash applied to this EIP712 Domain.
function hashEIP712Message(bytes32 hashStruct)
function hashEIP712CoordinatorMessage(bytes32 hashStruct)
internal
view
returns (bytes32 result)
{
bytes32 eip712DomainHash = EIP712_DOMAIN_HASH;
return hashEIP712Message(EIP712_COORDINATOR_DOMAIN_HASH, hashStruct);
}
/// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain
/// of the Exchange contract.
/// @param hashStruct The EIP712 hash struct.
/// @return EIP712 hash applied to the Exchange EIP712 Domain.
function hashEIP712ExchangeMessage(bytes32 hashStruct)
internal
view
returns (bytes32 result)
{
return hashEIP712Message(EIP712_EXCHANGE_DOMAIN_HASH, hashStruct);
}
/// @dev Calculates EIP712 encoding for a hash struct with a given domain hash.
/// @param eip712DomainHash Hash of the domain domain separator data.
/// @param hashStruct The EIP712 hash struct.
/// @return EIP712 hash applied to the Exchange EIP712 Domain.
function hashEIP712Message(bytes32 eip712DomainHash, bytes32 hashStruct)
internal
pure
returns (bytes32 result)
{
// Assembly for more efficient computing:
// keccak256(abi.encodePacked(
// EIP191_HEADER,
// EIP712_DOMAIN_HASH,
// hashStruct
// hashStruct
// ));
assembly {

View File

@@ -48,8 +48,8 @@ contract LibZeroExTransaction is
view
returns (bytes32 transactionHash)
{
// Note: this transaction hash will differ from the hash produced by the Exchange contract because it utilizes a different domain hash.
transactionHash = hashEIP712Message(hashZeroExTransaction(transaction));
// Hash the transaction with the domain separator of the Exchange contract.
transactionHash = hashEIP712ExchangeMessage(hashZeroExTransaction(transaction));
return transactionHash;
}
@@ -77,7 +77,7 @@ contract LibZeroExTransaction is
assembly {
// Compute hash of data
let dataHash := keccak256(add(data, 32), mload(data))
// Load free memory pointer
let memPtr := mload(64)

View File

@@ -27,8 +27,9 @@ contract MSignatureValidator is
// Allowed signature types.
enum SignatureType {
Illegal, // 0x00, default value
EIP712, // 0x01
EthSign, // 0x02
NSignatureTypes // 0x03, number of signature types. Always leave at end.
Invalid, // 0x01
EIP712, // 0x02
EthSign, // 0x03
NSignatureTypes // 0x04, number of signature types. Always leave at end.
}
}

View File

@@ -19,14 +19,22 @@
pragma solidity ^0.5.5;
pragma experimental "ABIEncoderV2";
import "../src/libs/LibConstants.sol";
import "../src/libs/LibCoordinatorApproval.sol";
import "../src/libs/LibZeroExTransaction.sol";
// solhint-disable no-empty-blocks
contract TestLibs is
LibConstants,
LibCoordinatorApproval,
LibZeroExTransaction
{
constructor (address _exchange)
public
LibConstants(_exchange)
{}
/// @dev Calculated the EIP712 hash of the Coordinator approval mesasage using the domain separator of this contract.
/// @param approval Coordinator approval message containing the transaction hash, transaction signature, and expiration of the approval.
/// @return EIP712 hash of the Coordinator approval message with the domain separator of this contract.
@@ -39,9 +47,9 @@ contract TestLibs is
return approvalHash;
}
/// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of this contract.
/// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of the Exchange contract.
/// @param transaction 0x transaction containing salt, signerAddress, and data.
/// @return EIP712 hash of the transaction with the domain separator of this contract.
/// @return EIP712 hash of the transaction with the domain separator of the Exchange contract.
function publicGetTransactionHash(ZeroExTransaction memory transaction)
public
view

View File

@@ -19,12 +19,19 @@
pragma solidity ^0.5.5;
pragma experimental "ABIEncoderV2";
import "../src/libs/LibConstants.sol";
import "../src/MixinSignatureValidator.sol";
import "../src/MixinCoordinatorApprovalVerifier.sol";
// solhint-disable no-empty-blocks
contract TestMixins is
LibConstants,
MixinSignatureValidator,
MixinCoordinatorApprovalVerifier
{}
{
constructor (address _exchange)
public
LibConstants(_exchange)
{}
}

View File

@@ -1,5 +1,6 @@
import { chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
import { addressUtils, chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { transactionHashUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
@@ -11,6 +12,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Libs tests', () => {
let testLibs: TestLibsContract;
const exchangeAddress = addressUtils.generatePseudoRandomAddress();
before(async () => {
await blockchainLifecycle.startAsync();
@@ -19,7 +21,12 @@ describe('Libs tests', () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
testLibs = await TestLibsContract.deployFrom0xArtifactAsync(artifacts.TestLibs, provider, txDefaults);
testLibs = await TestLibsContract.deployFrom0xArtifactAsync(
artifacts.TestLibs,
provider,
txDefaults,
exchangeAddress,
);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -31,12 +38,12 @@ describe('Libs tests', () => {
describe('getTransactionHash', () => {
it('should return the correct transaction hash', async () => {
const tx = {
verifyingContractAddress: testLibs.address,
verifyingContractAddress: exchangeAddress,
salt: new BigNumber(0),
signerAddress: constants.NULL_ADDRESS,
data: '0x1234',
};
const expectedTxHash = hashUtils.getTransactionHashHex(tx);
const expectedTxHash = transactionHashUtils.getTransactionHashHex(tx);
const txHash = await testLibs.publicGetTransactionHash.callAsync(tx);
expect(expectedTxHash).to.eq(txHash);
});
@@ -45,7 +52,7 @@ describe('Libs tests', () => {
describe('getApprovalHash', () => {
it('should return the correct approval hash', async () => {
const signedTx = {
verifyingContractAddress: testLibs.address,
verifyingContractAddress: exchangeAddress,
salt: new BigNumber(0),
signerAddress: constants.NULL_ADDRESS,
data: '0x1234',
@@ -55,12 +62,13 @@ describe('Libs tests', () => {
const txOrigin = constants.NULL_ADDRESS;
const approval = {
txOrigin,
transactionHash: hashUtils.getTransactionHashHex(signedTx),
transactionHash: transactionHashUtils.getTransactionHashHex(signedTx),
transactionSignature: signedTx.signature,
approvalExpirationTimeSeconds,
};
const expectedApprovalHash = hashUtils.getApprovalHashHex(
signedTx,
testLibs.address,
txOrigin,
approvalExpirationTimeSeconds,
);

View File

@@ -1,28 +1,22 @@
import {
addressUtils,
chaiSetup,
constants as devConstants,
expectContractCallFailedAsync,
getLatestBlockTimestampAsync,
provider,
TransactionFactory,
txDefaults,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { RevertReason, SignedOrder } from '@0x/types';
import { transactionHashUtils } from '@0x/order-utils';
import { RevertReason, SignatureType, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import * as ethUtil from 'ethereumjs-util';
import {
ApprovalFactory,
artifacts,
constants,
CoordinatorSignatureType,
CoordinatorTransactionFactory,
exchangeDataEncoder,
hashUtils,
TestMixinsContract,
} from '../src';
import { ApprovalFactory, artifacts, constants, exchangeDataEncoder, TestMixinsContract } from '../src';
chaiSetup.configure();
const expect = chai.expect;
@@ -33,10 +27,12 @@ describe('Mixins tests', () => {
let approvalSignerAddress1: string;
let approvalSignerAddress2: string;
let mixins: TestMixinsContract;
let transactionFactory: CoordinatorTransactionFactory;
let transactionFactory: TransactionFactory;
let approvalFactory1: ApprovalFactory;
let approvalFactory2: ApprovalFactory;
let defaultOrder: SignedOrder;
const exchangeAddress = addressUtils.generatePseudoRandomAddress();
before(async () => {
await blockchainLifecycle.startAsync();
});
@@ -44,7 +40,12 @@ describe('Mixins tests', () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
mixins = await TestMixinsContract.deployFrom0xArtifactAsync(artifacts.TestMixins, provider, txDefaults);
mixins = await TestMixinsContract.deployFrom0xArtifactAsync(
artifacts.TestMixins,
provider,
txDefaults,
exchangeAddress,
);
const accounts = await web3Wrapper.getAvailableAddressesAsync();
[transactionSignerAddress, approvalSignerAddress1, approvalSignerAddress2] = accounts.slice(0, 3);
defaultOrder = {
@@ -67,7 +68,7 @@ describe('Mixins tests', () => {
devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(transactionSignerAddress)];
const approvalSignerPrivateKey1 = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(approvalSignerAddress1)];
const approvalSignerPrivateKey2 = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(approvalSignerAddress2)];
transactionFactory = new CoordinatorTransactionFactory(transactionSignerPrivateKey, mixins.address);
transactionFactory = new TransactionFactory(transactionSignerPrivateKey, exchangeAddress);
approvalFactory1 = new ApprovalFactory(approvalSignerPrivateKey1, mixins.address);
approvalFactory2 = new ApprovalFactory(approvalSignerPrivateKey2, mixins.address);
});
@@ -81,47 +82,52 @@ describe('Mixins tests', () => {
describe('getSignerAddress', () => {
it('should return the correct address using the EthSign signature type', async () => {
const data = devConstants.NULL_BYTES;
const transaction = transactionFactory.newSignedCoordinatorTransaction(
data,
CoordinatorSignatureType.EthSign,
);
const transactionHash = hashUtils.getTransactionHashHex(transaction);
const transaction = transactionFactory.newSignedTransaction(data, SignatureType.EthSign);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature);
expect(transaction.signerAddress).to.eq(signerAddress);
});
it('should return the correct address using the EIP712 signature type', async () => {
const data = devConstants.NULL_BYTES;
const transaction = transactionFactory.newSignedCoordinatorTransaction(
data,
CoordinatorSignatureType.EIP712,
);
const transactionHash = hashUtils.getTransactionHashHex(transaction);
const transaction = transactionFactory.newSignedTransaction(data, SignatureType.EIP712);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature);
expect(transaction.signerAddress).to.eq(signerAddress);
});
it('should revert with with the Illegal signature type', async () => {
const data = devConstants.NULL_BYTES;
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const illegalSignatureByte = ethUtil.toBuffer(CoordinatorSignatureType.Illegal).toString('hex');
const transaction = transactionFactory.newSignedTransaction(data);
const illegalSignatureByte = ethUtil.toBuffer(SignatureType.Illegal).toString('hex');
transaction.signature = `${transaction.signature.slice(
0,
transaction.signature.length - 2,
)}${illegalSignatureByte}`;
const transactionHash = hashUtils.getTransactionHashHex(transaction);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
expectContractCallFailedAsync(
mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
RevertReason.SignatureIllegal,
);
});
it('should revert with with the Invalid signature type', async () => {
const data = devConstants.NULL_BYTES;
const transaction = transactionFactory.newSignedTransaction(data);
const invalidSignatureByte = ethUtil.toBuffer(SignatureType.Invalid).toString('hex');
transaction.signature = `0x${invalidSignatureByte}`;
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
expectContractCallFailedAsync(
mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
RevertReason.SignatureInvalid,
);
});
it("should revert with with a signature type that doesn't exist", async () => {
const data = devConstants.NULL_BYTES;
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const invalidSignatureByte = '03';
const transaction = transactionFactory.newSignedTransaction(data);
const invalidSignatureByte = '04';
transaction.signature = `${transaction.signature.slice(
0,
transaction.signature.length - 2,
)}${invalidSignatureByte}`;
const transactionHash = hashUtils.getTransactionHashHex(transaction);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
expectContractCallFailedAsync(
mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
RevertReason.SignatureUnsupported,
@@ -134,7 +140,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[approver1], expiration=[valid]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -167,7 +173,7 @@ describe('Mixins tests', () => {
};
const orders = [order];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -196,7 +202,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[], expiration=[]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidTransactionOrdersApproval.callAsync(
transaction,
orders,
@@ -220,7 +226,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[approver1], expiration=[invalid]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -249,7 +255,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[], expiration=[]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidTransactionOrdersApproval.callAsync(
transaction,
orders,
@@ -273,7 +279,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[invalid], expiration=[valid]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -309,7 +315,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[approver1], expiration=[invalid]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -344,7 +350,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName}, caller=approver2, senderAddress=[verifier], approval_sig=[approver1], expiration=[valid]`, async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -387,7 +393,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => {
const orders = [defaultOrder, defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -419,7 +425,7 @@ describe('Mixins tests', () => {
senderAddress: devConstants.NULL_ADDRESS,
}));
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -451,7 +457,7 @@ describe('Mixins tests', () => {
senderAddress: devConstants.NULL_ADDRESS,
}));
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidTransactionOrdersApproval.callAsync(
transaction,
orders,
@@ -473,7 +479,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,null], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, senderAddress: devConstants.NULL_ADDRESS }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -502,7 +508,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver2], approval_sig=[approver1,approver2], expiration=[valid,valid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval1 = approvalFactory1.newSignedApproval(
@@ -536,7 +542,7 @@ describe('Mixins tests', () => {
it(`Should be successful: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[], expiration=[]`, async () => {
const orders = [defaultOrder, defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidTransactionOrdersApproval.callAsync(
transaction,
orders,
@@ -558,7 +564,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver2], approval_sig=[approver2], expiration=[valid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval2 = approvalFactory2.newSignedApproval(
@@ -593,7 +599,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[], expiration=[]`, async () => {
const orders = [defaultOrder, defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
expectContractCallFailedAsync(
mixins.assertValidTransactionOrdersApproval.callAsync(
transaction,
@@ -621,7 +627,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[invalid], expiration=[valid]`, async () => {
const orders = [defaultOrder, defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval = approvalFactory1.newSignedApproval(
@@ -657,7 +663,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid,invalid], expiration=[valid,valid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval1 = approvalFactory1.newSignedApproval(
@@ -698,7 +704,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[invalid], expiration=[valid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval2 = approvalFactory2.newSignedApproval(
@@ -734,7 +740,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid,valid], expiration=[valid,invalid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds1 = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approvalExpirationTimeSeconds2 = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
@@ -775,7 +781,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid], expiration=[invalid]`, async () => {
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
const approval2 = approvalFactory2.newSignedApproval(
@@ -810,7 +816,7 @@ describe('Mixins tests', () => {
it(`Should revert: function=${fnName} caller=approver2, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[valid], expiration=[valid]`, async () => {
const orders = [defaultOrder, defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
const currentTimestamp = await getLatestBlockTimestampAsync();
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
const approval1 = approvalFactory1.newSignedApproval(
@@ -848,7 +854,7 @@ describe('Mixins tests', () => {
it('should allow the tx signer to call `cancelOrders` without approval', async () => {
const orders = [defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.CANCEL_ORDERS, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidCoordinatorApprovals.callAsync(
transaction,
transactionSignerAddress,
@@ -861,7 +867,7 @@ describe('Mixins tests', () => {
it('should allow the tx signer to call `batchCancelOrders` without approval', async () => {
const orders = [defaultOrder, defaultOrder];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.BATCH_CANCEL_ORDERS, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidCoordinatorApprovals.callAsync(
transaction,
transactionSignerAddress,
@@ -874,7 +880,7 @@ describe('Mixins tests', () => {
it('should allow the tx signer to call `cancelOrdersUpTo` without approval', async () => {
const orders: SignedOrder[] = [];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.CANCEL_ORDERS_UP_TO, orders);
const transaction = transactionFactory.newSignedCoordinatorTransaction(data);
const transaction = transactionFactory.newSignedTransaction(data);
await mixins.assertValidCoordinatorApprovals.callAsync(
transaction,
transactionSignerAddress,

View File

@@ -1,35 +1,35 @@
import { SignedZeroExTransaction } from '@0x/types';
import { signingUtils } from '@0x/contracts-test-utils';
import { SignatureType, SignedZeroExTransaction } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import { CoordinatorSignatureType, hashUtils, SignedCoordinatorApproval, signingUtils } from './index';
import { hashUtils, SignedCoordinatorApproval } from './index';
export class ApprovalFactory {
private readonly _privateKey: Buffer;
private readonly _verifyingContractAddress: string;
constructor(privateKey: Buffer, verifyingContractAddress: string) {
this._privateKey = privateKey;
this._verifyingContractAddress = verifyingContractAddress;
}
public newSignedApproval(
transaction: SignedZeroExTransaction,
txOrigin: string,
approvalExpirationTimeSeconds: BigNumber,
signatureType: CoordinatorSignatureType = CoordinatorSignatureType.EthSign,
signatureType: SignatureType = SignatureType.EthSign,
): SignedCoordinatorApproval {
const coordinatorTransaction = {
...transaction,
verifyingContractAddress: this._verifyingContractAddress,
};
const approvalHashBuff = hashUtils.getApprovalHashBuffer(
coordinatorTransaction,
transaction,
this._verifyingContractAddress,
txOrigin,
approvalExpirationTimeSeconds,
);
const signatureBuff = signingUtils.signMessage(approvalHashBuff, this._privateKey, signatureType);
const signedApproval = {
txOrigin,
transaction: coordinatorTransaction,
transaction,
approvalExpirationTimeSeconds,
signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')),
};

View File

@@ -1,17 +1,6 @@
import { BigNumber } from '@0x/utils';
export const constants = {
COORDINATOR_DOMAIN_NAME: '0x Protocol Coordinator',
COORDINATOR_DOMAIN_VERSION: '1.0.0',
COORDINATOR_APPROVAL_SCHEMA: {
name: 'CoordinatorApproval',
parameters: [
{ name: 'txOrigin', type: 'address' },
{ name: 'transactionHash', type: 'bytes32' },
{ name: 'transactionSignature', type: 'bytes' },
{ name: 'approvalExpirationTimeSeconds', type: 'uint256' },
],
},
SINGLE_FILL_FN_NAMES: ['fillOrder', 'fillOrKillOrder', 'fillOrderNoThrow'],
BATCH_FILL_FN_NAMES: ['batchFillOrders', 'batchFillOrKillOrders', 'batchFillOrdersNoThrow'],
MARKET_FILL_FN_NAMES: ['marketBuyOrders', 'marketBuyOrdersNoThrow', 'marketSellOrders', 'marketSellOrdersNoThrow'],

View File

@@ -1,34 +0,0 @@
import { generatePseudoRandomSalt } from '@0x/order-utils';
import { SignedZeroExTransaction } from '@0x/types';
import * as ethUtil from 'ethereumjs-util';
import { CoordinatorSignatureType, hashUtils, signingUtils } from './index';
export class CoordinatorTransactionFactory {
private readonly _signerBuff: Buffer;
private readonly _verifyingContractAddress: string;
private readonly _privateKey: Buffer;
constructor(privateKey: Buffer, verifyingContractAddress: string) {
this._privateKey = privateKey;
this._verifyingContractAddress = verifyingContractAddress;
this._signerBuff = ethUtil.privateToAddress(this._privateKey);
}
public newSignedCoordinatorTransaction(
data: string,
signatureType: CoordinatorSignatureType = CoordinatorSignatureType.EthSign,
): SignedZeroExTransaction {
const transaction = {
verifyingContractAddress: this._verifyingContractAddress,
signerAddress: ethUtil.addHexPrefix(this._signerBuff.toString('hex')),
salt: generatePseudoRandomSalt(),
data,
};
const transactionHashBuff = hashUtils.getTransactionHashBuffer(transaction);
const signatureBuff = signingUtils.signMessage(transactionHashBuff, this._privateKey, signatureType);
const signedTransaction = {
...transaction,
signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')),
};
return signedTransaction;
}
}

View File

@@ -1,23 +1,22 @@
import { eip712Utils } from '@0x/order-utils';
import { constants as orderUtilsConstants } from '@0x/order-utils/lib/src/constants';
import { SignedZeroExTransaction, ZeroExTransaction } from '@0x/types';
import { eip712Utils, transactionHashUtils } from '@0x/order-utils';
import { constants } from '@0x/order-utils/lib/src/constants';
import { SignedZeroExTransaction } from '@0x/types';
import { BigNumber, signTypedDataUtils } from '@0x/utils';
import * as _ from 'lodash';
import { constants } from './index';
export const hashUtils = {
getApprovalHashBuffer(
transaction: SignedZeroExTransaction,
verifyingContractAddress: string,
txOrigin: string,
approvalExpirationTimeSeconds: BigNumber,
): Buffer {
const domain = {
name: constants.COORDINATOR_DOMAIN_NAME,
version: constants.COORDINATOR_DOMAIN_VERSION,
verifyingContractAddress: transaction.verifyingContractAddress,
verifyingContractAddress,
};
const transactionHash = hashUtils.getTransactionHashHex(transaction);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
const approval = {
txOrigin,
transactionHash,
@@ -37,34 +36,13 @@ export const hashUtils = {
},
getApprovalHashHex(
transaction: SignedZeroExTransaction,
verifyingContractAddress: string,
txOrigin: string,
approvalExpirationTimeSeconds: BigNumber,
): string {
const hashHex = `0x${hashUtils
.getApprovalHashBuffer(transaction, txOrigin, approvalExpirationTimeSeconds)
.getApprovalHashBuffer(transaction, verifyingContractAddress, txOrigin, approvalExpirationTimeSeconds)
.toString('hex')}`;
return hashHex;
},
getTransactionHashBuffer(transaction: ZeroExTransaction | SignedZeroExTransaction): Buffer {
const domain = {
name: constants.COORDINATOR_DOMAIN_NAME,
version: constants.COORDINATOR_DOMAIN_VERSION,
verifyingContractAddress: transaction.verifyingContractAddress,
};
const normalizedTransaction = _.mapValues(transaction, value => {
return !_.isString(value) ? value.toString() : value;
});
const typedData = eip712Utils.createTypedData(
orderUtilsConstants.EXCHANGE_ZEROEX_TRANSACTION_SCHEMA.name,
{ ZeroExTransaction: orderUtilsConstants.EXCHANGE_ZEROEX_TRANSACTION_SCHEMA.parameters },
normalizedTransaction,
domain,
);
const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
return hashBuffer;
},
getTransactionHashHex(transaction: ZeroExTransaction | SignedZeroExTransaction): string {
const hashHex = `0x${hashUtils.getTransactionHashBuffer(transaction).toString('hex')}`;
return hashHex;
},
};

View File

@@ -1,6 +1,4 @@
export { hashUtils } from './hash_utils';
export { signingUtils } from './signing_utils';
export { CoordinatorTransactionFactory } from './coordinator_transaction_factory';
export { ApprovalFactory } from './approval_factory';
export { constants } from './constants';
export { exchangeDataEncoder } from './exchange_data_encoder';

View File

@@ -1,30 +0,0 @@
import * as ethUtil from 'ethereumjs-util';
import { CoordinatorSignatureType } from './types';
export const signingUtils = {
signMessage(message: Buffer, privateKey: Buffer, signatureType: CoordinatorSignatureType): Buffer {
if (signatureType === CoordinatorSignatureType.EthSign) {
const prefixedMessage = ethUtil.hashPersonalMessage(message);
const ecSignature = ethUtil.ecsign(prefixedMessage, privateKey);
const signature = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ecSignature.r,
ecSignature.s,
ethUtil.toBuffer(signatureType),
]);
return signature;
} else if (signatureType === CoordinatorSignatureType.EIP712) {
const ecSignature = ethUtil.ecsign(message, privateKey);
const signature = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ecSignature.r,
ecSignature.s,
ethUtil.toBuffer(signatureType),
]);
return signature;
} else {
throw new Error(`${signatureType} is not a valid signature type`);
}
},
};

View File

@@ -10,10 +10,3 @@ export interface CoordinatorApproval {
export interface SignedCoordinatorApproval extends CoordinatorApproval {
signature: string;
}
export enum CoordinatorSignatureType {
Illegal,
EIP712,
EthSign,
NSignatureTypes,
}

View File

@@ -14,6 +14,10 @@
{
"note": "Set evmVersion to byzantium",
"pr": 1678
},
{
"note": "Remove Coordinator EIP712 constants. They're now in the `order-utils` package.",
"pr": 1705
}
]
},

View File

@@ -70,14 +70,4 @@ export const constants = {
'CANCEL_ORDERS_UP_TO',
'SET_SIGNATURE_VALIDATOR_APPROVAL',
],
COORDINATOR_DOMAIN_NAME: '0x Protocol Trade Execution Coordinator',
COORDINATOR_DOMAIN_VERSION: '1.0.0',
COORDINATOR_APPROVAL_SCHEMA: {
name: 'COORDINATORApproval',
parameters: [
{ name: 'transactionHash', type: 'bytes32' },
{ name: 'transactionSignature', type: 'bytes' },
{ name: 'approvalExpirationTimeSeconds', type: 'uint256' },
],
},
};