diff options
author | Jacob Evans <jacob@dekz.net> | 2018-10-02 15:32:28 +0800 |
---|---|---|
committer | Jacob Evans <jacob@dekz.net> | 2018-10-05 10:00:41 +0800 |
commit | 07926ded6ef194969ffe26e3879d6e86a0eb9c50 (patch) | |
tree | 9603fa9942cdf1c364fe794e037013351416fc73 /packages/order-utils | |
parent | adcfaa2e80389f69e196b602955cee858a1eb40f (diff) | |
download | dexon-sol-tools-07926ded6ef194969ffe26e3879d6e86a0eb9c50.tar.gz dexon-sol-tools-07926ded6ef194969ffe26e3879d6e86a0eb9c50.tar.zst dexon-sol-tools-07926ded6ef194969ffe26e3879d6e86a0eb9c50.zip |
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
Diffstat (limited to 'packages/order-utils')
-rw-r--r-- | packages/order-utils/src/index.ts | 1 | ||||
-rw-r--r-- | packages/order-utils/src/order_factory.ts | 9 | ||||
-rw-r--r-- | packages/order-utils/src/signature_utils.ts | 120 | ||||
-rw-r--r-- | packages/order-utils/test/signature_utils_test.ts | 104 |
4 files changed, 63 insertions, 171 deletions
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<string> { + 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<string> { 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<string> { + async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise<string> { 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 */ diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 40ce16165..03354cd65 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -1,4 +1,4 @@ -import { Order, SignatureType, SignerType } from '@0xproject/types'; +import { Order, SignatureType } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types'; @@ -153,10 +153,17 @@ describe('Signature utils', () => { ]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; const eip712Signature = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress); + const isValidSignature = await signatureUtils.isValidSignatureAsync( + provider, + orderHashHex, + eip712Signature, + makerAddress, + ); expect(signatureHex).to.eq(eip712Signature); + expect(isValidSignature).to.eq(true); }); }); - describe('#ecSignOrderHashAsync', () => { + describe('#ecSignHashAsync', () => { let makerAddress: string; before(async () => { const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); @@ -166,12 +173,7 @@ describe('Signature utils', () => { const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; const expectedSignature = '0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; - const ecSignature = await signatureUtils.ecSignOrderHashAsync( - provider, - orderHash, - makerAddress, - SignerType.Default, - ); + const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); expect(ecSignature).to.equal(expectedSignature); }); it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { @@ -197,12 +199,7 @@ describe('Signature utils', () => { } }, }; - const ecSignature = await signatureUtils.ecSignOrderHashAsync( - fakeProvider, - orderHash, - makerAddress, - SignerType.Default, - ); + const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress); expect(ecSignature).to.equal(expectedSignature); }); it('should return the correct Signature for signatureHex concatenated as V + R + S', async () => { @@ -225,56 +222,12 @@ describe('Signature utils', () => { }, }; - const ecSignature = await signatureUtils.ecSignOrderHashAsync( - fakeProvider, - orderHash, - makerAddress, - SignerType.Default, - ); - expect(ecSignature).to.equal(expectedSignature); - }); - // Note this is due to a bug in Metamask where it does not prefix before signing, this is a known issue and is to be fixed in the future - // Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e - it('should receive a payload modified with a prefix when Metamask is SignerType', async () => { - const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; - const orderHashPrefixed = '0xae70f31d26096291aa681b26cb7574563956221d0b4213631e1ef9df675d4cba'; - const expectedSignature = - '0x1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03'; - // Generated from a MM eth_sign request from 0x5409ed021d9299bf6814279a6a1411a7e866a631 signing 0xae70f31d26096291aa681b26cb7574563956221d0b4213631e1ef9df675d4cba - const metamaskSignature = - '0x117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b1b'; - const fakeProvider = { - async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> { - if (payload.method === 'eth_sign') { - const [, message] = payload.params; - expect(message).to.equal(orderHashPrefixed); - callback(null, { - id: 42, - jsonrpc: '2.0', - result: metamaskSignature, - }); - } else { - callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); - } - }, - }; - - const ecSignature = await signatureUtils.ecSignOrderHashAsync( - fakeProvider, - orderHash, - makerAddress, - SignerType.Metamask, - ); + const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress); expect(ecSignature).to.equal(expectedSignature); }); it('should return a valid signature', async () => { const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; - const ecSignature = await signatureUtils.ecSignOrderHashAsync( - provider, - orderHash, - makerAddress, - SignerType.Default, - ); + const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); const isValidSignature = await signatureUtils.isValidSignatureAsync( provider, @@ -291,38 +244,11 @@ describe('Signature utils', () => { r: '0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d64393', s: '0x46b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf2', }; - it('should concatenate v,r,s and append the EthSign signature type when SignerType is Default', async () => { + it('should concatenate v,r,s and append the EthSign signature type', async () => { const expectedSignatureWithSignatureType = '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; - const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex( - ecSignature, - SignerType.Default, - ); + const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(ecSignature); expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); }); - it('should concatenate v,r,s and append the EthSign signature type when SignerType is Ledger', async () => { - const expectedSignatureWithSignatureType = - '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; - const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex( - ecSignature, - SignerType.Ledger, - ); - expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); - }); - it('should concatenate v,r,s and append the EthSign signature type when SignerType is Metamask', async () => { - const expectedSignatureWithSignatureType = - '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; - const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex( - ecSignature, - SignerType.Metamask, - ); - expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); - }); - it('should throw if the SignerType is invalid', async () => { - const expectedMessage = 'Unrecognized SignerType: INVALID_SIGNER'; - expect(() => - signatureUtils.convertECSignatureToSignatureHex(ecSignature, 'INVALID_SIGNER' as SignerType), - ).to.throw(expectedMessage); - }); }); }); |