@0x/order-utils: Add getOrderHash(), getExchangeTransactionHash(), getExchangeProxyTransactionHash()

This commit is contained in:
Lawrence Forman 2020-06-22 15:36:53 -04:00
parent aced474dc5
commit 829a353b14
7 changed files with 171 additions and 8 deletions

View File

@ -5,6 +5,10 @@
{ {
"note": "Add ERC20 Transformer utils and export useful constants.", "note": "Add ERC20 Transformer utils and export useful constants.",
"pr": 2604 "pr": 2604
},
{
"note": "Add `getOrderHash()`, `getExchangeTransactionHash()`, `getExchangeProxyTransactionHash()`",
"pr": 2610
} }
] ]
}, },

View File

@ -64,6 +64,7 @@
}, },
"dependencies": { "dependencies": {
"@0x/assert": "^3.0.8", "@0x/assert": "^3.0.8",
"@0x/contract-addresses": "^4.10.0",
"@0x/contract-wrappers": "^13.7.0", "@0x/contract-wrappers": "^13.7.0",
"@0x/json-schemas": "^5.0.8", "@0x/json-schemas": "^5.0.8",
"@0x/utils": "^5.5.0", "@0x/utils": "^5.5.0",

View File

@ -1,3 +1,4 @@
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import { BigNumber, NULL_ADDRESS, NULL_BYTES } from '@0x/utils'; import { BigNumber, NULL_ADDRESS, NULL_BYTES } from '@0x/utils';
import { MethodAbi } from 'ethereum-types'; import { MethodAbi } from 'ethereum-types';
@ -150,6 +151,27 @@ export const constants = {
{ name: 'transactionSignature', type: 'bytes' }, { name: 'transactionSignature', type: 'bytes' },
], ],
}, },
MAINNET_EXCHANGE_PROXY_DOMAIN: {
name: 'ZeroEx',
version: '1.0.0',
chainId: 1,
verifyingContract: getContractAddressesForChainOrThrow(1).exchangeProxy,
},
EXCHANGE_PROXY_MTX_SCEHMA: {
name: 'MetaTransactionData',
parameters: [
{ name: 'signer', type: 'address' },
{ name: 'sender', type: 'address' },
{ name: 'minGasPrice', type: 'uint256' },
{ name: 'maxGasPrice', type: 'uint256' },
{ name: 'expirationTime', type: 'uint256' },
{ name: 'salt', type: 'uint256' },
{ name: 'callData', type: 'bytes' },
{ name: 'value', type: 'uint256' },
{ name: 'feeToken', type: 'address' },
{ name: 'feeAmount', type: 'uint256' },
],
},
ERC20_METHOD_ABI, ERC20_METHOD_ABI,
ERC721_METHOD_ABI, ERC721_METHOD_ABI,
MULTI_ASSET_METHOD_ABI, MULTI_ASSET_METHOD_ABI,

View File

@ -5,11 +5,12 @@ import {
EIP712Object, EIP712Object,
EIP712TypedData, EIP712TypedData,
EIP712Types, EIP712Types,
ExchangeProxyMetaTransaction,
Order, Order,
SignedZeroExTransaction, SignedZeroExTransaction,
ZeroExTransaction, ZeroExTransaction,
} from '@0x/types'; } from '@0x/types';
import { hexUtils, signTypedDataUtils } from '@0x/utils'; import { BigNumber, hexUtils, signTypedDataUtils } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { constants } from './constants'; import { constants } from './constants';
@ -131,4 +132,18 @@ export const eip712Utils = {
); );
return typedData; return typedData;
}, },
createExchangeProxyMetaTransactionTypedData(mtx: ExchangeProxyMetaTransaction): EIP712TypedData {
return eip712Utils.createTypedData(
constants.EXCHANGE_PROXY_MTX_SCEHMA.name,
{
MetaTransactionData: constants.EXCHANGE_PROXY_MTX_SCEHMA.parameters,
},
_.mapValues(
_.omit(mtx, 'domain'),
// tslint:disable-next-line: custom-no-magic-numbers
v => (BigNumber.isBigNumber(v) ? v.toString(10) : v),
) as EIP712Object,
_domain,
);
},
}; };

View File

@ -0,0 +1,29 @@
import { ExchangeProxyMetaTransaction, Order, ZeroExTransaction } from '@0x/types';
import { hexUtils, signTypedDataUtils } from '@0x/utils';
import { eip712Utils } from './eip712_utils';
import { orderHashUtils } from './order_hash_utils';
import { transactionHashUtils } from './transaction_hash_utils';
/**
* Compute the EIP712 hash of an order.
*/
export function getOrderHash(order: Order): string {
return orderHashUtils.getOrderHash(order);
}
/**
* Compute the EIP712 hash of an Exchange meta-transaction.
*/
export function getExchangeMetaTransactionHash(tx: ZeroExTransaction): string {
return transactionHashUtils.getTransactionHash(tx);
}
/**
* Compute the EIP712 hash of an Exchange Proxy meta-transaction.
*/
export function getExchangeProxyMetaTransactionHash(mtx: ExchangeProxyMetaTransaction): string {
return hexUtils.toHex(
signTypedDataUtils.generateTypedDataHash(eip712Utils.createExchangeProxyMetaTransactionTypedData(mtx)),
);
}

View File

@ -49,6 +49,8 @@ export {
ZeroExTransaction, ZeroExTransaction,
SignedZeroExTransaction, SignedZeroExTransaction,
ValidatorSignature, ValidatorSignature,
ExchangeProxyMetaTransaction,
SignedExchangeProxyMetaTransaction,
} from '@0x/types'; } from '@0x/types';
export { export {
@ -77,6 +79,8 @@ export {
decodeAffiliateFeeTransformerData, decodeAffiliateFeeTransformerData,
} from './transformer_data_encoders'; } from './transformer_data_encoders';
export { getOrderHash, getExchangeMetaTransactionHash, getExchangeProxyMetaTransactionHash } from './hash_utils';
import { constants } from './constants'; import { constants } from './constants';
export const NULL_ADDRESS = constants.NULL_ADDRESS; export const NULL_ADDRESS = constants.NULL_ADDRESS;
export const NULL_BYTES = constants.NULL_BYTES; export const NULL_BYTES = constants.NULL_BYTES;

View File

@ -1,14 +1,16 @@
import { schemas } from '@0x/json-schemas'; import { schemas } from '@0x/json-schemas';
import { import {
ECSignature, ECSignature,
ExchangeProxyMetaTransaction,
Order, Order,
SignatureType, SignatureType,
SignedExchangeProxyMetaTransaction,
SignedOrder, SignedOrder,
SignedZeroExTransaction, SignedZeroExTransaction,
ValidatorSignature, ValidatorSignature,
ZeroExTransaction, ZeroExTransaction,
} from '@0x/types'; } from '@0x/types';
import { providerUtils } from '@0x/utils'; import { hexUtils, providerUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper'; import { Web3Wrapper } from '@0x/web3-wrapper';
import { SupportedProvider } from 'ethereum-types'; import { SupportedProvider } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util'; import * as ethUtil from 'ethereumjs-util';
@ -16,6 +18,7 @@ import * as _ from 'lodash';
import { assert } from './assert'; import { assert } from './assert';
import { eip712Utils } from './eip712_utils'; import { eip712Utils } from './eip712_utils';
import { getExchangeProxyMetaTransactionHash } from './hash_utils';
import { orderHashUtils } from './order_hash_utils'; import { orderHashUtils } from './order_hash_utils';
import { transactionHashUtils } from './transaction_hash_utils'; import { transactionHashUtils } from './transaction_hash_utils';
import { TypedDataError } from './types'; import { TypedDataError } from './types';
@ -187,6 +190,96 @@ export const signatureUtils = {
} }
} }
}, },
/**
* Signs an Exchange Proxy meta-transaction and returns a SignedExchangeProxyMetaTransaction.
* First `eth_signTypedData` is requested then a fallback to `eth_sign` if not
* available on the supplied provider.
* @param supportedProvider Web3 provider to use for all JSON RPC requests
* @param transaction The ExchangeProxyMetaTransaction to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the supplied Provider.
* @return A SignedExchangeProxyMetaTransaction containing the order and
* elliptic curve signature with Signature Type.
*/
async ecSignExchangeProxyMetaTransactionAsync(
supportedProvider: SupportedProvider,
transaction: ExchangeProxyMetaTransaction,
signerAddress: string,
): Promise<SignedExchangeProxyMetaTransaction> {
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
try {
const signedTransaction = await signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
supportedProvider,
transaction,
signerAddress,
);
return signedTransaction;
} catch (err) {
// HACK: We are unable to handle specific errors thrown since provider is not an object
// under our control. It could be Metamask Web3, Ethers, or any general RPC provider.
// We check for a user denying the signature request in a way that supports Metamask and
// Coinbase Wallet. Unfortunately for signers with a different error message,
// they will receive two signature requests.
if (err.message.includes('User denied message signature')) {
throw err;
}
const transactionHash = getExchangeProxyMetaTransactionHash(transaction);
const signatureHex = await signatureUtils.ecSignHashAsync(
supportedProvider,
transactionHash,
signerAddress,
);
const signedTransaction = {
...transaction,
signature: signatureHex,
};
return signedTransaction;
}
},
/**
* Signs an Exchange Proxy meta-transaction using `eth_signTypedData` and
* returns a SignedZeroExTransaction.
* @param supportedProvider Web3 provider to use for all JSON RPC requests
* @param transaction The Exchange Proxy transaction to sign.
* @param signerAddress The hex encoded Ethereum address you wish
* to sign it with. This address must be available via the supplied Provider.
* @return A SignedExchangeProxyMetaTransaction containing the
* ExchangeProxyMetaTransaction and elliptic curve signature with Signature Type.
*/
async ecSignTypedDataExchangeProxyMetaTransactionAsync(
supportedProvider: SupportedProvider,
transaction: ExchangeProxyMetaTransaction,
signerAddress: string,
): Promise<SignedExchangeProxyMetaTransaction> {
const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isETHAddressHex('signerAddress', signerAddress);
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
const web3Wrapper = new Web3Wrapper(provider);
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
const normalizedSignerAddress = signerAddress.toLowerCase();
const typedData = eip712Utils.createExchangeProxyMetaTransactionTypedData(transaction);
try {
const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData);
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
const signatureHex = hexUtils.concat(
ecSignatureRSV.v,
ecSignatureRSV.r,
ecSignatureRSV.s,
SignatureType.EIP712,
);
return {
...transaction,
signature: signatureHex,
};
} catch (err) {
// Detect if Metamask to transition users to the MetamaskSubprovider
if ((provider as any).isMetaMask) {
throw new Error(TypedDataError.InvalidMetamaskSigner);
} else {
throw err;
}
}
},
/** /**
* Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type. * Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type.
* @param supportedProvider Web3 provider to use for all JSON RPC requests * @param supportedProvider Web3 provider to use for all JSON RPC requests
@ -245,12 +338,7 @@ export const signatureUtils = {
* @return Hex encoded string of signature (v,r,s) with Signature Type * @return Hex encoded string of signature (v,r,s) with Signature Type
*/ */
convertECSignatureToSignatureHex(ecSignature: ECSignature): string { convertECSignatureToSignatureHex(ecSignature: ECSignature): string {
const signatureBuffer = Buffer.concat([ const signatureHex = hexUtils.concat(ecSignature.v, ecSignature.r, ecSignature.s);
ethUtil.toBuffer(ecSignature.v),
ethUtil.toBuffer(ecSignature.r),
ethUtil.toBuffer(ecSignature.s),
]);
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign); const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign);
return signatureWithType; return signatureWithType;
}, },