From adcfaa2e80389f69e196b602955cee858a1eb40f Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 1 Oct 2018 20:37:13 +1000 Subject: Expose eth_signTypedData functionality for order signing --- packages/order-utils/src/eip712_utils.ts | 6 +-- packages/order-utils/src/order_hash.ts | 2 +- packages/order-utils/src/signature_utils.ts | 50 ++++++++++++++++++++- packages/order-utils/test/signature_utils_test.ts | 55 ++++++++++++++++++----- 4 files changed, 98 insertions(+), 15 deletions(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts index b303c93dc..01f1e930e 100644 --- a/packages/order-utils/src/eip712_utils.ts +++ b/packages/order-utils/src/eip712_utils.ts @@ -5,11 +5,11 @@ import { crypto } from './crypto'; import { EIP712Schema, EIP712Types } from './types'; const EIP191_PREFIX = '\x19\x01'; -const EIP712_DOMAIN_NAME = '0x Protocol'; -const EIP712_DOMAIN_VERSION = '2'; const EIP712_VALUE_LENGTH = 32; +export const EIP712_DOMAIN_NAME = '0x Protocol'; +export const EIP712_DOMAIN_VERSION = '2'; -const EIP712_DOMAIN_SCHEMA: EIP712Schema = { +export const EIP712_DOMAIN_SCHEMA: EIP712Schema = { name: 'EIP712Domain', parameters: [ { name: 'name', type: EIP712Types.String }, diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts index 8e98f8767..151c1f801 100644 --- a/packages/order-utils/src/order_hash.ts +++ b/packages/order-utils/src/order_hash.ts @@ -8,7 +8,7 @@ import { EIP712Schema, EIP712Types } from './types'; const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string'; -const EIP712_ORDER_SCHEMA: EIP712Schema = { +export const EIP712_ORDER_SCHEMA: EIP712Schema = { name: 'Order', parameters: [ { name: 'makerAddress', type: EIP712Types.Address }, diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 3b656d3fc..05c673ae2 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, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types'; +import { ECSignature, Order, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; @@ -7,9 +7,11 @@ import * as _ from 'lodash'; import { artifacts } from './artifacts'; import { assert } from './assert'; +import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './eip712_utils'; 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 { OrderError } from './types'; import { utils } from './utils'; @@ -191,6 +193,52 @@ export const signatureUtils = { return false; } }, + /** + * Signs an order using `eth_signTypedData` and returns it's elliptic curve signature and signature type. + * This method currently supports Ganache. + * @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 { + assert.isWeb3Provider('provider', provider); + assert.isETHAddressHex('signerAddress', signerAddress); + const web3Wrapper = new Web3Wrapper(provider); + await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); + const normalizedSignerAddress = signerAddress.toLowerCase(); + const typedData = { + types: { + EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters, + Order: EIP712_ORDER_SCHEMA.parameters, + }, + domain: { + name: EIP712_DOMAIN_NAME, + 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(), + }, + primaryType: 'Order', + }; + const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); + const ecSignatureRSV = parseSignatureHexAsRSV(signature); + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(ecSignatureRSV.v), + ethUtil.toBuffer(ecSignatureRSV.r), + ethUtil.toBuffer(ecSignatureRSV.s), + ethUtil.toBuffer(SignatureType.EIP712), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + return signatureHex; + }, /** * Signs an orderHash and returns it's elliptic curve signature and signature type. * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 2ca1109a1..40ce16165 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -1,12 +1,13 @@ -import { SignerType } from '@0xproject/types'; +import { Order, SignatureType, SignerType } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import 'mocha'; -import * as Sinon from 'sinon'; -import { generatePseudoRandomSalt } from '../src'; +import { generatePseudoRandomSalt, orderHashUtils } from '../src'; +import { constants } from '../src/constants'; import { signatureUtils } from '../src/signature_utils'; import { chaiSetup } from './utils/chai_setup'; @@ -115,19 +116,53 @@ describe('Signature utils', () => { expect(salt.lessThan(twoPow256)).to.be.true(); }); }); - describe('#ecSignOrderHashAsync', () => { - let stubs: Sinon.SinonStub[] = []; + describe('#ecSignOrderAsync', () => { let makerAddress: string; + const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; + let order: Order; before(async () => { const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); makerAddress = availableAddreses[0]; + order = { + makerAddress, + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + makerAssetData: constants.NULL_ADDRESS, + takerAssetData: constants.NULL_ADDRESS, + exchangeAddress: fakeExchangeContractAddress, + salt: new BigNumber(0), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerAssetAmount: new BigNumber(0), + takerAssetAmount: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + }; }); - afterEach(() => { - // clean up any stubs after the test has completed - _.each(stubs, s => s.restore()); - stubs = []; + it('should result in the same signature as signing order hash without prefix', async () => { + const orderHashHex = orderHashUtils.getOrderHashHex(order); + const sig = ethUtil.ecsign( + ethUtil.toBuffer(orderHashHex), + Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'), + ); + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(sig.v), + ethUtil.toBuffer(sig.r), + ethUtil.toBuffer(sig.s), + ethUtil.toBuffer(SignatureType.EIP712), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + const eip712Signature = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress); + expect(signatureHex).to.eq(eip712Signature); + }); + }); + describe('#ecSignOrderHashAsync', () => { + let makerAddress: string; + before(async () => { + const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); + makerAddress = availableAddreses[0]; }); - it('Should return the correct Signature', async () => { + it('should return the correct Signature', async () => { const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; const expectedSignature = '0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; -- cgit v1.2.3 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 +++++++++------------- packages/order-utils/test/signature_utils_test.ts | 104 +++---------------- 4 files changed, 63 insertions(+), 171 deletions(-) (limited to 'packages/order-utils') 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 */ 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 { - 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); - }); }); }); -- cgit v1.2.3 From 2a82ff48c061eacb3b6f9fb36eeae7f515b6d11d Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 4 Oct 2018 17:12:35 +1000 Subject: Move SignTypedData to utils package --- packages/order-utils/src/constants.ts | 12 +++ packages/order-utils/src/eip712_utils.ts | 109 ---------------------------- packages/order-utils/src/index.ts | 4 - packages/order-utils/src/order_hash.ts | 52 ++++++++----- packages/order-utils/src/signature_utils.ts | 2 +- packages/order-utils/src/types.ts | 18 ----- 6 files changed, 45 insertions(+), 152 deletions(-) delete mode 100644 packages/order-utils/src/eip712_utils.ts (limited to 'packages/order-utils') diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index c23578c20..cc03755c3 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -14,3 +14,15 @@ export const constants = { INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite ZERO_AMOUNT: new BigNumber(0), }; + +export const EIP712_DOMAIN_NAME = '0x Protocol'; +export const EIP712_DOMAIN_VERSION = '2'; + +export const EIP712_DOMAIN_SCHEMA = { + name: 'EIP712Domain', + parameters: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string ' }, + { name: 'verifyingContract', type: 'address' }, + ], +}; diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts deleted file mode 100644 index 01f1e930e..000000000 --- a/packages/order-utils/src/eip712_utils.ts +++ /dev/null @@ -1,109 +0,0 @@ -import ethUtil = require('ethereumjs-util'); -import * as _ from 'lodash'; - -import { crypto } from './crypto'; -import { EIP712Schema, EIP712Types } from './types'; - -const EIP191_PREFIX = '\x19\x01'; -const EIP712_VALUE_LENGTH = 32; -export const EIP712_DOMAIN_NAME = '0x Protocol'; -export const EIP712_DOMAIN_VERSION = '2'; - -export const EIP712_DOMAIN_SCHEMA: EIP712Schema = { - name: 'EIP712Domain', - parameters: [ - { name: 'name', type: EIP712Types.String }, - { name: 'version', type: EIP712Types.String }, - { name: 'verifyingContract', type: EIP712Types.Address }, - ], -}; - -export const eip712Utils = { - /** - * Compiles the EIP712Schema and returns the hash of the schema. - * @param schema The EIP712 schema. - * @return The hash of the compiled schema - */ - compileSchema(schema: EIP712Schema): Buffer { - const eip712Schema = eip712Utils._encodeType(schema); - const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]); - return eip712SchemaHashBuffer; - }, - /** - * Merges the EIP712 hash of a struct with the DomainSeparator for 0x v2. - * @param hashStruct the EIP712 hash of a struct - * @param contractAddress the exchange contract address - * @return The hash of an EIP712 message with domain separator prefixed - */ - createEIP712Message(hashStruct: Buffer, contractAddress: string): Buffer { - const domainSeparatorHashBuffer = eip712Utils._getDomainSeparatorHashBuffer(contractAddress); - const messageBuff = crypto.solSHA3([EIP191_PREFIX, domainSeparatorHashBuffer, hashStruct]); - return messageBuff; - }, - /** - * Pad an address to 32 bytes - * @param address Address to pad - * @return padded address - */ - pad32Address(address: string): Buffer { - const addressBuffer = ethUtil.toBuffer(address); - const addressPadded = eip712Utils.pad32Buffer(addressBuffer); - return addressPadded; - }, - /** - * Pad an buffer to 32 bytes - * @param buffer Address to pad - * @return padded buffer - */ - pad32Buffer(buffer: Buffer): Buffer { - const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH); - return bufferPadded; - }, - /** - * Hash together a EIP712 schema with the corresponding data - * @param schema EIP712-compliant schema - * @param data Data the complies to the schema - * @return A buffer containing the SHA256 hash of the schema and encoded data - */ - structHash(schema: EIP712Schema, data: { [key: string]: any }): Buffer { - const encodedData = eip712Utils._encodeData(schema, data); - const schemaHash = eip712Utils.compileSchema(schema); - const hashBuffer = crypto.solSHA3([schemaHash, ...encodedData]); - return hashBuffer; - }, - _getDomainSeparatorSchemaBuffer(): Buffer { - return eip712Utils.compileSchema(EIP712_DOMAIN_SCHEMA); - }, - _getDomainSeparatorHashBuffer(exchangeAddress: string): Buffer { - const domainSeparatorSchemaBuffer = eip712Utils._getDomainSeparatorSchemaBuffer(); - const encodedData = eip712Utils._encodeData(EIP712_DOMAIN_SCHEMA, { - name: EIP712_DOMAIN_NAME, - version: EIP712_DOMAIN_VERSION, - verifyingContract: exchangeAddress, - }); - const domainSeparatorHashBuff2 = crypto.solSHA3([domainSeparatorSchemaBuffer, ...encodedData]); - return domainSeparatorHashBuff2; - }, - _encodeType(schema: EIP712Schema): string { - const namedTypes = _.map(schema.parameters, ({ name, type }) => `${type} ${name}`); - const namedTypesJoined = namedTypes.join(','); - const encodedType = `${schema.name}(${namedTypesJoined})`; - return encodedType; - }, - _encodeData(schema: EIP712Schema, data: { [key: string]: any }): any { - const encodedValues = []; - for (const parameter of schema.parameters) { - const value = data[parameter.name]; - if (parameter.type === EIP712Types.String || parameter.type === EIP712Types.Bytes) { - encodedValues.push(crypto.solSHA3([ethUtil.toBuffer(value)])); - } else if (parameter.type === EIP712Types.Uint256) { - encodedValues.push(value); - } else if (parameter.type === EIP712Types.Address) { - encodedValues.push(eip712Utils.pad32Address(value)); - } else { - throw new Error(`Unable to encode ${parameter.type}`); - } - } - return encodedValues; - }, -}; diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index e7a23682c..7194b9780 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -2,7 +2,6 @@ export { orderHashUtils } from './order_hash'; export { signatureUtils } from './signature_utils'; export { generatePseudoRandomSalt } from './salt'; export { assetDataUtils } from './asset_data_utils'; -export { eip712Utils } from './eip712_utils'; export { marketUtils } from './market_utils'; export { rateUtils } from './rate_utils'; export { sortingUtils } from './sorting_utils'; @@ -36,9 +35,6 @@ export { } from '@0xproject/types'; export { OrderError, - EIP712Parameter, - EIP712Schema, - EIP712Types, TradeSide, TransferType, FindFeeOrdersThatCoverFeesForTargetOrdersOpts, diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts index 151c1f801..37b9da811 100644 --- a/packages/order-utils/src/order_hash.ts +++ b/packages/order-utils/src/order_hash.ts @@ -1,28 +1,28 @@ import { schemas, SchemaValidator } from '@0xproject/json-schemas'; import { Order, SignedOrder } from '@0xproject/types'; +import { signTypedDataUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import { assert } from './assert'; -import { eip712Utils } from './eip712_utils'; -import { EIP712Schema, EIP712Types } from './types'; +import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants'; const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string'; -export const EIP712_ORDER_SCHEMA: EIP712Schema = { +export const EIP712_ORDER_SCHEMA = { name: 'Order', parameters: [ - { name: 'makerAddress', type: EIP712Types.Address }, - { name: 'takerAddress', type: EIP712Types.Address }, - { name: 'feeRecipientAddress', type: EIP712Types.Address }, - { name: 'senderAddress', type: EIP712Types.Address }, - { name: 'makerAssetAmount', type: EIP712Types.Uint256 }, - { name: 'takerAssetAmount', type: EIP712Types.Uint256 }, - { name: 'makerFee', type: EIP712Types.Uint256 }, - { name: 'takerFee', type: EIP712Types.Uint256 }, - { name: 'expirationTimeSeconds', type: EIP712Types.Uint256 }, - { name: 'salt', type: EIP712Types.Uint256 }, - { name: 'makerAssetData', type: EIP712Types.Bytes }, - { name: 'takerAssetData', type: EIP712Types.Bytes }, + { name: 'makerAddress', type: 'address' }, + { name: 'takerAddress', type: 'address' }, + { name: 'feeRecipientAddress', type: 'address' }, + { name: 'senderAddress', type: 'address' }, + { name: 'makerAssetAmount', type: 'uint256' }, + { name: 'takerAssetAmount', type: 'uint256' }, + { name: 'makerFee', type: 'uint256' }, + { name: 'takerFee', type: 'uint256' }, + { name: 'expirationTimeSeconds', type: 'uint256' }, + { name: 'salt', type: 'uint256' }, + { name: 'makerAssetData', type: 'bytes' }, + { name: 'takerAssetData', type: 'bytes' }, ], }; @@ -69,11 +69,23 @@ export const orderHashUtils = { * @return The resulting orderHash from hashing the supplied order as a Buffer */ getOrderHashBuffer(order: SignedOrder | Order): Buffer { - const orderParamsHashBuff = eip712Utils.structHash(EIP712_ORDER_SCHEMA, order); - const orderHashBuff = eip712Utils.createEIP712Message(orderParamsHashBuff, order.exchangeAddress); + const normalizedOrder = _.mapValues(order, value => { + return _.isObject(value) ? value.toString() : value; + }); + const typedData = { + types: { + EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters, + Order: EIP712_ORDER_SCHEMA.parameters, + }, + domain: { + name: EIP712_DOMAIN_NAME, + version: EIP712_DOMAIN_VERSION, + verifyingContract: order.exchangeAddress, + }, + message: normalizedOrder, + primaryType: EIP712_ORDER_SCHEMA.name, + }; + const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData); return orderHashBuff; }, - _getOrderSchemaBuffer(): Buffer { - return eip712Utils.compileSchema(EIP712_ORDER_SCHEMA); - }, }; diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 8e0fd702b..2d7fcfc9e 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -7,7 +7,7 @@ import * as _ from 'lodash'; import { artifacts } from './artifacts'; import { assert } from './assert'; -import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './eip712_utils'; +import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants'; import { ExchangeContract } from './generated_contract_wrappers/exchange'; import { IValidatorContract } from './generated_contract_wrappers/i_validator'; import { IWalletContract } from './generated_contract_wrappers/i_wallet'; diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index a843efaa4..80075270e 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -14,24 +14,6 @@ export enum TransferType { Fee = 'fee', } -export interface EIP712Parameter { - name: string; - type: EIP712Types; -} - -export interface EIP712Schema { - name: string; - parameters: EIP712Parameter[]; -} - -export enum EIP712Types { - Address = 'address', - Bytes = 'bytes', - Bytes32 = 'bytes32', - String = 'string', - Uint256 = 'uint256', -} - export interface CreateOrderOpts { takerAddress?: string; senderAddress?: string; -- cgit v1.2.3 From 3e2fe40a11919f09f1f454c71f02aaa147b46b0c Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 4 Oct 2018 17:32:54 +1000 Subject: Add eth_signTypedData support to our wallet subproviders --- packages/order-utils/CHANGELOG.json | 17 +++++++++++++++++ packages/order-utils/src/constants.ts | 2 +- packages/order-utils/src/index.ts | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 3e841c43c..a9d2fde8b 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,4 +1,21 @@ [ + { + "version": "2.0.0", + "changes": [ + { + "note": "Added ecSignOrderAsync to first sign an order as EIP712 and fallback to EthSign", + "pr": 1102 + }, + { + "note": "Added ecSignTypedDataOrderAsync to sign an order exclusively as EIP712", + "pr": 1102 + }, + { + "note": "Rename ecSignOrderHashAsync to ecSignHashAsync removing SignerType parameter", + "pr": 1102 + } + ] + }, { "version": "1.0.7", "changes": [ diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index cc03755c3..5403606c3 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -22,7 +22,7 @@ export const EIP712_DOMAIN_SCHEMA = { name: 'EIP712Domain', parameters: [ { name: 'name', type: 'string' }, - { name: 'version', type: 'string ' }, + { name: 'version', type: 'string' }, { name: 'verifyingContract', type: 'address' }, ], }; diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 7194b9780..89a843d8f 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -18,6 +18,8 @@ export { ExchangeTransferSimulator } from './exchange_transfer_simulator'; export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store'; export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store'; +export { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants'; + export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types'; export { SignedOrder, -- cgit v1.2.3 From 75d274f330dc0c18577e764ca77ffb36d5a3f27e Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 5 Oct 2018 11:45:53 +1000 Subject: Return SignedOrder from signing utils. Create a helper back in EIP712Utils for code cleanup. Moved constants in order-utils into the constants object --- packages/order-utils/CHANGELOG.json | 6 +- packages/order-utils/src/constants.ts | 47 ++++++++++---- packages/order-utils/src/eip712_utils.ts | 75 +++++++++++++++++++++++ packages/order-utils/src/index.ts | 9 ++- packages/order-utils/src/order_hash.ts | 43 ++----------- packages/order-utils/src/signature_utils.ts | 64 ++++++++++--------- packages/order-utils/test/eip712_utils_test.ts | 44 +++++++++++++ packages/order-utils/test/order_hash_test.ts | 14 +++++ packages/order-utils/test/signature_utils_test.ts | 51 ++++++++++++++- 9 files changed, 263 insertions(+), 90 deletions(-) create mode 100644 packages/order-utils/src/eip712_utils.ts create mode 100644 packages/order-utils/test/eip712_utils_test.ts (limited to 'packages/order-utils') diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index a9d2fde8b..2555ad350 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -3,15 +3,15 @@ "version": "2.0.0", "changes": [ { - "note": "Added ecSignOrderAsync to first sign an order as EIP712 and fallback to EthSign", + "note": "Added `ecSignOrderAsync` to first sign an order as EIP712 and fallback to EthSign", "pr": 1102 }, { - "note": "Added ecSignTypedDataOrderAsync to sign an order exclusively as EIP712", + "note": "Added `ecSignTypedDataOrderAsync` to sign an order exclusively as EIP712", "pr": 1102 }, { - "note": "Rename ecSignOrderHashAsync to ecSignHashAsync removing SignerType parameter", + "note": "Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter", "pr": 1102 } ] diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index 5403606c3..7de20a696 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -13,16 +13,39 @@ export const constants = { BASE_16: 16, INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite ZERO_AMOUNT: new BigNumber(0), -}; - -export const EIP712_DOMAIN_NAME = '0x Protocol'; -export const EIP712_DOMAIN_VERSION = '2'; - -export const EIP712_DOMAIN_SCHEMA = { - name: 'EIP712Domain', - parameters: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'verifyingContract', type: 'address' }, - ], + EIP712_DOMAIN_NAME: '0x Protocol', + EIP712_DOMAIN_VERSION: '2', + EIP712_DOMAIN_SCHEMA: { + name: 'EIP712Domain', + parameters: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'verifyingContract', type: 'address' }, + ], + }, + EIP712_ORDER_SCHEMA: { + name: 'Order', + parameters: [ + { name: 'makerAddress', type: 'address' }, + { name: 'takerAddress', type: 'address' }, + { name: 'feeRecipientAddress', type: 'address' }, + { name: 'senderAddress', type: 'address' }, + { name: 'makerAssetAmount', type: 'uint256' }, + { name: 'takerAssetAmount', type: 'uint256' }, + { name: 'makerFee', type: 'uint256' }, + { name: 'takerFee', type: 'uint256' }, + { name: 'expirationTimeSeconds', type: 'uint256' }, + { name: 'salt', type: 'uint256' }, + { name: 'makerAssetData', type: 'bytes' }, + { name: 'takerAssetData', type: 'bytes' }, + ], + }, + EIP712_ZEROEX_TRANSACTION_SCHEMA: { + name: 'ZeroExTransaction', + parameters: [ + { name: 'salt', type: 'uint256' }, + { name: 'signerAddress', type: 'address' }, + { name: 'data', type: 'bytes' }, + ], + }, }; diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts new file mode 100644 index 000000000..43b421de7 --- /dev/null +++ b/packages/order-utils/src/eip712_utils.ts @@ -0,0 +1,75 @@ +import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types'; +import * as _ from 'lodash'; + +import { constants } from './constants'; + +export const eip712Utils = { + /** + * Creates a EIP712TypedData object specific to the 0x protocol for use with signTypedData. + * @param primaryType The primary type found in message + * @param types The additional types for the data in message + * @param message The contents of the message + * @param exchangeAddress The address of the exchange contract + * @return A typed data object + */ + createTypedData: ( + primaryType: string, + types: EIP712Types, + message: EIP712Object, + exchangeAddress: string, + ): EIP712TypedData => { + const typedData = { + types: { + EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters, + ...types, + }, + domain: { + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + verifyingContract: exchangeAddress, + }, + message, + primaryType, + }; + return typedData; + }, + /** + * Creates an Order EIP712TypedData object for use with signTypedData. + * @param Order the order + * @return A typed data object + */ + createOrderTypedData: (order: Order): EIP712TypedData => { + const normalizedOrder = _.mapValues(order, value => { + return !_.isString(value) ? value.toString() : value; + }); + const typedData = eip712Utils.createTypedData( + constants.EIP712_ORDER_SCHEMA.name, + { Order: constants.EIP712_ORDER_SCHEMA.parameters }, + normalizedOrder, + order.exchangeAddress, + ); + return typedData; + }, + /** + * Creates an ExecuteTransaction EIP712TypedData object for use with signTypedData and + * 0x Exchange executeTransaction. + * @param ZeroExTransaction the 0x transaction + * @param exchangeAddress The address of the exchange contract + * @return A typed data object + */ + createZeroExTransactionTypedData: ( + zeroExTransaction: ZeroExTransaction, + exchangeAddress: string, + ): EIP712TypedData => { + const normalizedTransaction = _.mapValues(zeroExTransaction, value => { + return !_.isString(value) ? value.toString() : value; + }); + const typedData = eip712Utils.createTypedData( + constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name, + { ZeroExTransaction: constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters }, + normalizedTransaction, + exchangeAddress, + ); + return typedData; + }, +}; diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 89a843d8f..2e05fdf2b 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -18,7 +18,8 @@ export { ExchangeTransferSimulator } from './exchange_transfer_simulator'; export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store'; export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store'; -export { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants'; +export { constants } from './constants'; +export { eip712Utils } from './eip712_utils'; export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types'; export { @@ -34,6 +35,12 @@ export { OrderStateValid, OrderStateInvalid, ExchangeContractErrs, + EIP712Parameter, + EIP712TypedData, + EIP712Types, + EIP712Object, + EIP712ObjectValue, + ZeroExTransaction, } from '@0xproject/types'; export { OrderError, diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts index 37b9da811..a6dd6688c 100644 --- a/packages/order-utils/src/order_hash.ts +++ b/packages/order-utils/src/order_hash.ts @@ -4,28 +4,10 @@ import { signTypedDataUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import { assert } from './assert'; -import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants'; +import { eip712Utils } from './eip712_utils'; const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string'; -export const EIP712_ORDER_SCHEMA = { - name: 'Order', - parameters: [ - { name: 'makerAddress', type: 'address' }, - { name: 'takerAddress', type: 'address' }, - { name: 'feeRecipientAddress', type: 'address' }, - { name: 'senderAddress', type: 'address' }, - { name: 'makerAssetAmount', type: 'uint256' }, - { name: 'takerAssetAmount', type: 'uint256' }, - { name: 'makerFee', type: 'uint256' }, - { name: 'takerFee', type: 'uint256' }, - { name: 'expirationTimeSeconds', type: 'uint256' }, - { name: 'salt', type: 'uint256' }, - { name: 'makerAssetData', type: 'bytes' }, - { name: 'takerAssetData', type: 'bytes' }, - ], -}; - export const orderHashUtils = { /** * Checks if the supplied hex encoded order hash is valid. @@ -45,7 +27,7 @@ export const orderHashUtils = { /** * 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. + * @return Hex encoded string orderHash from hashing the supplied order. */ getOrderHashHex(order: SignedOrder | Order): string { try { @@ -64,27 +46,12 @@ export const orderHashUtils = { return orderHashHex; }, /** - * Computes the orderHash for a supplied order and returns it as a Buffer + * 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 as a Buffer + * @return A Buffer containing the resulting orderHash from hashing the supplied order */ getOrderHashBuffer(order: SignedOrder | Order): Buffer { - const normalizedOrder = _.mapValues(order, value => { - return _.isObject(value) ? value.toString() : value; - }); - const typedData = { - types: { - EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters, - Order: EIP712_ORDER_SCHEMA.parameters, - }, - domain: { - name: EIP712_DOMAIN_NAME, - version: EIP712_DOMAIN_VERSION, - verifyingContract: order.exchangeAddress, - }, - message: normalizedOrder, - primaryType: EIP712_ORDER_SCHEMA.name, - }; + const typedData = eip712Utils.createOrderTypedData(order); const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData); return orderHashBuff; }, diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 2d7fcfc9e..8c92b87dd 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, ValidatorSignature } from '@0xproject/types'; +import { ECSignature, Order, SignatureType, SignedOrder, ValidatorSignature } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; @@ -7,11 +7,11 @@ import * as _ from 'lodash'; import { artifacts } from './artifacts'; import { assert } from './assert'; -import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants'; +import { eip712Utils } from './eip712_utils'; 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, orderHashUtils } from './order_hash'; +import { orderHashUtils } from './order_hash'; import { OrderError } from './types'; import { utils } from './utils'; @@ -195,53 +195,45 @@ export const signatureUtils = { }, /** * 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. + * then a fallback to `eth_sign` if not available on the supplied 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. + * must be available via the supplied Provider. + * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. */ - async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { + async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { try { - const signatureHex = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); - return signatureHex; + const signedOrder = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); + return signedOrder; } 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; + const signedOrder = { + ...order, + signature: signatureHex, + }; + return signedOrder; } }, /** * 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. + * must be available via the supplied Provider. + * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. */ - async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { + 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); + // Detect if Metamask to transition users to the MetamaskSubprovider + if ((provider as any).isMetaMask) { + throw new Error('Unsupported Provider, please use MetamaskSubprovider.'); + } const normalizedSignerAddress = signerAddress.toLowerCase(); - const normalizedOrder = _.mapValues(order, value => { - return _.isObject(value) ? value.toString() : value; - }); - const typedData = { - types: { - EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters, - Order: EIP712_ORDER_SCHEMA.parameters, - }, - domain: { - name: EIP712_DOMAIN_NAME, - version: EIP712_DOMAIN_VERSION, - verifyingContract: order.exchangeAddress, - }, - message: normalizedOrder, - primaryType: 'Order', - }; + const typedData = eip712Utils.createOrderTypedData(order); const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); const ecSignatureRSV = parseSignatureHexAsRSV(signature); const signatureBuffer = Buffer.concat([ @@ -251,14 +243,16 @@ export const signatureUtils = { ethUtil.toBuffer(SignatureType.EIP712), ]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; - return signatureHex; + return { + ...order, + signature: signatureHex, + }; }, /** * 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 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. + * must be available via the supplied Provider. * @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type. */ async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise { @@ -268,6 +262,10 @@ export const signatureUtils = { const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); const normalizedSignerAddress = signerAddress.toLowerCase(); + // Detect if Metamask to transition users to the MetamaskSubprovider + if ((provider as any).isMetaMask) { + throw new Error('Unsupported Provider, please use MetamaskSubprovider.'); + } const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash); const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash); diff --git a/packages/order-utils/test/eip712_utils_test.ts b/packages/order-utils/test/eip712_utils_test.ts new file mode 100644 index 000000000..dc76595db --- /dev/null +++ b/packages/order-utils/test/eip712_utils_test.ts @@ -0,0 +1,44 @@ +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { constants } from '../src/constants'; +import { eip712Utils } from '../src/eip712_utils'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('EIP712 Utils', () => { + describe('createTypedData', () => { + it('adds in the EIP712DomainSeparator', () => { + const primaryType = 'Test'; + const typedData = eip712Utils.createTypedData( + primaryType, + { Test: [{ name: 'testValue', type: 'uint256' }] }, + { testValue: '1' }, + constants.NULL_ADDRESS, + ); + expect(typedData.domain).to.not.be.undefined(); + expect(typedData.types.EIP712Domain).to.not.be.undefined(); + const domainObject = typedData.domain; + expect(domainObject.name).to.eq(constants.EIP712_DOMAIN_NAME); + expect(typedData.primaryType).to.eq(primaryType); + }); + }); + describe('createTypedData', () => { + it('adds in the EIP712DomainSeparator', () => { + const typedData = eip712Utils.createZeroExTransactionTypedData( + { + salt: new BigNumber('0'), + data: constants.NULL_BYTES, + signerAddress: constants.NULL_BYTES, + }, + constants.NULL_ADDRESS, + ); + expect(typedData.primaryType).to.eq(constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name); + expect(typedData.types.EIP712Domain).to.not.be.undefined(); + }); + }); +}); diff --git a/packages/order-utils/test/order_hash_test.ts b/packages/order-utils/test/order_hash_test.ts index 3fdbbad21..fe44218d6 100644 --- a/packages/order-utils/test/order_hash_test.ts +++ b/packages/order-utils/test/order_hash_test.ts @@ -35,6 +35,20 @@ describe('Order hashing', () => { const orderHash = orderHashUtils.getOrderHashHex(order); expect(orderHash).to.be.equal(expectedOrderHash); }); + it('calculates the order hash if amounts are strings', async () => { + // It's common for developers using javascript to provide the amounts + // as strings. Since we eventually toString() the BigNumber + // before encoding we should result in the same orderHash in this scenario + // tslint:disable-next-line:no-unnecessary-type-assertion + const orderHash = orderHashUtils.getOrderHashHex({ + ...order, + makerAssetAmount: '0', + takerAssetAmount: '0', + makerFee: '0', + takerFee: '0', + } as any); + expect(orderHash).to.be.equal(expectedOrderHash); + }); it('throws a readable error message if taker format is invalid', async () => { const orderWithInvalidtakerFormat = { ...order, diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 03354cd65..7f6987f6a 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -152,14 +152,14 @@ describe('Signature utils', () => { ethUtil.toBuffer(SignatureType.EIP712), ]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; - const eip712Signature = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress); + const signedOrder = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress); const isValidSignature = await signatureUtils.isValidSignatureAsync( provider, orderHashHex, - eip712Signature, + signedOrder.signature, makerAddress, ); - expect(signatureHex).to.eq(eip712Signature); + expect(signatureHex).to.eq(signedOrder.signature); expect(isValidSignature).to.eq(true); }); }); @@ -238,6 +238,51 @@ describe('Signature utils', () => { expect(isValidSignature).to.be.true(); }); }); + describe('#ecSignTypedDataOrderAsync', () => { + let makerAddress: string; + const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; + let order: Order; + before(async () => { + const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); + makerAddress = availableAddreses[0]; + order = { + makerAddress, + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + makerAssetData: constants.NULL_ADDRESS, + takerAssetData: constants.NULL_ADDRESS, + exchangeAddress: fakeExchangeContractAddress, + salt: new BigNumber(0), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerAssetAmount: new BigNumber(0), + takerAssetAmount: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + }; + }); + it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { + const expectedSignature = + '0x1cd472c439833774b55d248c31b6585f21aea1b9363ebb4ec58549e46b62eb5a6f696f5781f62de008ee7f77650ef940d99c97ec1dee67b3f5cea1bbfdfeb2eba602'; + const fakeProvider = { + async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise { + if (payload.method === 'eth_signTypedData') { + const [address, typedData] = payload.params; + const signature = await web3Wrapper.signTypedDataAsync(address, typedData); + callback(null, { + id: 42, + jsonrpc: '2.0', + result: signature, + }); + } else { + callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); + } + }, + }; + const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(fakeProvider, order, makerAddress); + expect(signedOrder.signature).to.equal(expectedSignature); + }); + }); describe('#convertECSignatureToSignatureHex', () => { const ecSignature: ECSignature = { v: 27, -- cgit v1.2.3 From e1236a484623e9d2caab823c476175cb255ae816 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 8 Oct 2018 11:13:04 +1100 Subject: Detect MM on signature validation failure. Report a developer friendly error in this event to educate them on the compatability wrapper MetamaskSubprovider --- packages/order-utils/src/signature_utils.ts | 52 ++++++++++++++++------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 8c92b87dd..e518b29a0 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -228,25 +228,30 @@ export const signatureUtils = { assert.isETHAddressHex('signerAddress', signerAddress); const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); - // Detect if Metamask to transition users to the MetamaskSubprovider - if ((provider as any).isMetaMask) { - throw new Error('Unsupported Provider, please use MetamaskSubprovider.'); - } const normalizedSignerAddress = signerAddress.toLowerCase(); const typedData = eip712Utils.createOrderTypedData(order); - const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); - const ecSignatureRSV = parseSignatureHexAsRSV(signature); - const signatureBuffer = Buffer.concat([ - ethUtil.toBuffer(ecSignatureRSV.v), - ethUtil.toBuffer(ecSignatureRSV.r), - ethUtil.toBuffer(ecSignatureRSV.s), - ethUtil.toBuffer(SignatureType.EIP712), - ]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - return { - ...order, - signature: signatureHex, - }; + try { + const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); + const ecSignatureRSV = parseSignatureHexAsRSV(signature); + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(ecSignatureRSV.v), + ethUtil.toBuffer(ecSignatureRSV.r), + ethUtil.toBuffer(ecSignatureRSV.s), + ethUtil.toBuffer(SignatureType.EIP712), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + return { + ...order, + signature: signatureHex, + }; + } catch (err) { + // Detect if Metamask to transition users to the MetamaskSubprovider + if ((provider as any).isMetaMask) { + throw new Error(`Unsupported Provider, please use MetamaskSubprovider: ${err.message}`); + } else { + throw err; + } + } }, /** * Signs a hash and returns its elliptic curve signature and signature type. @@ -262,11 +267,6 @@ export const signatureUtils = { const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); const normalizedSignerAddress = signerAddress.toLowerCase(); - // Detect if Metamask to transition users to the MetamaskSubprovider - if ((provider as any).isMetaMask) { - throw new Error('Unsupported Provider, please use MetamaskSubprovider.'); - } - const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash); const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash); @@ -301,8 +301,12 @@ export const signatureUtils = { return convertedSignatureHex; } } - - throw new Error(OrderError.InvalidSignature); + // Detect if Metamask to transition users to the MetamaskSubprovider + if ((provider as any).isMetaMask) { + throw new Error('Unsupported Provider, please use MetamaskSubprovider.'); + } else { + throw new Error(OrderError.InvalidSignature); + } }, /** * Combines ECSignature with V,R,S and the EthSign signature type for use in 0x protocol -- cgit v1.2.3 From 9e8031d5e3cf94cabe07685be510397367e90413 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 9 Oct 2018 18:26:13 +1100 Subject: Throw and handle errors from Providers. In web3 wrapper when a response contains an error field we throw this rather than return response.result which is often undefined. In Signature Utils we handle the error thrown when a user rejects the signing dialogue to prevent double signing. Exposed the ZeroExTransaction JSON schema. In Website only use the MetamaskSubprovider if we can detect the provider is Metamask --- packages/order-utils/src/eip712_utils.ts | 8 ++ packages/order-utils/src/order_hash.ts | 2 +- packages/order-utils/src/signature_utils.ts | 26 +++- packages/order-utils/test/eip712_utils_test.ts | 2 +- packages/order-utils/test/signature_utils_test.ts | 149 +++++++++++++--------- 5 files changed, 116 insertions(+), 71 deletions(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts index 43b421de7..56f736500 100644 --- a/packages/order-utils/src/eip712_utils.ts +++ b/packages/order-utils/src/eip712_utils.ts @@ -1,3 +1,5 @@ +import { assert } from '@0xproject/assert'; +import { schemas } from '@0xproject/json-schemas'; import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types'; import * as _ from 'lodash'; @@ -18,6 +20,8 @@ export const eip712Utils = { message: EIP712Object, exchangeAddress: string, ): EIP712TypedData => { + assert.isETHAddressHex('exchangeAddress', exchangeAddress); + assert.isString('primaryType', primaryType); const typedData = { types: { EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters, @@ -31,6 +35,7 @@ export const eip712Utils = { message, primaryType, }; + assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema); return typedData; }, /** @@ -39,6 +44,7 @@ export const eip712Utils = { * @return A typed data object */ createOrderTypedData: (order: Order): EIP712TypedData => { + assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); const normalizedOrder = _.mapValues(order, value => { return !_.isString(value) ? value.toString() : value; }); @@ -61,6 +67,8 @@ export const eip712Utils = { zeroExTransaction: ZeroExTransaction, exchangeAddress: string, ): EIP712TypedData => { + assert.isETHAddressHex('exchangeAddress', exchangeAddress); + assert.doesConformToSchema('zeroExTransaction', zeroExTransaction, schemas.zeroExTransactionSchema); const normalizedTransaction = _.mapValues(zeroExTransaction, value => { return !_.isString(value) ? value.toString() : value; }); diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts index a6dd6688c..b523a3523 100644 --- a/packages/order-utils/src/order_hash.ts +++ b/packages/order-utils/src/order_hash.ts @@ -52,7 +52,7 @@ export const orderHashUtils = { */ getOrderHashBuffer(order: SignedOrder | Order): Buffer { const typedData = eip712Utils.createOrderTypedData(order); - const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData); + const orderHashBuff = signTypedDataUtils.generateTypedDataHash(typedData); return orderHashBuff; }, }; diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index e518b29a0..2605ccd32 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -194,7 +194,7 @@ export const signatureUtils = { } }, /** - * Signs an order and returns its elliptic curve signature and signature type. First `eth_signTypedData` is requested + * Signs an order and returns a SignedOrder. First `eth_signTypedData` is requested * then a fallback to `eth_sign` if not available on the supplied provider. * @param order The Order to sign. * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address @@ -202,11 +202,18 @@ export const signatureUtils = { * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. */ async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { + assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); try { - const signedOrder = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); + const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); return signedOrder; } catch (err) { - // Fallback to using EthSign when ethSignTypedData is not supported + // 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 + if (err.message.includes('User denied message signature')) { + throw err; + } const orderHash = orderHashUtils.getOrderHashHex(order); const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress); const signedOrder = { @@ -217,7 +224,7 @@ export const signatureUtils = { } }, /** - * Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type. + * Signs an order using `eth_signTypedData` and returns a SignedOrder. * @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 supplied Provider. @@ -226,6 +233,7 @@ export const signatureUtils = { async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise { assert.isWeb3Provider('provider', provider); assert.isETHAddressHex('signerAddress', signerAddress); + assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); const web3Wrapper = new Web3Wrapper(provider); await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); const normalizedSignerAddress = signerAddress.toLowerCase(); @@ -247,14 +255,16 @@ export const signatureUtils = { } catch (err) { // Detect if Metamask to transition users to the MetamaskSubprovider if ((provider as any).isMetaMask) { - throw new Error(`Unsupported Provider, please use MetamaskSubprovider: ${err.message}`); + throw new Error( + `MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`, + ); } else { throw err; } } }, /** - * Signs a hash 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 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 supplied Provider. @@ -303,7 +313,9 @@ export const signatureUtils = { } // Detect if Metamask to transition users to the MetamaskSubprovider if ((provider as any).isMetaMask) { - throw new Error('Unsupported Provider, please use MetamaskSubprovider.'); + throw new Error( + `MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`, + ); } else { throw new Error(OrderError.InvalidSignature); } diff --git a/packages/order-utils/test/eip712_utils_test.ts b/packages/order-utils/test/eip712_utils_test.ts index dc76595db..d65cabe9c 100644 --- a/packages/order-utils/test/eip712_utils_test.ts +++ b/packages/order-utils/test/eip712_utils_test.ts @@ -33,7 +33,7 @@ describe('EIP712 Utils', () => { { salt: new BigNumber('0'), data: constants.NULL_BYTES, - signerAddress: constants.NULL_BYTES, + signerAddress: constants.NULL_ADDRESS, }, constants.NULL_ADDRESS, ); diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 7f6987f6a..f2d6790fb 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -17,6 +17,28 @@ chaiSetup.configure(); const expect = chai.expect; describe('Signature utils', () => { + let makerAddress: string; + const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; + let order: Order; + before(async () => { + const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); + makerAddress = availableAddreses[0]; + order = { + makerAddress, + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + makerAssetData: constants.NULL_ADDRESS, + takerAssetData: constants.NULL_ADDRESS, + exchangeAddress: fakeExchangeContractAddress, + salt: new BigNumber(0), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerAssetAmount: new BigNumber(0), + takerAssetAmount: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + }; + }); describe('#isValidSignatureAsync', () => { let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; const ethSignSignature = @@ -117,54 +139,54 @@ describe('Signature utils', () => { }); }); describe('#ecSignOrderAsync', () => { - let makerAddress: string; - const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; - let order: Order; - before(async () => { - const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); - makerAddress = availableAddreses[0]; - order = { - makerAddress, - takerAddress: constants.NULL_ADDRESS, - senderAddress: constants.NULL_ADDRESS, - feeRecipientAddress: constants.NULL_ADDRESS, - makerAssetData: constants.NULL_ADDRESS, - takerAssetData: constants.NULL_ADDRESS, - exchangeAddress: fakeExchangeContractAddress, - salt: new BigNumber(0), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - makerAssetAmount: new BigNumber(0), - takerAssetAmount: new BigNumber(0), - expirationTimeSeconds: new BigNumber(0), + it('should default to eth_sign if eth_signTypedData is unavailable', async () => { + const expectedSignature = + '0x1c3582f06356a1314dbf1c0e534c4d8e92e59b056ee607a7ff5a825f5f2cc5e6151c5cc7fdd420f5608e4d5bef108e42ad90c7a4b408caef32e24374cf387b0d7603'; + + const fakeProvider = { + async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise { + if (payload.method === 'eth_signTypedData') { + callback(new Error('Internal RPC Error')); + } else if (payload.method === 'eth_sign') { + const [address, message] = payload.params; + const signature = await web3Wrapper.signMessageAsync(address, message); + callback(null, { + id: 42, + jsonrpc: '2.0', + result: signature, + }); + } else { + callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); + } + }, }; + const signedOrder = await signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress); + expect(signedOrder.signature).to.equal(expectedSignature); }); - it('should result in the same signature as signing order hash without prefix', async () => { - const orderHashHex = orderHashUtils.getOrderHashHex(order); - const sig = ethUtil.ecsign( - ethUtil.toBuffer(orderHashHex), - Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'), - ); - const signatureBuffer = Buffer.concat([ - ethUtil.toBuffer(sig.v), - ethUtil.toBuffer(sig.r), - ethUtil.toBuffer(sig.s), - ethUtil.toBuffer(SignatureType.EIP712), - ]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - const signedOrder = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress); - const isValidSignature = await signatureUtils.isValidSignatureAsync( - provider, - orderHashHex, - signedOrder.signature, - makerAddress, + it('should throw if the user denies the signing request', async () => { + const fakeProvider = { + async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise { + if (payload.method === 'eth_signTypedData') { + callback(new Error('User denied message signature')); + } else if (payload.method === 'eth_sign') { + const [address, message] = payload.params; + const signature = await web3Wrapper.signMessageAsync(address, message); + callback(null, { + id: 42, + jsonrpc: '2.0', + result: signature, + }); + } else { + callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); + } + }, + }; + expect(signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress)).to.to.be.rejectedWith( + 'User denied message signature', ); - expect(signatureHex).to.eq(signedOrder.signature); - expect(isValidSignature).to.eq(true); }); }); describe('#ecSignHashAsync', () => { - let makerAddress: string; before(async () => { const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); makerAddress = availableAddreses[0]; @@ -239,27 +261,30 @@ describe('Signature utils', () => { }); }); describe('#ecSignTypedDataOrderAsync', () => { - let makerAddress: string; - const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; - let order: Order; - before(async () => { - const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); - makerAddress = availableAddreses[0]; - order = { + it('should result in the same signature as signing the order hash without an ethereum message prefix', async () => { + // Note: Since order hash is an EIP712 hash the result of a valid EIP712 signature + // of order hash is the same as signing the order without the Ethereum Message prefix. + const orderHashHex = orderHashUtils.getOrderHashHex(order); + const sig = ethUtil.ecsign( + ethUtil.toBuffer(orderHashHex), + Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'), + ); + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(sig.v), + ethUtil.toBuffer(sig.r), + ethUtil.toBuffer(sig.s), + ethUtil.toBuffer(SignatureType.EIP712), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, makerAddress); + const isValidSignature = await signatureUtils.isValidSignatureAsync( + provider, + orderHashHex, + signedOrder.signature, makerAddress, - takerAddress: constants.NULL_ADDRESS, - senderAddress: constants.NULL_ADDRESS, - feeRecipientAddress: constants.NULL_ADDRESS, - makerAssetData: constants.NULL_ADDRESS, - takerAssetData: constants.NULL_ADDRESS, - exchangeAddress: fakeExchangeContractAddress, - salt: new BigNumber(0), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - makerAssetAmount: new BigNumber(0), - takerAssetAmount: new BigNumber(0), - expirationTimeSeconds: new BigNumber(0), - }; + ); + expect(signatureHex).to.eq(signedOrder.signature); + expect(isValidSignature).to.eq(true); }); it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { const expectedSignature = -- cgit v1.2.3 From 7f554303b4333b083102eb17cd9acb6f6b73cc75 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 9 Oct 2018 20:29:41 +1100 Subject: Update the exported types for the packages which touch RPC providers --- packages/order-utils/src/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 2e05fdf2b..dbb782b85 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -21,7 +21,14 @@ export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_la export { constants } from './constants'; export { eip712Utils } from './eip712_utils'; -export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types'; +export { + Provider, + JSONRPCRequestPayload, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + export { SignedOrder, Order, -- cgit v1.2.3 From 75b9e639194e98febf8e378619afef2d578cbc7e Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 9 Oct 2018 20:58:30 +1100 Subject: Move Metamask Error to OrderErrors --- packages/order-utils/CHANGELOG.json | 7 ++++--- packages/order-utils/src/signature_utils.ts | 11 ++++------- packages/order-utils/src/types.ts | 1 + 3 files changed, 9 insertions(+), 10 deletions(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 2555ad350..5a0c0db47 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -3,15 +3,16 @@ "version": "2.0.0", "changes": [ { - "note": "Added `ecSignOrderAsync` to first sign an order as EIP712 and fallback to EthSign", + "note": + "Added `ecSignOrderAsync` to first sign an order using `eth_signTypedData` and fallback to `eth_sign`.", "pr": 1102 }, { - "note": "Added `ecSignTypedDataOrderAsync` to sign an order exclusively as EIP712", + "note": "Added `ecSignTypedDataOrderAsync` to sign an order exclusively using `eth_signTypedData`.", "pr": 1102 }, { - "note": "Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter", + "note": "Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter.", "pr": 1102 } ] diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 2605ccd32..372d210d0 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -210,7 +210,8 @@ export const signatureUtils = { // 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 + // 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; } @@ -255,9 +256,7 @@ export const signatureUtils = { } catch (err) { // Detect if Metamask to transition users to the MetamaskSubprovider if ((provider as any).isMetaMask) { - throw new Error( - `MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`, - ); + throw new Error(OrderError.InvalidMetamaskSigner); } else { throw err; } @@ -313,9 +312,7 @@ export const signatureUtils = { } // Detect if Metamask to transition users to the MetamaskSubprovider if ((provider as any).isMetaMask) { - throw new Error( - `MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`, - ); + throw new Error(OrderError.InvalidMetamaskSigner); } else { throw new Error(OrderError.InvalidSignature); } diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index 80075270e..5b13dd754 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; export enum OrderError { InvalidSignature = 'INVALID_SIGNATURE', + InvalidMetamaskSigner = "MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.", } export enum TradeSide { -- cgit v1.2.3 From 31ee8870b80579caf3762c1178043bb5bb6fe4fb Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 9 Oct 2018 12:15:42 +0200 Subject: Upgrade ethers.js version --- packages/order-utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index 23ed9ca12..6471de9f5 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -70,7 +70,7 @@ "ethereum-types": "^1.0.11", "ethereumjs-abi": "0.6.5", "ethereumjs-util": "^5.1.1", - "ethers": "4.0.0-beta.14", + "ethers": "~4.0.4", "lodash": "^4.17.5" }, "publishConfig": { -- cgit v1.2.3