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.ts229
1 files changed, 208 insertions, 21 deletions
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts
index ebd636b20..dadf5052c 100644
--- a/packages/order-utils/src/signature_utils.ts
+++ b/packages/order-utils/src/signature_utils.ts
@@ -1,28 +1,166 @@
import { schemas } from '@0xproject/json-schemas';
-import { ECSignature, Provider } from '@0xproject/types';
+import { ECSignature, Provider, SignatureType, ValidatorSignature } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
+import { artifacts } from './artifacts';
import { assert } from './assert';
-import { OrderError } from './types';
+import { ExchangeContract } from './generated_contract_wrappers/exchange';
+import { IValidatorContract } from './generated_contract_wrappers/i_validator';
+import { IWalletContract } from './generated_contract_wrappers/i_wallet';
+import { MessagePrefixOpts, MessagePrefixType, OrderError } from './types';
+import { utils } from './utils';
/**
- * Verifies that the elliptic curve signature `signature` was generated
- * by signing `data` with the private key corresponding to the `signerAddress` address.
+ * Verifies that the provided signature is valid according to the 0x Protocol smart contracts
* @param data The hex encoded data signed by the supplied signature.
- * @param signature An object containing the elliptic curve signature parameters.
+ * @param signature A hex encoded 0x Protocol signature made up of: [SignatureType][TypeSpecificData].
+ * E.g [SignatureType.EIP712][vrs]
* @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
* @return Whether the signature is valid for the supplied signerAddress and data.
*/
-export function isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
+export async function isValidSignatureAsync(
+ provider: Provider,
+ data: string,
+ signature: string,
+ signerAddress: string,
+): Promise<boolean> {
+ const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature);
+ if (_.isUndefined(signatureTypeIndexIfExists)) {
+ throw new Error(`Unrecognized signatureType in signature: ${signature}`);
+ }
+
+ switch (signatureTypeIndexIfExists) {
+ case SignatureType.Illegal:
+ case SignatureType.Invalid:
+ return false;
+
+ case SignatureType.EIP712: {
+ const ecSignature = parseECSignature(signature);
+ return isValidECSignature(data, ecSignature, signerAddress);
+ }
+
+ case SignatureType.EthSign: {
+ const ecSignature = parseECSignature(signature);
+ const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.EthSign);
+ return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress);
+ }
+
+ case SignatureType.Caller:
+ // HACK: We currently do not "validate" the caller signature type.
+ // It can only be validated during Exchange contract execution.
+ throw new Error('Caller signature type cannot be validated off-chain');
+
+ case SignatureType.Wallet: {
+ const isValid = await isValidWalletSignatureAsync(provider, data, signature, signerAddress);
+ return isValid;
+ }
+
+ case SignatureType.Validator: {
+ const isValid = await isValidValidatorSignatureAsync(provider, data, signature, signerAddress);
+ return isValid;
+ }
+
+ case SignatureType.PreSigned: {
+ return isValidPresignedSignatureAsync(provider, data, signerAddress);
+ }
+
+ case SignatureType.Trezor: {
+ const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.Trezor);
+ const ecSignature = parseECSignature(signature);
+ return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress);
+ }
+
+ default:
+ throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`);
+ }
+}
+
+/**
+ * Verifies that the provided presigned signature is valid according to the 0x Protocol smart contracts
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned]
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the data was preSigned by the supplied signerAddress.
+ */
+export async function isValidPresignedSignatureAsync(
+ provider: Provider,
+ data: string,
+ signerAddress: string,
+): Promise<boolean> {
+ const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider);
+ const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress);
+ return isValid;
+}
+
+/**
+ * Verifies that the provided wallet signature is valid according to the 0x Protocol smart contracts
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned]
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the data was preSigned by the supplied signerAddress.
+ */
+export async function isValidWalletSignatureAsync(
+ provider: Provider,
+ data: string,
+ signature: string,
+ signerAddress: string,
+): Promise<boolean> {
+ // tslint:disable-next-line:custom-no-magic-numbers
+ const signatureWithoutType = signature.slice(-2);
+ const walletContract = new IWalletContract(artifacts.IWallet.abi, signerAddress, provider);
+ const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType);
+ return isValid;
+}
+
+/**
+ * Verifies that the provided validator signature is valid according to the 0x Protocol smart contracts
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned]
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the data was preSigned by the supplied signerAddress.
+ */
+export async function isValidValidatorSignatureAsync(
+ provider: Provider,
+ data: string,
+ signature: string,
+ signerAddress: string,
+): Promise<boolean> {
+ const validatorSignature = parseValidatorSignature(signature);
+ const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider);
+ const isValidatorApproved = await exchangeContract.allowedValidators.callAsync(
+ signerAddress,
+ validatorSignature.validatorAddress,
+ );
+ if (!isValidatorApproved) {
+ throw new Error(`Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`);
+ }
+
+ const validatorContract = new IValidatorContract(artifacts.IValidator.abi, signerAddress, provider);
+ const isValid = await validatorContract.isValidSignature.callAsync(
+ data,
+ signerAddress,
+ validatorSignature.signature,
+ );
+ return isValid;
+}
+
+/**
+ * Checks if the supplied elliptic curve signature corresponds to signing `data` with
+ * the private key corresponding to `signerAddress`
+ * @param data The hex encoded data signed by the supplied signature.
+ * @param signature An object containing the elliptic curve signature parameters.
+ * @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
+ * @return Whether the ECSignature is valid.
+ */
+export function isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
assert.isHexString('data', data);
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
assert.isETHAddressHex('signerAddress', signerAddress);
const normalizedSignerAddress = signerAddress.toLowerCase();
- const dataBuff = ethUtil.toBuffer(data);
- const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
+ const msgHashBuff = ethUtil.toBuffer(data);
try {
const pubKey = ethUtil.ecrecover(
msgHashBuff,
@@ -36,23 +174,23 @@ export function isValidSignature(data: string, signature: ECSignature, signerAdd
return false;
}
}
+
/**
* Signs an orderHash and returns it's elliptic curve signature.
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
* @param orderHash Hex encoded orderHash 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 shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message`
- * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
- * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
- * before sending the request.
+ * @param hashPrefixOpts Different signers add/require different prefixes be appended to the message being signed.
+ * Since we cannot know ahead of time which signer you are using, you must supply both a prefixType and
+ * whether it must be added before calling `eth_sign` (some signers add it themselves)
* @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
*/
-export async function signOrderHashAsync(
+export async function ecSignOrderHashAsync(
provider: Provider,
orderHash: string,
signerAddress: string,
- shouldAddPersonalMessagePrefix: boolean,
+ messagePrefixOpts: MessagePrefixOpts,
): Promise<ECSignature> {
assert.isHexString('orderHash', orderHash);
const web3Wrapper = new Web3Wrapper(provider);
@@ -60,12 +198,10 @@ export async function signOrderHashAsync(
const normalizedSignerAddress = signerAddress.toLowerCase();
let msgHashHex = orderHash;
- if (shouldAddPersonalMessagePrefix) {
- const orderHashBuff = ethUtil.toBuffer(orderHash);
- const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
- msgHashHex = ethUtil.bufferToHex(msgHashBuff);
+ const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, messagePrefixOpts.prefixType);
+ if (messagePrefixOpts.shouldAddPrefixBeforeCallingEthSign) {
+ msgHashHex = prefixedMsgHashHex;
}
-
const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
// HACK: There is no consensus on whether the signatureHex string should be formatted as
@@ -76,7 +212,7 @@ export async function signOrderHashAsync(
const validVParamValues = [27, 28];
const ecSignatureVRS = parseSignatureHexAsVRS(signature);
if (_.includes(validVParamValues, ecSignatureVRS.v)) {
- const isValidVRSSignature = isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress);
+ const isValidVRSSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureVRS, normalizedSignerAddress);
if (isValidVRSSignature) {
return ecSignatureVRS;
}
@@ -84,7 +220,7 @@ export async function signOrderHashAsync(
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
- const isValidRSVSignature = isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress);
+ const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress);
if (isValidRSVSignature) {
return ecSignatureRSV;
}
@@ -93,6 +229,57 @@ export async function signOrderHashAsync(
throw new Error(OrderError.InvalidSignature);
}
+export function addSignedMessagePrefix(message: string, messagePrefixType: MessagePrefixType): string {
+ switch (messagePrefixType) {
+ case MessagePrefixType.None:
+ return message;
+
+ case MessagePrefixType.EthSign: {
+ const msgBuff = ethUtil.toBuffer(message);
+ const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff);
+ const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
+ return prefixedMsgHex;
+ }
+
+ case MessagePrefixType.Trezor: {
+ const msgBuff = ethUtil.toBuffer(message);
+ const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff);
+ const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
+ return prefixedMsgHex;
+ }
+
+ default:
+ throw new Error(`Unrecognized MessagePrefixType: ${messagePrefixType}`);
+ }
+}
+
+function hashTrezorPersonalMessage(message: Buffer): Buffer {
+ const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.length));
+ return ethUtil.sha3(Buffer.concat([prefix, message]));
+}
+
+function parseECSignature(signature: string): ECSignature {
+ const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor];
+ assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes);
+
+ // tslint:disable-next-line:custom-no-magic-numbers
+ const vrsHex = signature.slice(0, -2);
+ const ecSignature = parseSignatureHexAsVRS(vrsHex);
+
+ return ecSignature;
+}
+
+function parseValidatorSignature(signature: string): ValidatorSignature {
+ assert.isOneOfExpectedSignatureTypes(signature, [SignatureType.Validator]);
+ // tslint:disable:custom-no-magic-numbers
+ const validatorSignature = {
+ validatorAddress: signature.slice(-22, -2),
+ signature: signature.slice(0, -22),
+ };
+ // tslint:enable:custom-no-magic-numbers
+ return validatorSignature;
+}
+
function parseSignatureHexAsVRS(signatureHex: string): ECSignature {
const signatureBuffer = ethUtil.toBuffer(signatureHex);
let v = signatureBuffer[0];