From d85ce6ac758a9a726c298b972a7c557e01f2ab2f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 3 Aug 2018 17:15:14 +0200 Subject: Make signature_util into an object literal so related functions are rendered together in the docs --- packages/0x.js/src/0x.ts | 9 +- packages/contract-wrappers/src/utils/assert.ts | 4 +- .../contracts/test/exchange/signature_validator.ts | 22 +- packages/fill-scenarios/src/fill_scenarios.ts | 3 +- packages/order-utils/src/index.ts | 12 +- packages/order-utils/src/order_factory.ts | 9 +- packages/order-utils/src/order_validation_utils.ts | 4 +- packages/order-utils/src/signature_utils.ts | 559 +++++++++++---------- packages/order-utils/test/signature_utils_test.ts | 68 ++- packages/order-watcher/src/utils/assert.ts | 4 +- 10 files changed, 378 insertions(+), 316 deletions(-) diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index 2a2b82f63..9c01ebd9e 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -11,9 +11,8 @@ import { } from '@0xproject/contract-wrappers'; import { assetDataUtils, - ecSignOrderHashAsync, generatePseudoRandomSalt, - isValidSignatureAsync, + signatureUtils, MessagePrefixOpts, orderHashUtils, } from '@0xproject/order-utils'; @@ -198,7 +197,7 @@ export class ZeroEx { * @return Whether the signature is valid for the supplied signerAddress and data. */ public async isValidSignatureAsync(data: string, signature: string, signerAddress: string): Promise { - const isValid = await isValidSignatureAsync( + const isValid = await signatureUtils.isValidSignatureAsync( this._contractWrappers.getProvider(), data, signature, @@ -238,7 +237,7 @@ export class ZeroEx { * @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 MessagePrefixOpts Options regarding the desired prefix and whether to add it before calling `eth_sign` + * @param messagePrefixOpts Options regarding the desired prefix and whether to add it before calling `eth_sign` * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. */ public async ecSignOrderHashAsync( @@ -246,7 +245,7 @@ export class ZeroEx { signerAddress: string, messagePrefixOpts: MessagePrefixOpts, ): Promise { - const signature = await ecSignOrderHashAsync( + const signature = await signatureUtils.ecSignOrderHashAsync( this._contractWrappers.getProvider(), orderHash, signerAddress, diff --git a/packages/contract-wrappers/src/utils/assert.ts b/packages/contract-wrappers/src/utils/assert.ts index 842b16fa0..652e5bec3 100644 --- a/packages/contract-wrappers/src/utils/assert.ts +++ b/packages/contract-wrappers/src/utils/assert.ts @@ -1,7 +1,7 @@ import { assert as sharedAssert } from '@0xproject/assert'; // HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here import { Schema } from '@0xproject/json-schemas'; // tslint:disable-line:no-unused-variable -import { isValidSignatureAsync } from '@0xproject/order-utils'; +import { signatureUtils } from '@0xproject/order-utils'; import { ECSignature } from '@0xproject/types'; // tslint:disable-line:no-unused-variable import { BigNumber } from '@0xproject/utils'; // tslint:disable-line:no-unused-variable import { Web3Wrapper } from '@0xproject/web3-wrapper'; @@ -15,7 +15,7 @@ export const assert = { signature: string, signerAddress: string, ): Promise { - const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress); + const isValid = await signatureUtils.isValidSignatureAsync(provider, orderHash, signature, signerAddress); this.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`); }, isValidSubscriptionToken(variableName: string, subscriptionToken: string): void { diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts index f2bb42c75..d5e522220 100644 --- a/packages/contracts/test/exchange/signature_validator.ts +++ b/packages/contracts/test/exchange/signature_validator.ts @@ -1,5 +1,5 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { addSignedMessagePrefix, assetDataUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils'; +import { signatureUtils, assetDataUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils'; import { RevertReason, SignatureType, SignedOrder } from '@0xproject/types'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; @@ -213,7 +213,10 @@ describe('MixinSignatureValidator', () => { it('should return true when SignatureType=EthSign and signature is valid', async () => { // Create EthSign signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithEthSignPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.EthSign); + const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix( + orderHashHex, + MessagePrefixType.EthSign, + ); const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); // Create 0x signature from EthSign signature @@ -236,7 +239,10 @@ describe('MixinSignatureValidator', () => { it('should return false when SignatureType=EthSign and signature is invalid', async () => { // Create EthSign signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithEthSignPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.EthSign); + const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix( + orderHashHex, + MessagePrefixType.EthSign, + ); const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); // Create 0x signature from EthSign signature @@ -385,7 +391,10 @@ describe('MixinSignatureValidator', () => { it('should return true when SignatureType=Trezor and signature is valid', async () => { // Create Trezor signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithTrezorPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.Trezor); + const orderHashWithTrezorPrefixHex = signatureUtils.addSignedMessagePrefix( + orderHashHex, + MessagePrefixType.Trezor, + ); const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey); // Create 0x signature from Trezor signature @@ -408,7 +417,10 @@ describe('MixinSignatureValidator', () => { it('should return false when SignatureType=Trezor and signature is invalid', async () => { // Create Trezor signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithTrezorPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.Trezor); + const orderHashWithTrezorPrefixHex = signatureUtils.addSignedMessagePrefix( + orderHashHex, + MessagePrefixType.Trezor, + ); const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey); // Create 0x signature from Trezor signature diff --git a/packages/fill-scenarios/src/fill_scenarios.ts b/packages/fill-scenarios/src/fill_scenarios.ts index 8f2766e24..087fd37a5 100644 --- a/packages/fill-scenarios/src/fill_scenarios.ts +++ b/packages/fill-scenarios/src/fill_scenarios.ts @@ -1,4 +1,5 @@ -import { assetDataUtils, orderFactory } from '@0xproject/order-utils'; +import { assetDataUtils } from '@0xproject/order-utils'; +import { orderFactory } from '@0xproject/order-utils/lib/src/order_factory'; import { AssetProxyId, ERC721AssetData, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index da53f620a..5852a01b9 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -1,15 +1,5 @@ export { orderHashUtils } from './order_hash'; -export { - isValidSignatureAsync, - isValidPresignedSignatureAsync, - isValidWalletSignatureAsync, - isValidValidatorSignatureAsync, - isValidECSignature, - ecSignOrderHashAsync, - addSignedMessagePrefix, - parseECSignature, -} from './signature_utils'; -export { orderFactory } from './order_factory'; +export { signatureUtils } from './signature_utils'; export { constants } from './constants'; export { crypto } from './crypto'; export { generatePseudoRandomSalt } from './salt'; diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 803cb82b1..14122f353 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -6,7 +6,7 @@ import * as _ from 'lodash'; import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; -import { ecSignOrderHashAsync } from './signature_utils'; +import { signatureUtils } from './signature_utils'; import { MessagePrefixType } from './types'; export const orderFactory = { @@ -49,7 +49,12 @@ export const orderFactory = { prefixType: MessagePrefixType.EthSign, shouldAddPrefixBeforeCallingEthSign: false, }; - const ecSignature = await ecSignOrderHashAsync(provider, orderHash, makerAddress, messagePrefixOpts); + const ecSignature = await signatureUtils.ecSignOrderHashAsync( + provider, + orderHash, + makerAddress, + messagePrefixOpts, + ); const signature = getVRSHexString(ecSignature); const signedOrder: SignedOrder = _.assign(order, { signature }); return signedOrder; diff --git a/packages/order-utils/src/order_validation_utils.ts b/packages/order-utils/src/order_validation_utils.ts index 67d747081..ccc6e653f 100644 --- a/packages/order-utils/src/order_validation_utils.ts +++ b/packages/order-utils/src/order_validation_utils.ts @@ -9,7 +9,7 @@ import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_f import { constants } from './constants'; import { ExchangeTransferSimulator } from './exchange_transfer_simulator'; import { orderHashUtils } from './order_hash'; -import { isValidSignatureAsync } from './signature_utils'; +import { signatureUtils } from './signature_utils'; import { utils } from './utils'; export class OrderValidationUtils { @@ -147,7 +147,7 @@ export class OrderValidationUtils { throw new Error(RevertReason.InvalidTakerAmount); } const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const isValid = await isValidSignatureAsync( + const isValid = await signatureUtils.isValidSignatureAsync( provider, orderHash, signedOrder.signature, diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 26fb24705..2dc465256 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -13,288 +13,307 @@ import { IWalletContract } from './generated_contract_wrappers/i_wallet'; import { MessagePrefixOpts, MessagePrefixType, OrderError } from './types'; import { utils } from './utils'; -/** - * 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 A hex encoded 0x Protocol signature made up of: [TypeSpecificData][SignatureType]. - * E.g [vrs][SignatureType.EIP712] - * @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 async function isValidSignatureAsync( - provider: Provider, - data: string, - signature: string, - signerAddress: string, -): Promise { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isHexString('signature', signature); - assert.isETHAddressHex('signerAddress', signerAddress); - 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); +export const signatureUtils = { + /** + * Verifies that the provided signature is valid according to the 0x Protocol smart contracts + * @param provider Web3 provider to use for all JSON RPC requests + * @param data The hex encoded data signed by the supplied signature. + * @param signature A hex encoded 0x Protocol signature made up of: [TypeSpecificData][SignatureType]. + * E.g [vrs][SignatureType.EIP712] + * @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. + */ + async isValidSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, + ): Promise { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isHexString('signature', signature); + assert.isETHAddressHex('signerAddress', signerAddress); + const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature); + if (_.isUndefined(signatureTypeIndexIfExists)) { + throw new Error(`Unrecognized signatureType in signature: ${signature}`); } - 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; + switch (signatureTypeIndexIfExists) { + case SignatureType.Illegal: + case SignatureType.Invalid: + return false; + + case SignatureType.EIP712: { + const ecSignature = signatureUtils.parseECSignature(signature); + return signatureUtils.isValidECSignature(data, ecSignature, signerAddress); + } + + case SignatureType.EthSign: { + const ecSignature = signatureUtils.parseECSignature(signature); + const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data, MessagePrefixType.EthSign); + return signatureUtils.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 signatureUtils.isValidWalletSignatureAsync( + provider, + data, + signature, + signerAddress, + ); + return isValid; + } + + case SignatureType.Validator: { + const isValid = await signatureUtils.isValidValidatorSignatureAsync( + provider, + data, + signature, + signerAddress, + ); + return isValid; + } + + case SignatureType.PreSigned: { + return signatureUtils.isValidPresignedSignatureAsync(provider, data, signerAddress); + } + + case SignatureType.Trezor: { + const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data, MessagePrefixType.Trezor); + const ecSignature = signatureUtils.parseECSignature(signature); + return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); + } + + default: + throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); } - - 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); + }, + /** + * Verifies that the provided presigned signature is valid according to the 0x Protocol smart contracts + * @param provider Web3 provider to use for all JSON RPC requests + * @param data The hex encoded data signed by the supplied signature + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the data was preSigned by the supplied signerAddress + */ + async isValidPresignedSignatureAsync(provider: Provider, data: string, signerAddress: string): Promise { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isETHAddressHex('signerAddress', signerAddress); + const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.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 provider Web3 provider to use for all JSON RPC requests + * @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. + */ + async isValidWalletSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, + ): Promise { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isHexString('signature', signature); + assert.isETHAddressHex('signerAddress', signerAddress); + // tslint:disable-next-line:custom-no-magic-numbers + const signatureWithoutType = signature.slice(-2); + const walletContract = new IWalletContract(artifacts.IWallet.compilerOutput.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 provider Web3 provider to use for all JSON RPC requests + * @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. + */ + async isValidValidatorSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, + ): Promise { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isHexString('signature', signature); + assert.isETHAddressHex('signerAddress', signerAddress); + const validatorSignature = parseValidatorSignature(signature); + const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.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}.`, + ); } - 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 { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isETHAddressHex('signerAddress', signerAddress); - const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.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 { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isHexString('signature', signature); - assert.isETHAddressHex('signerAddress', signerAddress); - // tslint:disable-next-line:custom-no-magic-numbers - const signatureWithoutType = signature.slice(-2); - const walletContract = new IWalletContract(artifacts.IWallet.compilerOutput.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 { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isHexString('signature', signature); - assert.isETHAddressHex('signerAddress', signerAddress); - const validatorSignature = parseValidatorSignature(signature); - const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.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.compilerOutput.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 msgHashBuff = ethUtil.toBuffer(data); - try { - const pubKey = ethUtil.ecrecover( - msgHashBuff, - signature.v, - ethUtil.toBuffer(signature.r), - ethUtil.toBuffer(signature.s), + const validatorContract = new IValidatorContract( + artifacts.IValidator.compilerOutput.abi, + signerAddress, + provider, ); - const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); - return retrievedAddress === signerAddress; - } catch (err) { - 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 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 ecSignOrderHashAsync( - provider: Provider, - orderHash: string, - signerAddress: string, - messagePrefixOpts: MessagePrefixOpts, -): Promise { - assert.isWeb3Provider('provider', provider); - assert.isHexString('orderHash', orderHash); - assert.isETHAddressHex('signerAddress', signerAddress); - const web3Wrapper = new Web3Wrapper(provider); - await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); - const normalizedSignerAddress = signerAddress.toLowerCase(); - - let msgHashHex = orderHash; - 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 - // v + r + s OR r + s + v, and different clients (even different versions of the same client) - // return the signature params in different orders. In order to support all client implementations, - // we parse the signature in both ways, and evaluate if either one is a valid signature. - // tslint:disable-next-line:custom-no-magic-numbers - const validVParamValues = [27, 28]; - const ecSignatureVRS = parseSignatureHexAsVRS(signature); - if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureVRS, normalizedSignerAddress); - if (isValidVRSSignature) { - return ecSignatureVRS; + 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. + */ + isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean { + assert.isHexString('data', data); + assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); + assert.isETHAddressHex('signerAddress', signerAddress); + + const msgHashBuff = ethUtil.toBuffer(data); + try { + const pubKey = ethUtil.ecrecover( + msgHashBuff, + signature.v, + ethUtil.toBuffer(signature.r), + ethUtil.toBuffer(signature.s), + ); + const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); + return retrievedAddress === signerAddress; + } catch (err) { + return false; } - } - - const ecSignatureRSV = parseSignatureHexAsRSV(signature); - if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress); - if (isValidRSVSignature) { - return ecSignatureRSV; + }, + /** + * 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 provider The provider to use for JSON RPC calls + * @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 messagePrefixOpts 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. + */ + async ecSignOrderHashAsync( + provider: Provider, + orderHash: string, + signerAddress: string, + messagePrefixOpts: MessagePrefixOpts, + ): Promise { + assert.isWeb3Provider('provider', provider); + assert.isHexString('orderHash', orderHash); + 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, messagePrefixOpts.prefixType); + if (messagePrefixOpts.shouldAddPrefixBeforeCallingEthSign) { + msgHashHex = prefixedMsgHashHex; } - } - - throw new Error(OrderError.InvalidSignature); -} - -/** - * Adds the relevant prefix to the message being signed. - * @param message Message to sign - * @param messagePrefixType The type of message prefix to add. Different signers expect - * specific message prefixes. - * @return Prefixed message - */ -export function addSignedMessagePrefix(message: string, messagePrefixType: MessagePrefixType): string { - assert.isString('message', message); - assert.doesBelongToStringEnum('messagePrefixType', messagePrefixType, MessagePrefixType); - 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; + const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex); + + // 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) + // return the signature params in different orders. In order to support all client implementations, + // we parse the signature in both ways, and evaluate if either one is a valid signature. + // tslint:disable-next-line:custom-no-magic-numbers + const validVParamValues = [27, 28]; + const ecSignatureVRS = parseSignatureHexAsVRS(signature); + if (_.includes(validVParamValues, ecSignatureVRS.v)) { + const isValidVRSSignature = signatureUtils.isValidECSignature( + prefixedMsgHashHex, + ecSignatureVRS, + normalizedSignerAddress, + ); + if (isValidVRSSignature) { + return ecSignatureVRS; + } } - case MessagePrefixType.Trezor: { - const msgBuff = ethUtil.toBuffer(message); - const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff); - const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); - return prefixedMsgHex; + const ecSignatureRSV = parseSignatureHexAsRSV(signature); + if (_.includes(validVParamValues, ecSignatureRSV.v)) { + const isValidRSVSignature = signatureUtils.isValidECSignature( + prefixedMsgHashHex, + ecSignatureRSV, + normalizedSignerAddress, + ); + if (isValidRSVSignature) { + return ecSignatureRSV; + } } - default: - throw new Error(`Unrecognized MessagePrefixType: ${messagePrefixType}`); - } -} - -/** - * Parse a 0x protocol hex-encoded signature string into it's ECSignature components - * @param signature A hex encoded ecSignature 0x Protocol signature - * @return An ECSignature object with r,s,v parameters - */ -export function parseECSignature(signature: string): ECSignature { - assert.isHexString('signature', signature); - 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; -} + throw new Error(OrderError.InvalidSignature); + }, + /** + * Adds the relevant prefix to the message being signed. + * @param message Message to sign + * @param messagePrefixType The type of message prefix to add. Different signers expect + * specific message prefixes. + * @return Prefixed message + */ + addSignedMessagePrefix(message: string, messagePrefixType: MessagePrefixType): string { + assert.isString('message', message); + assert.doesBelongToStringEnum('messagePrefixType', messagePrefixType, MessagePrefixType); + 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}`); + } + }, + /** + * Parse a 0x protocol hex-encoded signature string into it's ECSignature components + * @param signature A hex encoded ecSignature 0x Protocol signature + * @return An ECSignature object with r,s,v parameters + */ + parseECSignature(signature: string): ECSignature { + assert.isHexString('signature', signature); + 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 hashTrezorPersonalMessage(message: Buffer): Buffer { const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.byteLength)); diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 5714f9671..2f36386ad 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -5,8 +5,8 @@ import * as _ from 'lodash'; import 'mocha'; import * as Sinon from 'sinon'; -import { ecSignOrderHashAsync, generatePseudoRandomSalt, MessagePrefixType } from '../src'; -import { isValidECSignature, isValidSignatureAsync } from '../src/signature_utils'; +import { generatePseudoRandomSalt, MessagePrefixType } from '../src'; +import { signatureUtils } from '../src/signature_utils'; import { chaiSetup } from './utils/chai_setup'; import { provider, web3Wrapper } from './utils/web3_wrapper'; @@ -22,12 +22,14 @@ describe('Signature utils', () => { let address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(await isValidSignatureAsync(provider, '0x0', ethSignSignature, address)).to.be.false(); + expect( + await signatureUtils.isValidSignatureAsync(provider, '0x0', ethSignSignature, address), + ).to.be.false(); }); it("should return false if the address doesn't pertain to the signature & data", async () => { const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; expect( - await isValidSignatureAsync(provider, dataHex, ethSignSignature, validUnrelatedAddress), + await signatureUtils.isValidSignatureAsync(provider, dataHex, ethSignSignature, validUnrelatedAddress), ).to.be.false(); }); it("should return false if the signature doesn't pertain to the dataHex & address", async () => { @@ -35,18 +37,27 @@ describe('Signature utils', () => { // tslint:disable-next-line:custom-no-magic-numbers signatureArray[5] = 'C'; // V = 28, instead of 27 const wrongSignature = signatureArray.join(''); - expect(await isValidSignatureAsync(provider, dataHex, wrongSignature, address)).to.be.false(); + expect( + await signatureUtils.isValidSignatureAsync(provider, dataHex, wrongSignature, address), + ).to.be.false(); }); it('should throw if signatureType is invalid', () => { const signatureArray = ethSignSignature.split(''); signatureArray[3] = '9'; // SignatureType w/ index 9 doesn't exist const signatureWithInvalidType = signatureArray.join(''); - expect(isValidSignatureAsync(provider, dataHex, signatureWithInvalidType, address)).to.be.rejected(); + expect( + signatureUtils.isValidSignatureAsync(provider, dataHex, signatureWithInvalidType, address), + ).to.be.rejected(); }); it('should return true for a valid Ecrecover (EthSign) signature', async () => { - const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, ethSignSignature, address); + const isValidSignatureLocal = await signatureUtils.isValidSignatureAsync( + provider, + dataHex, + ethSignSignature, + address, + ); expect(isValidSignatureLocal).to.be.true(); }); @@ -55,7 +66,12 @@ describe('Signature utils', () => { address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; const eip712Signature = '0x1bdde07aac4bf12c12ddbb155919c43eba4146a2cfcf904a862950dbebe332554c6674975603eb5a4eaf8fd7f2e06350267e5b36cda9851a89f8bb49fe2fc9afe202'; - const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, eip712Signature, address); + const isValidSignatureLocal = await signatureUtils.isValidSignatureAsync( + provider, + dataHex, + eip712Signature, + address, + ); expect(isValidSignatureLocal).to.be.true(); }); @@ -64,7 +80,12 @@ describe('Signature utils', () => { address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; const trezorSignature = '0x1ce4760660e6495b5ae6723087bea073b3a99ce98ea81fdf00c240279c010e63d05b87bc34c4d67d4776e8d5aeb023a67484f4eaf0fd353b40893e5101e845cd9908'; - const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, trezorSignature, address); + const isValidSignatureLocal = await signatureUtils.isValidSignatureAsync( + provider, + dataHex, + trezorSignature, + address, + ); expect(isValidSignatureLocal).to.be.true(); }); }); @@ -78,18 +99,18 @@ describe('Signature utils', () => { const address = '0x0e5cb767cce09a7f3ca594df118aa519be5e2b5a'; it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(isValidECSignature('0x0', signature, address)).to.be.false(); + expect(signatureUtils.isValidECSignature('0x0', signature, address)).to.be.false(); }); it("should return false if the address doesn't pertain to the signature & data", async () => { const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; - expect(isValidECSignature(data, signature, validUnrelatedAddress)).to.be.false(); + expect(signatureUtils.isValidECSignature(data, signature, validUnrelatedAddress)).to.be.false(); }); it("should return false if the signature doesn't pertain to the data & address", async () => { const wrongSignature = _.assign({}, signature, { v: 28 }); - expect(isValidECSignature(data, wrongSignature, address)).to.be.false(); + expect(signatureUtils.isValidECSignature(data, wrongSignature, address)).to.be.false(); }); it('should return true if the signature does pertain to the data & address', async () => { - const isValidSignatureLocal = isValidECSignature(data, signature, address); + const isValidSignatureLocal = signatureUtils.isValidECSignature(data, signature, address); expect(isValidSignatureLocal).to.be.true(); }); }); @@ -129,7 +150,12 @@ describe('Signature utils', () => { prefixType: MessagePrefixType.EthSign, shouldAddPrefixBeforeCallingEthSign: false, }; - const ecSignature = await ecSignOrderHashAsync(provider, orderHash, makerAddress, messagePrefixOpts); + const ecSignature = await signatureUtils.ecSignOrderHashAsync( + provider, + orderHash, + makerAddress, + messagePrefixOpts, + ); expect(ecSignature).to.deep.equal(expectedECSignature); }); it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => { @@ -162,7 +188,12 @@ describe('Signature utils', () => { prefixType: MessagePrefixType.EthSign, shouldAddPrefixBeforeCallingEthSign: false, }; - const ecSignature = await ecSignOrderHashAsync(fakeProvider, orderHash, makerAddress, messagePrefixOpts); + const ecSignature = await signatureUtils.ecSignOrderHashAsync( + fakeProvider, + orderHash, + makerAddress, + messagePrefixOpts, + ); expect(ecSignature).to.deep.equal(expectedECSignature); }); it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => { @@ -192,7 +223,12 @@ describe('Signature utils', () => { prefixType: MessagePrefixType.EthSign, shouldAddPrefixBeforeCallingEthSign: false, }; - const ecSignature = await ecSignOrderHashAsync(fakeProvider, orderHash, makerAddress, messagePrefixOpts); + const ecSignature = await signatureUtils.ecSignOrderHashAsync( + fakeProvider, + orderHash, + makerAddress, + messagePrefixOpts, + ); expect(ecSignature).to.deep.equal(expectedECSignature); }); }); diff --git a/packages/order-watcher/src/utils/assert.ts b/packages/order-watcher/src/utils/assert.ts index a891a60d2..e4a1e2c7b 100644 --- a/packages/order-watcher/src/utils/assert.ts +++ b/packages/order-watcher/src/utils/assert.ts @@ -7,7 +7,7 @@ import { BigNumber } from '@0xproject/utils'; // tslint:enable:no-unused-variable import { Provider } from 'ethereum-types'; -import { isValidSignatureAsync } from '@0xproject/order-utils'; +import { signatureUtils } from '@0xproject/order-utils'; export const assert = { ...sharedAssert, @@ -17,7 +17,7 @@ export const assert = { signature: string, signerAddress: string, ): Promise { - const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress); + const isValid = await signatureUtils.isValidSignatureAsync(provider, orderHash, signature, signerAddress); assert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`); }, }; -- cgit v1.2.3