Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

[0x.js][order-utils][web3-wrapper] Expose eth_signTypedData functionality for order signing #1102

Merged
merged 10 commits into from
Oct 9, 2018

Conversation

dekz
Copy link
Member

@dekz dekz commented Oct 1, 2018

Description

screen shot 2018-10-03 at 3 27 12 pm

TODO

  • rename ecSignOrderHashAsync to ecSignHashAsync for use in executeTransaction etc
  • Expose a higher level function where the user can switch between eth_signTypedData and eth_sign if signTypedData is unsupported.
  • Mnemonic wallet and PrivateKey wallet support eth_signTypedData
  • check in with MM regarding eth_sign and their prefix. This was a TODO for them dependent on merging EIP712 support.
  • Detect MM if signing fails and throw a more helpful error message (i.e you should use MMSubprovider) for a graceful transition.

Note: MM now has another inconsistency with eth_signTypedData. It has published 4.12 and named the correct spec compliant implementation eth_signTypedData_v3. It also requires the typedData parameter to be JSON encoded (inconsistent with Ganache).

I propose we quarantine all the MM inconsistency in a Subprovider called MetamaskSubprovider. This allows us to handle the inconsistent eth_sign behaviour (no prefix is added) as well as handle the eth_signTypedData inconsistency. Removing this leaky abstraction from the rest of our code as much as possible (SignerType removed).

This subprovider can be used standalone or as a web3ProviderEngine subprovider.

// standalone 
const signature = await signatureUtils.ecSignOrderAsync(new MetamaskSubprovider(web3.currentProvider), order, makerAddress);

// web3 provider engine
const providerEngine = new Web3ProviderEngine();
const metamaskSubprovider = new MetamaskSubprovider(web3.currentProvider);
providerEngine.addProvider(metamaskSubprovider);
providerEngine.addProvider(new RPCSubprovider(KOVAN_RPC));
const signature = await signatureUtils.ecSignOrderAsync(providerEngine, order, makerAddress);

After merge and publish

  • update sandbox to use the latest versions of 0x.js
  • Portal to be updated with EIP712 order signing

Testing instructions

Ganache and MM share the same implementation of eth_signTypedData. We test against this implementation inside of ganache. The output of eth_signTypedData is the same as a signed message of our orderHash without the prefix (since our orderHash is EIP712).

  • Ganache (for automated tests)
  • Metamask (codesandbox and website)
  • Metamask + Kovan

Types of changes

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Prefix PR title with bracketed package name(s) corresponding to the changed package(s). For example: [sol-cov] Fixed bug.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@dekz dekz added the WIP label Oct 1, 2018
@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch 8 times, most recently from 9336467 to 20ca573 Compare October 2, 2018 10:01
@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch from 20ca573 to da49112 Compare October 2, 2018 10:29
@coveralls
Copy link

coveralls commented Oct 2, 2018

Coverage Status

Coverage decreased (-0.4%) to 84.87% when pulling 8620cbc213556b3961087f1eedf3ba7283494081 on feature/0x.js/eip712-sign-typed-data into 119f8c9 on development.

@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch 11 times, most recently from b867dce to 15a17fd Compare October 4, 2018 09:17
@dekz dekz changed the title [WIP] [order-utils] [web3-wrapper] Expose eth_signTypedData functionality for order signing [0x.js][order-utils][web3-wrapper] Expose eth_signTypedData functionality for order signing Oct 4, 2018
@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch 17 times, most recently from 7115285 to 050cc17 Compare October 5, 2018 06:58
Create a helper back in EIP712Utils for code cleanup.
Moved constants in order-utils into the constants object
@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch from 050cc17 to 75d274f Compare October 5, 2018 07:12
Report a developer friendly error in this event to educate them on the compatability wrapper MetamaskSubprovider
pad32Buffer(buffer: Buffer): Buffer {
const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH);
return bufferPadded;
createOrderTypedData: (order: Order): EIP712TypedData => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, slight preference for createTypedDataOrder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a slight preference for important information first when reading the code.

createTypedDataOrder
createTypedDataZeroExTransaction

vs

createOrderTypedData
createZeroExTransactionTypedData

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, add method devdoc comment for this method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already added

packages/order-utils/src/order_hash.ts Outdated Show resolved Hide resolved
"pr": 1102
},
{
"note": "Added `MetamaskSubprovider` to handle inconsistencies with signing.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inconsistencies in Metamask's signing JSON RPC endpoints

const eip721MessageBuffer = eip712Utils.createEIP712Message(executeTransactionHashBuff, exchangeAddress);
const messageHex = `0x${eip721MessageBuffer.toString('hex')}`;
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress);
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename signTypedDataHash to generateTypedDataHash. There is no signing going on and the current method name suggests as much.

pad32Buffer(buffer: Buffer): Buffer {
const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH);
return bufferPadded;
createOrderTypedData: (order: Order): EIP712TypedData => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, add method devdoc comment for this method.

const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH);
return bufferPadded;
createOrderTypedData: (order: Order): EIP712TypedData => {
const normalizedOrder = _.mapValues(order, value => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's first assert that the Order passed in conforms to the Order schema.

message: EIP712Object,
exchangeAddress: string,
): EIP712TypedData => {
const typedData = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add assertions for the params. This is a public, exported method.

}
},
/**
* Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not true any more: returns its elliptic curve signature and signature type

} 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}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be more explicit: MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.

const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
},
/**
* Signs a hash and returns its elliptic curve signature and signature type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mention eth_sign here to mirror the other method description.

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.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy above error message.

@@ -161,7 +161,7 @@ export class Blockchain {
// We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node.
const provider = new Web3ProviderEngine();
provider.addProvider(new SignerSubprovider(injectedWeb3.currentProvider));
provider.addProvider(new MetamaskSubprovider(injectedWeb3.currentProvider));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, we only want to do this IF provider.isMetamask is true. Let's add that check. Website should also work with other injected signers.

@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch from 8620cbc to c979949 Compare October 9, 2018 07:36
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
@dekz dekz force-pushed the feature/0x.js/eip712-sign-typed-data branch from c979949 to 9e8031d Compare October 9, 2018 08:01
"version": "2.0.0",
"changes": [
{
"note": "Added `ecSignOrderAsync` to first sign an order as EIP712 and fallback to EthSign",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's replace as EIP712 and fallback to EthSign with via 'eth_signTypedData' and fallback to 'eth_sign'

// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add: . Unfortunately for signers with a different error message, they will receive two signature requests.

// 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.`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this to OrderError enum.

@dekz dekz merged commit 024bcf4 into development Oct 9, 2018
@dekz dekz deleted the feature/0x.js/eip712-sign-typed-data branch October 9, 2018 10:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants