From 07926ded6ef194969ffe26e3879d6e86a0eb9c50 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 2 Oct 2018 17:32:28 +1000 Subject: Introduce Metamask Subprovider. MM has a number of inconsistencies with other providers when implementing the JSON RPC interface. This subprovider wraps those nuances so they do not leak into the rest of our code --- packages/order-utils/src/index.ts | 1 - packages/order-utils/src/order_factory.ts | 9 +-- packages/order-utils/src/signature_utils.ts | 120 +++++++++++----------------- 3 files changed, 48 insertions(+), 82 deletions(-) (limited to 'packages/order-utils/src') diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 1553647c6..e7a23682c 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -29,7 +29,6 @@ export { ERC20AssetData, ERC721AssetData, AssetProxyId, - SignerType, SignatureType, OrderStateValid, OrderStateInvalid, diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index b1292903a..0f0cd6046 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -1,4 +1,4 @@ -import { Order, SignedOrder, SignerType } from '@0xproject/types'; +import { Order, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; @@ -71,12 +71,7 @@ export const orderFactory = { createOrderOpts, ); const orderHash = orderHashUtils.getOrderHashHex(order); - const signature = await signatureUtils.ecSignOrderHashAsync( - provider, - orderHash, - makerAddress, - SignerType.Default, - ); + const signature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); const signedOrder: SignedOrder = _.assign(order, { signature }); return signedOrder; }, diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 05c673ae2..8e0fd702b 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -1,5 +1,5 @@ import { schemas } from '@0xproject/json-schemas'; -import { ECSignature, Order, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types'; +import { ECSignature, Order, SignatureType, ValidatorSignature } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; @@ -11,7 +11,7 @@ import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from import { ExchangeContract } from './generated_contract_wrappers/exchange'; import { IValidatorContract } from './generated_contract_wrappers/i_validator'; import { IWalletContract } from './generated_contract_wrappers/i_wallet'; -import { EIP712_ORDER_SCHEMA } from './order_hash'; +import { EIP712_ORDER_SCHEMA, orderHashUtils } from './order_hash'; import { OrderError } from './types'; import { utils } from './utils'; @@ -51,7 +51,7 @@ export const signatureUtils = { case SignatureType.EthSign: { const ecSignature = signatureUtils.parseECSignature(signature); - const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data, SignerType.Default); + const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data); return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); } @@ -194,19 +194,41 @@ export const signatureUtils = { } }, /** - * Signs an order using `eth_signTypedData` and returns it's elliptic curve signature and signature type. - * This method currently supports Ganache. + * Signs an order and returns its elliptic curve signature and signature type. First `eth_signTypedData` is requested + * then a fallback to `eth_sign` if not available on this provider. * @param order The Order 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. * @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type. */ async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { + try { + const signatureHex = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); + return signatureHex; + } catch (err) { + // Fallback to using EthSign when ethSignTypedData is not supported + const orderHash = orderHashUtils.getOrderHashHex(order); + const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress); + return signatureHex; + } + }, + /** + * Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type. + * @param order The Order 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. + * @return A hex encoded string containing the Elliptic curve signature generated by signing the order with `eth_signTypedData` + * and the Signature Type. + */ + async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { assert.isWeb3Provider('provider', provider); assert.isETHAddressHex('signerAddress', signerAddress); const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); const normalizedSignerAddress = signerAddress.toLowerCase(); + const normalizedOrder = _.mapValues(order, value => { + return _.isObject(value) ? value.toString() : value; + }); const typedData = { types: { EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters, @@ -217,15 +239,7 @@ export const signatureUtils = { version: EIP712_DOMAIN_VERSION, verifyingContract: order.exchangeAddress, }, - message: { - ...order, - salt: order.salt.toString(), - makerFee: order.makerFee.toString(), - takerFee: order.takerFee.toString(), - makerAssetAmount: order.makerAssetAmount.toString(), - takerAssetAmount: order.takerAssetAmount.toString(), - expirationTimeSeconds: order.expirationTimeSeconds.toString(), - }, + message: normalizedOrder, primaryType: 'Order', }; const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); @@ -240,36 +254,23 @@ export const signatureUtils = { return signatureHex; }, /** - * Signs an orderHash and returns it's elliptic curve signature and signature type. + * Signs a hash and returns its elliptic curve signature and signature type. * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 - * @param orderHash Hex encoded orderHash to sign. + * @param msgHash Hex encoded message 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 signerType Different signers add/require different prefixes to be prepended to the message being signed. - * Since we cannot know ahead of time which signer you are using, you must supply a SignerType. - * @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type. + * @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type. */ - async ecSignOrderHashAsync( - provider: Provider, - orderHash: string, - signerAddress: string, - signerType: SignerType, - ): Promise { + async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise { assert.isWeb3Provider('provider', provider); - assert.isHexString('orderHash', orderHash); + assert.isHexString('msgHash', msgHash); assert.isETHAddressHex('signerAddress', signerAddress); const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); const normalizedSignerAddress = signerAddress.toLowerCase(); - let msgHashHex = orderHash; - const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(orderHash, signerType); - // Metamask incorrectly implements eth_sign and does not prefix the message as per the spec - // Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e - if (signerType === SignerType.Metamask) { - msgHashHex = prefixedMsgHashHex; - } - const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex); + const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash); + const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash); // HACK: There is no consensus on whether the signatureHex string should be formatted as // v + r + s OR r + s + v, and different clients (even different versions of the same client) @@ -286,10 +287,7 @@ export const signatureUtils = { normalizedSignerAddress, ); if (isValidRSVSignature) { - const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex( - ecSignatureRSV, - signerType, - ); + const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureRSV); return convertedSignatureHex; } } @@ -301,10 +299,7 @@ export const signatureUtils = { normalizedSignerAddress, ); if (isValidVRSSignature) { - const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex( - ecSignatureVRS, - signerType, - ); + const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureVRS); return convertedSignatureHex; } } @@ -312,30 +307,18 @@ export const signatureUtils = { throw new Error(OrderError.InvalidSignature); }, /** - * Combines ECSignature with V,R,S and the relevant signature type for use in 0x protocol + * Combines ECSignature with V,R,S and the EthSign signature type for use in 0x protocol * @param ecSignature The ECSignature of the signed data - * @param signerType The SignerType of the signed data * @return Hex encoded string of signature (v,r,s) with Signature Type */ - convertECSignatureToSignatureHex(ecSignature: ECSignature, signerType: SignerType): string { + convertECSignatureToSignatureHex(ecSignature: ECSignature): string { const signatureBuffer = Buffer.concat([ ethUtil.toBuffer(ecSignature.v), ethUtil.toBuffer(ecSignature.r), ethUtil.toBuffer(ecSignature.s), ]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; - let signatureType; - switch (signerType) { - case SignerType.Metamask: - case SignerType.Ledger: - case SignerType.Default: { - signatureType = SignatureType.EthSign; - break; - } - default: - throw new Error(`Unrecognized SignerType: ${signerType}`); - } - const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, signatureType); + const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign); return signatureWithType; }, /** @@ -352,28 +335,17 @@ export const signatureUtils = { /** * Adds the relevant prefix to the message being signed. * @param message Message to sign - * @param signerType The type of message prefix to add for a given SignerType. Different signers expect - * specific message prefixes. * @return Prefixed message */ - addSignedMessagePrefix(message: string, signerType: SignerType = SignerType.Default): string { + addSignedMessagePrefix(message: string): string { assert.isString('message', message); - assert.doesBelongToStringEnum('signerType', signerType, SignerType); - switch (signerType) { - case SignerType.Metamask: - case SignerType.Ledger: - case SignerType.Default: { - const msgBuff = ethUtil.toBuffer(message); - const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); - const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); - return prefixedMsgHex; - } - default: - throw new Error(`Unrecognized SignerType: ${signerType}`); - } + const msgBuff = ethUtil.toBuffer(message); + const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); + const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); + return prefixedMsgHex; }, /** - * Parse a 0x protocol hex-encoded signature string into it's ECSignature components + * Parse a 0x protocol hex-encoded signature string into its ECSignature components * @param signature A hex encoded ecSignature 0x Protocol signature * @return An ECSignature object with r,s,v parameters */ -- cgit