From 8cd4578d839c4311ff69ce9bb0245867e25dcf6d Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 1 Jun 2018 11:34:12 -0700 Subject: Add signature specific validation methods, and other refactors --- packages/order-utils/src/assert.ts | 14 ++- packages/order-utils/src/signature_utils.ts | 120 ++++++++++++---------- packages/order-utils/src/utils.ts | 9 ++ packages/order-utils/test/signature_utils_test.ts | 2 - packages/types/src/index.ts | 8 ++ 5 files changed, 98 insertions(+), 55 deletions(-) create mode 100644 packages/order-utils/src/utils.ts diff --git a/packages/order-utils/src/assert.ts b/packages/order-utils/src/assert.ts index 07cde453a..a1318b9b8 100644 --- a/packages/order-utils/src/assert.ts +++ b/packages/order-utils/src/assert.ts @@ -3,11 +3,13 @@ import { assert as sharedAssert } from '@0xproject/assert'; // tslint:disable-next-line:no-unused-variable import { Schema } from '@0xproject/json-schemas'; // tslint:disable-next-line:no-unused-variable -import { ECSignature } from '@0xproject/types'; +import { ECSignature, SignatureType } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; +import { utils } from './utils'; + export const assert = { ...sharedAssert, async isSenderAddressAsync( @@ -22,4 +24,14 @@ export const assert = { `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`, ); }, + isOneOfExpectedSignatureTypes(signature: string, signatureTypes: SignatureType[]): void { + sharedAssert.isHexString('signature', signature); + const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature); + const isExpectedSignatureType = _.includes(signatureTypes, signatureTypeIndexIfExists); + if (!isExpectedSignatureType) { + throw new Error( + `Unexpected signatureType: ${signatureTypeIndexIfExists}. Valid signature types: ${signatureTypes}`, + ); + } + }, }; diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 427448260..dadf5052c 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, Provider, SignatureType } 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'; @@ -7,9 +7,10 @@ import * as _ from 'lodash'; import { artifacts } from './artifacts'; import { assert } from './assert'; import { ExchangeContract } from './generated_contract_wrappers/exchange'; -import { IWalletContract } from './generated_contract_wrappers/i_signer'; 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 provided signature is valid according to the 0x Protocol smart contracts @@ -25,7 +26,7 @@ export async function isValidSignatureAsync( signature: string, signerAddress: string, ): Promise { - const signatureTypeIndexIfExists = getSignatureTypeIndexIfExists(signature); + const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature); if (_.isUndefined(signatureTypeIndexIfExists)) { throw new Error(`Unrecognized signatureType in signature: ${signature}`); } @@ -52,30 +53,17 @@ export async function isValidSignatureAsync( throw new Error('Caller signature type cannot be validated off-chain'); case SignatureType.Wallet: { - const signerContract = new IWalletContract(artifacts.IWallet.abi, signerAddress, provider); - const isValid = await signerContract.isValidSignature.callAsync(data, signature); + const isValid = await isValidWalletSignatureAsync(provider, data, signature, signerAddress); return isValid; } case SignatureType.Validator: { - const validatorAddress = getValidatorAddressFromSignature(signature); - const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider); - const isValidatorApproved = await exchangeContract.allowedValidators.callAsync( - signerAddress, - validatorAddress, - ); - if (!isValidatorApproved) { - throw new Error(`Validator ${validatorAddress} was not pre-approved by ${signerAddress}.`); - } - - const validatorSignature = getValidatorSignatureFromSignature(signature); - const validatorContract = new IValidatorContract(artifacts.IValidator.abi, signerAddress, provider); - const isValid = await validatorContract.isValidSignature.callAsync(data, signerAddress, validatorSignature); + const isValid = await isValidValidatorSignatureAsync(provider, data, signature, signerAddress); return isValid; } case SignatureType.PreSigned: { - return isValidPresignedSignatureAsync(provider, data, signature, signerAddress); + return isValidPresignedSignatureAsync(provider, data, signerAddress); } case SignatureType.Trezor: { @@ -99,7 +87,6 @@ export async function isValidSignatureAsync( export async function isValidPresignedSignatureAsync( provider: Provider, data: string, - signature: string, signerAddress: string, ): Promise { const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider); @@ -107,6 +94,58 @@ export async function isValidPresignedSignatureAsync( 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 { + // 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 { + 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` @@ -220,13 +259,8 @@ function hashTrezorPersonalMessage(message: Buffer): Buffer { } function parseECSignature(signature: string): ECSignature { - assert.isHexString('signature', signature); - const signatureTypeIndexIfExists = getSignatureTypeIndexIfExists(signature); const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor]; - const isECSignatureType = _.includes(ecSignatureTypes, signatureTypeIndexIfExists); - if (!isECSignatureType) { - throw new Error(`Cannot parse non-ECSignature type: ${signatureTypeIndexIfExists}`); - } + assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes); // tslint:disable-next-line:custom-no-magic-numbers const vrsHex = signature.slice(0, -2); @@ -235,35 +269,17 @@ function parseECSignature(signature: string): ECSignature { return ecSignature; } -function getValidatorSignatureFromSignature(signature: string): string { - const signatureTypeIndex = getSignatureTypeIndexIfExists(signature); - if (signatureTypeIndex !== SignatureType.Validator) { - throw new Error('Cannot get validator address from non-validator signature'); - } - // tslint:disable-next-line:custom-no-magic-numbers - const validatorSignature = signature.slice(0, -22); +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 getValidatorAddressFromSignature(signature: string): string { - const signatureTypeIndex = getSignatureTypeIndexIfExists(signature); - if (signatureTypeIndex !== SignatureType.Validator) { - throw new Error('Cannot get validator address from non-validator signature'); - } - // tslint:disable-next-line:custom-no-magic-numbers - const validatorAddress = signature.slice(-22, -2); - return `0x${validatorAddress}`; -} - -function getSignatureTypeIndexIfExists(signature: string): number { - const unprefixedSignature = ethUtil.stripHexPrefix(signature); - // tslint:disable-next-line:custom-no-magic-numbers - const signatureTypeHex = unprefixedSignature.slice(-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]; diff --git a/packages/order-utils/src/utils.ts b/packages/order-utils/src/utils.ts new file mode 100644 index 000000000..3b465cece --- /dev/null +++ b/packages/order-utils/src/utils.ts @@ -0,0 +1,9 @@ +export const utils = { + getSignatureTypeIndexIfExists(signature: string): number { + // tslint:disable-next-line:custom-no-magic-numbers + const signatureTypeHex = signature.slice(-2); + const base = 16; + const signatureTypeInt = parseInt(signatureTypeHex, base); + return signatureTypeInt; + }, +}; diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 4ecd14877..16d961ed7 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -69,8 +69,6 @@ describe('Signature utils', () => { const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, trezorSignature, address); expect(isValidSignatureLocal).to.be.true(); }); - - // TODO: Add remaining signature tests: Validator, Wallet, Presigned? }); describe('#isValidECSignature', () => { const signature = { diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index bb8fe896d..b1d6f97cb 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -325,6 +325,14 @@ export interface ECSignature { s: string; } +/** + * Validator signature components + */ +export interface ValidatorSignature { + validatorAddress: string; + signature: string; +} + /** * Errors originating from the 0x exchange contract */ -- cgit v1.2.3