aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/order-utils/src')
-rw-r--r--packages/order-utils/src/artifacts.ts10
-rw-r--r--packages/order-utils/src/assert.ts14
-rw-r--r--packages/order-utils/src/asset_proxy_utils.ts151
-rw-r--r--packages/order-utils/src/crypto.ts46
-rw-r--r--packages/order-utils/src/formatters.ts23
-rw-r--r--packages/order-utils/src/index.ts9
-rw-r--r--packages/order-utils/src/order_factory.ts76
-rw-r--r--packages/order-utils/src/order_hash.ts183
-rw-r--r--packages/order-utils/src/order_state_utils.ts32
-rw-r--r--packages/order-utils/src/remaining_fillable_calculator.ts12
-rw-r--r--packages/order-utils/src/signature_utils.ts237
-rw-r--r--packages/order-utils/src/types.ts22
-rw-r--r--packages/order-utils/src/utils.ts9
13 files changed, 647 insertions, 177 deletions
diff --git a/packages/order-utils/src/artifacts.ts b/packages/order-utils/src/artifacts.ts
new file mode 100644
index 000000000..f6fd00472
--- /dev/null
+++ b/packages/order-utils/src/artifacts.ts
@@ -0,0 +1,10 @@
+import { Artifact } from '@0xproject/types';
+
+import * as Exchange from './artifacts/Exchange.json';
+import * as IValidator from './artifacts/IValidator.json';
+import * as IWallet from './artifacts/IWallet.json';
+export const artifacts = {
+ Exchange: (Exchange as any) as Artifact,
+ IWallet: (IWallet as any) as Artifact,
+ IValidator: (IValidator as any) as Artifact,
+};
diff --git a/packages/order-utils/src/assert.ts b/packages/order-utils/src/assert.ts
index 5ac402e7e..a1318b9b8 100644
--- a/packages/order-utils/src/assert.ts
+++ b/packages/order-utils/src/assert.ts
@@ -3,12 +3,12 @@ import { assert as sharedAssert } from '@0xproject/assert';
// tslint:disable-next-line:no-unused-variable
import { Schema } from '@0xproject/json-schemas';
// tslint:disable-next-line:no-unused-variable
-import { ECSignature } from '@0xproject/types';
+import { ECSignature, SignatureType } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
-import { isValidSignature } from './signature_utils';
+import { utils } from './utils';
export const assert = {
...sharedAssert,
@@ -24,4 +24,14 @@ export const assert = {
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
);
},
+ isOneOfExpectedSignatureTypes(signature: string, signatureTypes: SignatureType[]): void {
+ sharedAssert.isHexString('signature', signature);
+ const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature);
+ const isExpectedSignatureType = _.includes(signatureTypes, signatureTypeIndexIfExists);
+ if (!isExpectedSignatureType) {
+ throw new Error(
+ `Unexpected signatureType: ${signatureTypeIndexIfExists}. Valid signature types: ${signatureTypes}`,
+ );
+ }
+ },
};
diff --git a/packages/order-utils/src/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts
new file mode 100644
index 000000000..55f2d56df
--- /dev/null
+++ b/packages/order-utils/src/asset_proxy_utils.ts
@@ -0,0 +1,151 @@
+import { AssetProxyId, ERC20ProxyData, ERC721ProxyData, ProxyData } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import BN = require('bn.js');
+import ethUtil = require('ethereumjs-util');
+
+const ERC20_PROXY_METADATA_BYTE_LENGTH = 21;
+const ERC721_PROXY_METADATA_BYTE_LENGTH = 53;
+
+export const assetProxyUtils = {
+ encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer {
+ return ethUtil.toBuffer(assetProxyId);
+ },
+ decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
+ return ethUtil.bufferToInt(encodedAssetProxyId);
+ },
+ encodeAddress(address: string): Buffer {
+ if (!ethUtil.isValidAddress(address)) {
+ throw new Error(`Invalid Address: ${address}`);
+ }
+ const encodedAddress = ethUtil.toBuffer(address);
+ return encodedAddress;
+ },
+ decodeAddress(encodedAddress: Buffer): string {
+ const address = ethUtil.bufferToHex(encodedAddress);
+ if (!ethUtil.isValidAddress(address)) {
+ throw new Error(`Invalid Address: ${address}`);
+ }
+ return address;
+ },
+ encodeUint256(value: BigNumber): Buffer {
+ const base = 10;
+ const formattedValue = new BN(value.toString(base));
+ const encodedValue = ethUtil.toBuffer(formattedValue);
+ // tslint:disable-next-line:custom-no-magic-numbers
+ const paddedValue = ethUtil.setLengthLeft(encodedValue, 32);
+ return paddedValue;
+ },
+ decodeUint256(encodedValue: Buffer): BigNumber {
+ const formattedValue = ethUtil.bufferToHex(encodedValue);
+ const value = new BigNumber(formattedValue, 16);
+ return value;
+ },
+ encodeERC20ProxyData(tokenAddress: string): string {
+ const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20);
+ const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
+ const encodedMetadata = Buffer.concat([encodedAddress, encodedAssetProxyId]);
+ const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
+ return encodedMetadataHex;
+ },
+ decodeERC20ProxyData(proxyData: string): ERC20ProxyData {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength !== ERC20_PROXY_METADATA_BYTE_LENGTH) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(-1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ if (assetProxyId !== AssetProxyId.ERC20) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${
+ AssetProxyId.ERC20
+ }), but got ${assetProxyId}`,
+ );
+ }
+ const addressOffset = ERC20_PROXY_METADATA_BYTE_LENGTH - 1;
+ const encodedTokenAddress = encodedProxyMetadata.slice(0, addressOffset);
+ const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
+ const erc20ProxyData = {
+ assetProxyId,
+ tokenAddress,
+ };
+ return erc20ProxyData;
+ },
+ encodeERC721ProxyData(tokenAddress: string, tokenId: BigNumber): string {
+ const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721);
+ const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
+ const encodedTokenId = assetProxyUtils.encodeUint256(tokenId);
+ const encodedMetadata = Buffer.concat([encodedAddress, encodedTokenId, encodedAssetProxyId]);
+ const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
+ return encodedMetadataHex;
+ },
+ decodeERC721ProxyData(proxyData: string): ERC721ProxyData {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength !== ERC721_PROXY_METADATA_BYTE_LENGTH) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 53. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(-1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ if (assetProxyId !== AssetProxyId.ERC721) {
+ throw new Error(
+ `Could not decode ERC721 Proxy Data. Expected Asset Proxy Id to be ERC721 (${
+ AssetProxyId.ERC721
+ }), but got ${assetProxyId}`,
+ );
+ }
+ const addressOffset = ERC20_PROXY_METADATA_BYTE_LENGTH - 1;
+ const encodedTokenAddress = encodedProxyMetadata.slice(0, addressOffset);
+ const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
+ const tokenIdOffset = ERC721_PROXY_METADATA_BYTE_LENGTH - 1;
+ const encodedTokenId = encodedProxyMetadata.slice(addressOffset, tokenIdOffset);
+ const tokenId = assetProxyUtils.decodeUint256(encodedTokenId);
+ const erc721ProxyData = {
+ assetProxyId,
+ tokenAddress,
+ tokenId,
+ };
+ return erc721ProxyData;
+ },
+ decodeProxyDataId(proxyData: string): AssetProxyId {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength < 1) {
+ throw new Error(
+ `Could not decode Proxy Data. Expected length of encoded data to be at least 1. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(-1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ return assetProxyId;
+ },
+ decodeProxyData(proxyData: string): ProxyData {
+ const assetProxyId = assetProxyUtils.decodeProxyDataId(proxyData);
+ switch (assetProxyId) {
+ case AssetProxyId.ERC20:
+ const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(proxyData);
+ const generalizedERC20ProxyData = {
+ assetProxyId,
+ tokenAddress: erc20ProxyData.tokenAddress,
+ };
+ return generalizedERC20ProxyData;
+ case AssetProxyId.ERC721:
+ const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(proxyData);
+ const generaliedERC721ProxyData = {
+ assetProxyId,
+ tokenAddress: erc721ProxyData.tokenAddress,
+ data: erc721ProxyData.tokenId,
+ };
+ return generaliedERC721ProxyData;
+ default:
+ throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`);
+ }
+ },
+};
diff --git a/packages/order-utils/src/crypto.ts b/packages/order-utils/src/crypto.ts
new file mode 100644
index 000000000..517ca2840
--- /dev/null
+++ b/packages/order-utils/src/crypto.ts
@@ -0,0 +1,46 @@
+import BN = require('bn.js');
+import ABI = require('ethereumjs-abi');
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+export const crypto = {
+ /**
+ * We convert types from JS to Solidity as follows:
+ * BigNumber -> uint256
+ * number -> uint8
+ * string -> string
+ * boolean -> bool
+ * valid Ethereum address -> address
+ */
+ solSHA3(args: any[]): Buffer {
+ return crypto._solHash(args, ABI.soliditySHA3);
+ },
+ solSHA256(args: any[]): Buffer {
+ return crypto._solHash(args, ABI.soliditySHA256);
+ },
+ _solHash(args: any[], hashFunction: (types: string[], values: any[]) => Buffer): Buffer {
+ const argTypes: string[] = [];
+ _.each(args, (arg, i) => {
+ const isNumber = _.isFinite(arg);
+ if (isNumber) {
+ argTypes.push('uint8');
+ } else if (arg.isBigNumber) {
+ argTypes.push('uint256');
+ const base = 10;
+ args[i] = new BN(arg.toString(base), base);
+ } else if (ethUtil.isValidAddress(arg)) {
+ argTypes.push('address');
+ } else if (_.isString(arg)) {
+ argTypes.push('string');
+ } else if (_.isBuffer(arg)) {
+ argTypes.push('bytes');
+ } else if (_.isBoolean(arg)) {
+ argTypes.push('bool');
+ } else {
+ throw new Error(`Unable to guess arg type: ${arg}`);
+ }
+ });
+ const hash = hashFunction(argTypes, args);
+ return hash;
+ },
+};
diff --git a/packages/order-utils/src/formatters.ts b/packages/order-utils/src/formatters.ts
deleted file mode 100644
index 2b6f4ddb7..000000000
--- a/packages/order-utils/src/formatters.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Order, OrderAddresses, OrderValues } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-
-export const formatters = {
- getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] {
- const orderAddresses: OrderAddresses = [
- order.maker,
- order.taker,
- order.makerTokenAddress,
- order.takerTokenAddress,
- order.feeRecipient,
- ];
- const orderValues: OrderValues = [
- order.makerTokenAmount,
- order.takerTokenAmount,
- order.makerFee,
- order.takerFee,
- order.expirationUnixTimestampSec,
- order.salt,
- ];
- return [orderAddresses, orderValues];
- },
-};
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index e9cea95ed..b844fbfcb 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -1,11 +1,12 @@
-export { getOrderHashHex, isValidOrderHash } from './order_hash';
-export { isValidSignature, signOrderHashAsync } from './signature_utils';
+export { orderHashUtils } from './order_hash';
+export { isValidSignatureAsync, ecSignOrderHashAsync, addSignedMessagePrefix } from './signature_utils';
export { orderFactory } from './order_factory';
export { constants } from './constants';
+export { crypto } from './crypto';
export { generatePseudoRandomSalt } from './salt';
-export { OrderError } from './types';
-export { formatters } from './formatters';
+export { OrderError, MessagePrefixType, MessagePrefixOpts } from './types';
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
export { RemainingFillableCalculator } from './remaining_fillable_calculator';
export { OrderStateUtils } from './order_state_utils';
+export { assetProxyUtils } from './asset_proxy_utils';
diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts
index 2759aac81..678336ac5 100644
--- a/packages/order-utils/src/order_factory.ts
+++ b/packages/order-utils/src/order_factory.ts
@@ -1,49 +1,69 @@
-import { Provider, SignedOrder } from '@0xproject/types';
+import { ECSignature, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+import { Provider } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
-import { getOrderHashHex } from './order_hash';
+import { orderHashUtils } from './order_hash';
import { generatePseudoRandomSalt } from './salt';
-import { signOrderHashAsync } from './signature_utils';
-
-const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
+import { ecSignOrderHashAsync } from './signature_utils';
+import { MessagePrefixType } from './types';
export const orderFactory = {
async createSignedOrderAsync(
provider: Provider,
- maker: string,
- taker: string,
+ makerAddress: string,
+ takerAddress: string,
+ senderAddress: string,
makerFee: BigNumber,
takerFee: BigNumber,
- makerTokenAmount: BigNumber,
- makerTokenAddress: string,
- takerTokenAmount: BigNumber,
- takerTokenAddress: string,
- exchangeContractAddress: string,
- feeRecipient: string,
- expirationUnixTimestampSecIfExists?: BigNumber,
+ makerAssetAmount: BigNumber,
+ makerAssetData: string,
+ takerAssetAmount: BigNumber,
+ takerAssetData: string,
+ exchangeAddress: string,
+ feeRecipientAddress: string,
+ expirationTimeSecondsIfExists?: BigNumber,
): Promise<SignedOrder> {
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
- const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists)
+ const expirationTimeSeconds = _.isUndefined(expirationTimeSecondsIfExists)
? defaultExpirationUnixTimestampSec
- : expirationUnixTimestampSecIfExists;
+ : expirationTimeSecondsIfExists;
const order = {
- maker,
- taker,
+ makerAddress,
+ takerAddress,
+ senderAddress,
makerFee,
takerFee,
- makerTokenAmount,
- takerTokenAmount,
- makerTokenAddress,
- takerTokenAddress,
+ makerAssetAmount,
+ takerAssetAmount,
+ makerAssetData,
+ takerAssetData,
salt: generatePseudoRandomSalt(),
- exchangeContractAddress,
- feeRecipient,
- expirationUnixTimestampSec,
+ exchangeAddress,
+ feeRecipientAddress,
+ expirationTimeSeconds,
+ };
+ const orderHash = orderHashUtils.getOrderHashHex(order);
+ const messagePrefixOpts = {
+ prefixType: MessagePrefixType.EthSign,
+ shouldAddPrefixBeforeCallingEthSign: false,
};
- const orderHash = getOrderHashHex(order);
- const ecSignature = await signOrderHashAsync(provider, orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX);
- const signedOrder: SignedOrder = _.assign(order, { ecSignature });
+ const ecSignature = await ecSignOrderHashAsync(provider, orderHash, makerAddress, messagePrefixOpts);
+ const signature = getVRSHexString(ecSignature);
+ const signedOrder: SignedOrder = _.assign(order, { signature });
return signedOrder;
},
};
+
+function getVRSHexString(ecSignature: ECSignature): string {
+ const vrs = `0x${intToHex(ecSignature.v)}${ethUtil.stripHexPrefix(ecSignature.r)}${ethUtil.stripHexPrefix(
+ ecSignature.s,
+ )}`;
+ return vrs;
+}
+
+function intToHex(i: number): string {
+ const hex = ethUtil.bufferToHex(ethUtil.toBuffer(i));
+ return hex;
+}
diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts
index 108344a04..2ef746ef8 100644
--- a/packages/order-utils/src/order_hash.ts
+++ b/packages/order-utils/src/order_hash.ts
@@ -1,90 +1,117 @@
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
-import { Order, SignedOrder, SolidityTypes } from '@0xproject/types';
+import { Order, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import BN = require('bn.js');
+import { SolidityTypes } from 'ethereum-types';
import * as ethABI from 'ethereumjs-abi';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { assert } from './assert';
+import { crypto } from './crypto';
-const INVALID_TAKER_FORMAT = 'instance.taker is not of a type(s) string';
+const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string';
-/**
- * Converts BigNumber instance to BN
- * The only reason we convert to BN is to remain compatible with `ethABI.soliditySHA3` that
- * expects values of Solidity type `uint` to be passed as type `BN`.
- * We do not use BN anywhere else in the codebase.
- */
-function bigNumberToBN(value: BigNumber): BN {
- const base = 10;
- return new BN(value.toString(), base);
-}
-
-/**
- * Computes the orderHash for a supplied order.
- * @param order An object that conforms to the Order or SignedOrder interface definitions.
- * @return The resulting orderHash from hashing the supplied order.
- */
-export function getOrderHashHex(order: Order | SignedOrder): string {
- try {
- assert.doesConformToSchema('order', order, schemas.orderSchema);
- } catch (error) {
- if (_.includes(error.message, INVALID_TAKER_FORMAT)) {
- const errMsg =
- 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
- throw new Error(errMsg);
+export const orderHashUtils = {
+ /**
+ * Checks if the supplied hex encoded order hash is valid.
+ * Note: Valid means it has the expected format, not that an order with the orderHash exists.
+ * Use this method when processing orderHashes submitted as user input.
+ * @param orderHash Hex encoded orderHash.
+ * @return Whether the supplied orderHash has the expected format.
+ */
+ isValidOrderHash(orderHash: string): boolean {
+ // Since this method can be called to check if any arbitrary string conforms to an orderHash's
+ // format, we only assert that we were indeed passed a string.
+ assert.isString('orderHash', orderHash);
+ const schemaValidator = new SchemaValidator();
+ const isValid = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
+ return isValid;
+ },
+ /**
+ * Computes the orderHash for a supplied order.
+ * @param order An object that conforms to the Order or SignedOrder interface definitions.
+ * @return The resulting orderHash from hashing the supplied order.
+ */
+ getOrderHashHex(order: SignedOrder | Order): string {
+ try {
+ assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
+ } catch (error) {
+ if (_.includes(error.message, INVALID_TAKER_FORMAT)) {
+ const errMsg =
+ 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
+ throw new Error(errMsg);
+ }
+ throw error;
}
- throw error;
- }
- const orderParts = [
- { value: order.exchangeContractAddress, type: SolidityTypes.Address },
- { value: order.maker, type: SolidityTypes.Address },
- { value: order.taker, type: SolidityTypes.Address },
- { value: order.makerTokenAddress, type: SolidityTypes.Address },
- { value: order.takerTokenAddress, type: SolidityTypes.Address },
- { value: order.feeRecipient, type: SolidityTypes.Address },
- {
- value: bigNumberToBN(order.makerTokenAmount),
- type: SolidityTypes.Uint256,
- },
- {
- value: bigNumberToBN(order.takerTokenAmount),
- type: SolidityTypes.Uint256,
- },
- {
- value: bigNumberToBN(order.makerFee),
- type: SolidityTypes.Uint256,
- },
- {
- value: bigNumberToBN(order.takerFee),
- type: SolidityTypes.Uint256,
- },
- {
- value: bigNumberToBN(order.expirationUnixTimestampSec),
- type: SolidityTypes.Uint256,
- },
- { value: bigNumberToBN(order.salt), type: SolidityTypes.Uint256 },
- ];
- const types = _.map(orderParts, o => o.type);
- const values = _.map(orderParts, o => o.value);
- const hashBuff = ethABI.soliditySHA3(types, values);
- const hashHex = ethUtil.bufferToHex(hashBuff);
- return hashHex;
-}
-/**
- * Checks if the supplied hex encoded order hash is valid.
- * Note: Valid means it has the expected format, not that an order with the orderHash exists.
- * Use this method when processing orderHashes submitted as user input.
- * @param orderHash Hex encoded orderHash.
- * @return Whether the supplied orderHash has the expected format.
- */
-export function isValidOrderHash(orderHash: string): boolean {
- // Since this method can be called to check if any arbitrary string conforms to an orderHash's
- // format, we only assert that we were indeed passed a string.
- assert.isString('orderHash', orderHash);
- const schemaValidator = new SchemaValidator();
- const isValid = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
- return isValid;
-}
+ const orderHashBuff = this.getOrderHashBuff(order);
+ const orderHashHex = `0x${orderHashBuff.toString('hex')}`;
+ return orderHashHex;
+ },
+ /**
+ * Computes the orderHash for a supplied order and returns it as a Buffer
+ * @param order An object that conforms to the Order or SignedOrder interface definitions.
+ * @return The resulting orderHash from hashing the supplied order as a Buffer
+ */
+ getOrderHashBuff(order: SignedOrder | Order): Buffer {
+ const makerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.makerAssetData)]);
+ const takerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.takerAssetData)]);
+
+ const orderParamsHashBuff = crypto.solSHA3([
+ order.makerAddress,
+ order.takerAddress,
+ order.feeRecipientAddress,
+ order.senderAddress,
+ order.makerAssetAmount,
+ order.takerAssetAmount,
+ order.makerFee,
+ order.takerFee,
+ order.expirationTimeSeconds,
+ order.salt,
+ makerAssetDataHash,
+ takerAssetDataHash,
+ ]);
+ const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`;
+ const orderSchemaHashHex = this._getOrderSchemaHex();
+ const domainSeparatorHashHex = this._getDomainSeparatorHashHex(order.exchangeAddress);
+ const domainSeparatorSchemaHex = this._getDomainSeparatorSchemaHex();
+ const orderHashBuff = crypto.solSHA3([
+ new BigNumber(domainSeparatorSchemaHex),
+ new BigNumber(domainSeparatorHashHex),
+ new BigNumber(orderSchemaHashHex),
+ new BigNumber(orderParamsHashHex),
+ ]);
+ return orderHashBuff;
+ },
+ _getOrderSchemaHex(): string {
+ const orderSchemaHashBuff = crypto.solSHA3([
+ '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,',
+ ')',
+ ]);
+ const schemaHashHex = `0x${orderSchemaHashBuff.toString('hex')}`;
+ return schemaHashHex;
+ },
+ _getDomainSeparatorSchemaHex(): string {
+ const domainSeparatorSchemaHashBuff = crypto.solSHA3(['DomainSeparator(address contract)']);
+ const schemaHashHex = `0x${domainSeparatorSchemaHashBuff.toString('hex')}`;
+ return schemaHashHex;
+ },
+ _getDomainSeparatorHashHex(exchangeAddress: string): string {
+ const domainSeparatorHashBuff = crypto.solSHA3([exchangeAddress]);
+ const domainSeparatorHashHex = `0x${domainSeparatorHashBuff.toString('hex')}`;
+ return domainSeparatorHashHex;
+ },
+};
diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts
index 36171f526..61050c9d6 100644
--- a/packages/order-utils/src/order_state_utils.ts
+++ b/packages/order-utils/src/order_state_utils.ts
@@ -11,7 +11,8 @@ import * as _ from 'lodash';
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
-import { getOrderHashHex } from './order_hash';
+import { assetProxyUtils } from './asset_proxy_utils';
+import { orderHashUtils } from './order_hash';
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
@@ -23,7 +24,7 @@ export class OrderStateUtils {
const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add(
orderRelevantState.filledTakerTokenAmount,
);
- const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
+ const availableTakerTokenAmount = signedOrder.takerAssetAmount.minus(unavailableTakerTokenAmount);
if (availableTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
@@ -42,9 +43,9 @@ export class OrderStateUtils {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
}
}
- const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount
+ const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
- .dividedBy(signedOrder.makerTokenAmount);
+ .dividedBy(signedOrder.makerAssetAmount);
if (
orderRelevantState.remainingFillableTakerTokenAmount.lessThan(
minFillableTakerTokenAmountWithinNoRoundingErrorRange,
@@ -62,7 +63,7 @@ export class OrderStateUtils {
}
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
- const orderHash = getOrderHashHex(signedOrder);
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
try {
OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
const orderState: OrderStateValid = {
@@ -82,22 +83,22 @@ export class OrderStateUtils {
}
public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
const zrxTokenAddress = this._orderFilledCancelledFetcher.getZRXTokenAddress();
- const orderHash = getOrderHashHex(signedOrder);
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
- signedOrder.makerTokenAddress,
- signedOrder.maker,
+ signedOrder.makerAssetData,
+ signedOrder.makerAddress,
);
const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- signedOrder.makerTokenAddress,
- signedOrder.maker,
+ signedOrder.makerAssetData,
+ signedOrder.makerAddress,
);
const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
zrxTokenAddress,
- signedOrder.maker,
+ signedOrder.makerAddress,
);
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
zrxTokenAddress,
- signedOrder.maker,
+ signedOrder.makerAddress,
);
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
const cancelledTakerTokenAmount = await this._orderFilledCancelledFetcher.getCancelledTakerAmountAsync(
@@ -106,8 +107,8 @@ export class OrderStateUtils {
const unavailableTakerTokenAmount = await this._orderFilledCancelledFetcher.getUnavailableTakerAmountAsync(
orderHash,
);
- const totalMakerTokenAmount = signedOrder.makerTokenAmount;
- const totalTakerTokenAmount = signedOrder.takerTokenAmount;
+ const totalMakerTokenAmount = signedOrder.makerAssetAmount;
+ const totalTakerTokenAmount = signedOrder.takerAssetAmount;
const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount);
const remainingMakerTokenAmount = remainingTakerTokenAmount
.times(totalMakerTokenAmount)
@@ -115,7 +116,8 @@ export class OrderStateUtils {
const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
- const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress;
+ const zrxAssetData = assetProxyUtils.encodeERC20ProxyData(zrxTokenAddress);
+ const isMakerTokenZRX = signedOrder.makerAssetData === zrxAssetData;
const remainingFillableCalculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
diff --git a/packages/order-utils/src/remaining_fillable_calculator.ts b/packages/order-utils/src/remaining_fillable_calculator.ts
index 184c13aa4..b291d8ea9 100644
--- a/packages/order-utils/src/remaining_fillable_calculator.ts
+++ b/packages/order-utils/src/remaining_fillable_calculator.ts
@@ -23,7 +23,7 @@ export class RemainingFillableCalculator {
this._remainingMakerTokenAmount = remainingMakerTokenAmount;
this._remainingMakerFeeAmount = remainingMakerTokenAmount
.times(signedOrder.makerFee)
- .dividedToIntegerBy(signedOrder.makerTokenAmount);
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
}
public computeRemainingMakerFillable(): BigNumber {
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
@@ -36,8 +36,8 @@ export class RemainingFillableCalculator {
}
public computeRemainingTakerFillable(): BigNumber {
return this.computeRemainingMakerFillable()
- .times(this._signedOrder.takerTokenAmount)
- .dividedToIntegerBy(this._signedOrder.makerTokenAmount);
+ .times(this._signedOrder.takerAssetAmount)
+ .dividedToIntegerBy(this._signedOrder.makerAssetAmount);
}
private _hasSufficientFundsForFeeAndTransferAmount(): boolean {
if (this._isMakerTokenZRX) {
@@ -59,7 +59,7 @@ export class RemainingFillableCalculator {
}
private _calculatePartiallyFillableMakerTokenAmount(): BigNumber {
// Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1
- const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee);
+ const orderToFeeRatio = this._signedOrder.makerAssetAmount.dividedBy(this._signedOrder.makerFee);
// The number of times the maker can fill the order, if each fill only required the transfer of a single
// baseUnit of fee tokens.
// Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2
@@ -81,10 +81,10 @@ export class RemainingFillableCalculator {
// When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored.
// This can result in a RoundingError being thrown by the Exchange Contract.
const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits
- .times(this._signedOrder.makerTokenAmount)
+ .times(this._signedOrder.makerAssetAmount)
.dividedToIntegerBy(this._signedOrder.makerFee);
const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits
- .times(this._signedOrder.makerTokenAmount)
+ .times(this._signedOrder.makerAssetAmount)
.dividedToIntegerBy(this._signedOrder.makerFee);
const partiallyFillableAmount = BigNumber.min(
partiallyFillableMakerTokenAmount,
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts
index ebd636b20..c57699af0 100644
--- a/packages/order-utils/src/signature_utils.ts
+++ b/packages/order-utils/src/signature_utils.ts
@@ -1,28 +1,167 @@
import { schemas } from '@0xproject/json-schemas';
-import { ECSignature, Provider } from '@0xproject/types';
+import { ECSignature, SignatureType, ValidatorSignature } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { Provider } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
+import { artifacts } from './artifacts';
import { assert } from './assert';
-import { OrderError } from './types';
+import { ExchangeContract } from './generated_contract_wrappers/exchange';
+import { IValidatorContract } from './generated_contract_wrappers/i_validator';
+import { IWalletContract } from './generated_contract_wrappers/i_wallet';
+import { MessagePrefixOpts, MessagePrefixType, OrderError } from './types';
+import { utils } from './utils';
/**
- * Verifies that the elliptic curve signature `signature` was generated
- * by signing `data` with the private key corresponding to the `signerAddress` address.
+ * Verifies that the provided signature is valid according to the 0x Protocol smart contracts
* @param data The hex encoded data signed by the supplied signature.
- * @param signature An object containing the elliptic curve signature parameters.
+ * @param signature A hex encoded 0x Protocol signature made up of: [TypeSpecificData][SignatureType].
+ * E.g [vrs][SignatureType.EIP712]
* @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
* @return Whether the signature is valid for the supplied signerAddress and data.
*/
-export function isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
+export async function isValidSignatureAsync(
+ provider: Provider,
+ data: string,
+ signature: string,
+ signerAddress: string,
+): Promise<boolean> {
+ const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature);
+ if (_.isUndefined(signatureTypeIndexIfExists)) {
+ throw new Error(`Unrecognized signatureType in signature: ${signature}`);
+ }
+
+ switch (signatureTypeIndexIfExists) {
+ case SignatureType.Illegal:
+ case SignatureType.Invalid:
+ return false;
+
+ case SignatureType.EIP712: {
+ const ecSignature = parseECSignature(signature);
+ return isValidECSignature(data, ecSignature, signerAddress);
+ }
+
+ case SignatureType.EthSign: {
+ const ecSignature = parseECSignature(signature);
+ const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.EthSign);
+ return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress);
+ }
+
+ case SignatureType.Caller:
+ // HACK: We currently do not "validate" the caller signature type.
+ // It can only be validated during Exchange contract execution.
+ throw new Error('Caller signature type cannot be validated off-chain');
+
+ case SignatureType.Wallet: {
+ const isValid = await isValidWalletSignatureAsync(provider, data, signature, signerAddress);
+ return isValid;
+ }
+
+ case SignatureType.Validator: {
+ const isValid = await isValidValidatorSignatureAsync(provider, data, signature, signerAddress);
+ return isValid;
+ }
+
+ case SignatureType.PreSigned: {
+ return isValidPresignedSignatureAsync(provider, data, signerAddress);
+ }
+
+ case SignatureType.Trezor: {
+ const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.Trezor);
+ const ecSignature = parseECSignature(signature);
+ return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress);
+ }
+
+ default:
+ throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`);
+ }
+}
+
+/**
+ * Verifies that the provided presigned signature is valid according to the 0x Protocol smart contracts
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned]
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the data was preSigned by the supplied signerAddress.
+ */
+export async function isValidPresignedSignatureAsync(
+ provider: Provider,
+ data: string,
+ signerAddress: string,
+): Promise<boolean> {
+ const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider);
+ const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress);
+ return isValid;
+}
+
+/**
+ * Verifies that the provided wallet signature is valid according to the 0x Protocol smart contracts
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned]
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the data was preSigned by the supplied signerAddress.
+ */
+export async function isValidWalletSignatureAsync(
+ provider: Provider,
+ data: string,
+ signature: string,
+ signerAddress: string,
+): Promise<boolean> {
+ // tslint:disable-next-line:custom-no-magic-numbers
+ const signatureWithoutType = signature.slice(-2);
+ const walletContract = new IWalletContract(artifacts.IWallet.abi, signerAddress, provider);
+ const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType);
+ return isValid;
+}
+
+/**
+ * Verifies that the provided validator signature is valid according to the 0x Protocol smart contracts
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned]
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the data was preSigned by the supplied signerAddress.
+ */
+export async function isValidValidatorSignatureAsync(
+ provider: Provider,
+ data: string,
+ signature: string,
+ signerAddress: string,
+): Promise<boolean> {
+ const validatorSignature = parseValidatorSignature(signature);
+ const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider);
+ const isValidatorApproved = await exchangeContract.allowedValidators.callAsync(
+ signerAddress,
+ validatorSignature.validatorAddress,
+ );
+ if (!isValidatorApproved) {
+ throw new Error(`Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`);
+ }
+
+ const validatorContract = new IValidatorContract(artifacts.IValidator.abi, signerAddress, provider);
+ const isValid = await validatorContract.isValidSignature.callAsync(
+ data,
+ signerAddress,
+ validatorSignature.signature,
+ );
+ return isValid;
+}
+
+/**
+ * Checks if the supplied elliptic curve signature corresponds to signing `data` with
+ * the private key corresponding to `signerAddress`
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature An object containing the elliptic curve signature parameters.
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the ECSignature is valid.
+ */
+export function isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
assert.isHexString('data', data);
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
assert.isETHAddressHex('signerAddress', signerAddress);
const normalizedSignerAddress = signerAddress.toLowerCase();
- const dataBuff = ethUtil.toBuffer(data);
- const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
+ const msgHashBuff = ethUtil.toBuffer(data);
try {
const pubKey = ethUtil.ecrecover(
msgHashBuff,
@@ -36,23 +175,23 @@ export function isValidSignature(data: string, signature: ECSignature, signerAdd
return false;
}
}
+
/**
* Signs an orderHash and returns it's elliptic curve signature.
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
* @param orderHash Hex encoded orderHash to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the Provider supplied to 0x.js.
- * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message`
- * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
- * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
- * before sending the request.
+ * @param hashPrefixOpts Different signers add/require different prefixes be appended to the message being signed.
+ * Since we cannot know ahead of time which signer you are using, you must supply both a prefixType and
+ * whether it must be added before calling `eth_sign` (some signers add it themselves)
* @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
*/
-export async function signOrderHashAsync(
+export async function ecSignOrderHashAsync(
provider: Provider,
orderHash: string,
signerAddress: string,
- shouldAddPersonalMessagePrefix: boolean,
+ messagePrefixOpts: MessagePrefixOpts,
): Promise<ECSignature> {
assert.isHexString('orderHash', orderHash);
const web3Wrapper = new Web3Wrapper(provider);
@@ -60,12 +199,10 @@ export async function signOrderHashAsync(
const normalizedSignerAddress = signerAddress.toLowerCase();
let msgHashHex = orderHash;
- if (shouldAddPersonalMessagePrefix) {
- const orderHashBuff = ethUtil.toBuffer(orderHash);
- const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
- msgHashHex = ethUtil.bufferToHex(msgHashBuff);
+ const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, messagePrefixOpts.prefixType);
+ if (messagePrefixOpts.shouldAddPrefixBeforeCallingEthSign) {
+ msgHashHex = prefixedMsgHashHex;
}
-
const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
// HACK: There is no consensus on whether the signatureHex string should be formatted as
@@ -76,7 +213,7 @@ export async function signOrderHashAsync(
const validVParamValues = [27, 28];
const ecSignatureVRS = parseSignatureHexAsVRS(signature);
if (_.includes(validVParamValues, ecSignatureVRS.v)) {
- const isValidVRSSignature = isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress);
+ const isValidVRSSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureVRS, normalizedSignerAddress);
if (isValidVRSSignature) {
return ecSignatureVRS;
}
@@ -84,7 +221,7 @@ export async function signOrderHashAsync(
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
- const isValidRSVSignature = isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress);
+ const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress);
if (isValidRSVSignature) {
return ecSignatureRSV;
}
@@ -93,6 +230,64 @@ export async function signOrderHashAsync(
throw new Error(OrderError.InvalidSignature);
}
+/**
+ * Adds the relevant prefix to the message being signed.
+ * @param message Message to sign
+ * @param messagePrefixType The type of message prefix to add. Different signers expect
+ * specific message prefixes.
+ * @return Prefixed message
+ */
+export function addSignedMessagePrefix(message: string, messagePrefixType: MessagePrefixType): string {
+ switch (messagePrefixType) {
+ case MessagePrefixType.None:
+ return message;
+
+ case MessagePrefixType.EthSign: {
+ const msgBuff = ethUtil.toBuffer(message);
+ const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff);
+ const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
+ return prefixedMsgHex;
+ }
+
+ case MessagePrefixType.Trezor: {
+ const msgBuff = ethUtil.toBuffer(message);
+ const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff);
+ const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
+ return prefixedMsgHex;
+ }
+
+ default:
+ throw new Error(`Unrecognized MessagePrefixType: ${messagePrefixType}`);
+ }
+}
+
+function hashTrezorPersonalMessage(message: Buffer): Buffer {
+ const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.length));
+ return ethUtil.sha3(Buffer.concat([prefix, message]));
+}
+
+function parseECSignature(signature: string): ECSignature {
+ const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor];
+ assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes);
+
+ // tslint:disable-next-line:custom-no-magic-numbers
+ const vrsHex = signature.slice(0, -2);
+ const ecSignature = parseSignatureHexAsVRS(vrsHex);
+
+ return ecSignature;
+}
+
+function parseValidatorSignature(signature: string): ValidatorSignature {
+ assert.isOneOfExpectedSignatureTypes(signature, [SignatureType.Validator]);
+ // tslint:disable:custom-no-magic-numbers
+ const validatorSignature = {
+ validatorAddress: signature.slice(-22, -2),
+ signature: signature.slice(0, -22),
+ };
+ // tslint:enable:custom-no-magic-numbers
+ return validatorSignature;
+}
+
function parseSignatureHexAsVRS(signatureHex: string): ECSignature {
const signatureBuffer = ethUtil.toBuffer(signatureHex);
let v = signatureBuffer[0];
diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts
index f79d52359..db0bfb249 100644
--- a/packages/order-utils/src/types.ts
+++ b/packages/order-utils/src/types.ts
@@ -1,3 +1,25 @@
export enum OrderError {
InvalidSignature = 'INVALID_SIGNATURE',
}
+
+/**
+ * The requisite message prefix (is any) to add to an `eth_sign` request.
+ */
+export enum MessagePrefixType {
+ None = 'NONE',
+ EthSign = 'ETH_SIGN',
+ Trezor = 'TREZOR',
+}
+
+/**
+ * Options related to message prefixing of messages sent to `eth_sign`
+ * Some signers prepend a message prefix (e.g Parity Signer, Ledger, TestRPC), while
+ * others require it already be prepended (e.g Metamask). In addition, different signers
+ * expect slightly different prefixes (See: https://github.com/ethereum/go-ethereum/issues/14794).
+ * Depending on the signer that will receive your signing request, you must specify the
+ * desired prefix and whether it should be added before making the `eth_sign` request.
+ */
+export interface MessagePrefixOpts {
+ prefixType: MessagePrefixType;
+ shouldAddPrefixBeforeCallingEthSign: boolean;
+}
diff --git a/packages/order-utils/src/utils.ts b/packages/order-utils/src/utils.ts
new file mode 100644
index 000000000..3b465cece
--- /dev/null
+++ b/packages/order-utils/src/utils.ts
@@ -0,0 +1,9 @@
+export const utils = {
+ getSignatureTypeIndexIfExists(signature: string): number {
+ // tslint:disable-next-line:custom-no-magic-numbers
+ const signatureTypeHex = signature.slice(-2);
+ const base = 16;
+ const signatureTypeInt = parseInt(signatureTypeHex, base);
+ return signatureTypeInt;
+ },
+};