aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils/src/signature_utils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/order-utils/src/signature_utils.ts')
-rw-r--r--packages/order-utils/src/signature_utils.ts163
1 files changed, 97 insertions, 66 deletions
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts
index 3b656d3fc..372d210d0 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, SignedOrder, 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 { 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 { orderHashUtils } from './order_hash';
import { OrderError } from './types';
import { utils } from './utils';
@@ -49,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);
}
@@ -192,36 +194,90 @@ export const signatureUtils = {
}
},
/**
- * 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
- * @param orderHash Hex encoded orderHash to sign.
+ * 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
- * 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.
+ * must be available via the supplied Provider.
+ * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
*/
- async ecSignOrderHashAsync(
- provider: Provider,
- orderHash: string,
- signerAddress: string,
- signerType: SignerType,
- ): Promise<string> {
+ async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
+ try {
+ const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
+ return signedOrder;
+ } catch (err) {
+ // HACK: We are unable to handle specific errors thrown since provider is not an object
+ // under our control. It could be Metamask Web3, Ethers, or any general RPC provider.
+ // We check for a user denying the signature request in a way that supports Metamask and
+ // Coinbase Wallet. Unfortunately for signers with a different error message,
+ // they will receive two signature requests.
+ if (err.message.includes('User denied message signature')) {
+ throw err;
+ }
+ const orderHash = orderHashUtils.getOrderHashHex(order);
+ const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
+ const signedOrder = {
+ ...order,
+ signature: signatureHex,
+ };
+ return signedOrder;
+ }
+ },
+ /**
+ * 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.
+ * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
+ */
+ async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
assert.isWeb3Provider('provider', provider);
- assert.isHexString('orderHash', orderHash);
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();
-
- 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 typedData = eip712Utils.createOrderTypedData(order);
+ 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(OrderError.InvalidMetamaskSigner);
+ } else {
+ throw err;
+ }
}
- const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
+ },
+ /**
+ * 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.
+ * @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<string> {
+ assert.isWeb3Provider('provider', provider);
+ assert.isHexString('msgHash', msgHash);
+ assert.isETHAddressHex('signerAddress', signerAddress);
+ const web3Wrapper = new Web3Wrapper(provider);
+ await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
+ const normalizedSignerAddress = signerAddress.toLowerCase();
+ 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)
@@ -238,10 +294,7 @@ export const signatureUtils = {
normalizedSignerAddress,
);
if (isValidRSVSignature) {
- const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(
- ecSignatureRSV,
- signerType,
- );
+ const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureRSV);
return convertedSignatureHex;
}
}
@@ -253,41 +306,30 @@ export const signatureUtils = {
normalizedSignerAddress,
);
if (isValidVRSSignature) {
- const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(
- ecSignatureVRS,
- signerType,
- );
+ const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureVRS);
return convertedSignatureHex;
}
}
-
- throw new Error(OrderError.InvalidSignature);
+ // Detect if Metamask to transition users to the MetamaskSubprovider
+ if ((provider as any).isMetaMask) {
+ throw new Error(OrderError.InvalidMetamaskSigner);
+ } else {
+ 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;
},
/**
@@ -304,28 +346,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
*/