From 4874d55d03918b47967024777194d88a5f2bc1fc Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 29 May 2018 16:58:30 -0700 Subject: Initial refactor of order-utils. Move many utils from contracts into this package. --- packages/order-utils/src/signature_utils.ts | 121 +++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 9 deletions(-) (limited to 'packages/order-utils/src/signature_utils.ts') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index ebd636b20..106bbf4e8 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -1,28 +1,97 @@ import { schemas } from '@0xproject/json-schemas'; -import { ECSignature, Provider } from '@0xproject/types'; +import { ECSignature, Provider, SignatureType } 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 { ExchangeContract } from './generated_contract_wrappers/exchange'; +import { ISignerContract } from './generated_contract_wrappers/i_signer'; import { OrderError } from './types'; /** - * 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 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 { + const signatureTypeIndexIfExists = getSignatureTypeIndexIfExists(signature); + if (_.isUndefined(signatureTypeIndexIfExists)) { + throw new Error(`Unrecognized signatureType in signature: ${signature}`); + } + + switch (signatureTypeIndexIfExists) { + case SignatureType.Illegal: + case SignatureType.Invalid: + return false; + + // Question: Does it make sense to handle this? + case SignatureType.Caller: + return true; + + // TODO: Rename this type to `EthSign` b/c multiple of the signature + // types use ECRecover... + case SignatureType.Ecrecover: { + const ecSignature = parseECSignature(signature); + const dataBuff = ethUtil.toBuffer(data); + const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + const msgHash = ethUtil.bufferToHex(msgHashBuff); + return isValidECSignature(msgHash, ecSignature, signerAddress); + } + + case SignatureType.EIP712: { + const ecSignature = parseECSignature(signature); + return isValidECSignature(data, ecSignature, signerAddress); + } + + case SignatureType.Trezor: { + const dataBuff = ethUtil.toBuffer(data); + const msgHashBuff = hashTrezorPersonalMessage(dataBuff); + const msgHash = ethUtil.bufferToHex(msgHashBuff); + const ecSignature = parseECSignature(signature); + return isValidECSignature(msgHash, ecSignature, signerAddress); + } + + // TODO: Rename Contract -> Wallet + case SignatureType.Contract: { + const signerContract = new ISignerContract(artifacts.ISigner.abi, signerAddress, provider); + const isValid = await signerContract.isValidSignature.callAsync(data, signature); + return isValid; + } + + case SignatureType.PreSigned: { + const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider); + const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress); + return true; + } + + default: + throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); + } +} + +export function getVRSHexString(ecSignature: ECSignature): string { + const vrs = `0x${intToHex(ecSignature.v)}${ethUtil.stripHexPrefix(ecSignature.r)}${ethUtil.stripHexPrefix( + ecSignature.s, + )}`; + return vrs; +} + +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,6 +105,7 @@ 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 @@ -48,7 +118,7 @@ export function isValidSignature(data: string, signature: ECSignature, signerAdd * before sending the request. * @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, @@ -76,7 +146,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(orderHash, ecSignatureVRS, normalizedSignerAddress); if (isValidVRSSignature) { return ecSignatureVRS; } @@ -84,7 +154,7 @@ export async function signOrderHashAsync( const ecSignatureRSV = parseSignatureHexAsRSV(signature); if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress); + const isValidRSVSignature = isValidECSignature(orderHash, ecSignatureRSV, normalizedSignerAddress); if (isValidRSVSignature) { return ecSignatureRSV; } @@ -93,6 +163,39 @@ export async function signOrderHashAsync( throw new Error(OrderError.InvalidSignature); } +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 signatureTypeIndexIfExists = getSignatureTypeIndexIfExists(signature); + const ecSignatureTypes = [SignatureType.Ecrecover, SignatureType.EIP712, SignatureType.Trezor]; + const isECSignatureType = _.includes(ecSignatureTypes, signatureTypeIndexIfExists); + if (!isECSignatureType) { + throw new Error(`Cannot parse non-ECSignature type: ${signatureTypeIndexIfExists}`); + } + + // tslint:disable-next-line:custom-no-magic-numbers + const vrsHex = `0x${signature.substr(4)}`; + const ecSignature = parseSignatureHexAsVRS(vrsHex); + + return ecSignature; +} + +function intToHex(i: number): string { + const hex = ethUtil.bufferToHex(ethUtil.toBuffer(i)); + return hex; +} + +function getSignatureTypeIndexIfExists(signature: string): number { + const unprefixedSignature = ethUtil.stripHexPrefix(signature); + const signatureTypeHex = unprefixedSignature.substr(0, 2); + const base = 16; + const signatureTypeInt = parseInt(signatureTypeHex, base); + return signatureTypeInt; +} + function parseSignatureHexAsVRS(signatureHex: string): ECSignature { const signatureBuffer = ethUtil.toBuffer(signatureHex); let v = signatureBuffer[0]; -- cgit v1.2.3