Make signature_util into an object literal so related functions are rendered together in the docs

This commit is contained in:
Fabio Berger 2018-08-03 17:15:14 +02:00
parent 0d3d9dad84
commit d85ce6ac75
10 changed files with 379 additions and 317 deletions

View File

@ -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<boolean> {
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<ECSignature> {
const signature = await ecSignOrderHashAsync(
const signature = await signatureUtils.ecSignOrderHashAsync(
this._contractWrappers.getProvider(),
orderHash,
signerAddress,

View File

@ -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<void> {
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 {

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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;

View File

@ -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,

View File

@ -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<boolean> {
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);
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<boolean> {
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.EthSign: {
const ecSignature = parseECSignature(signature);
const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.EthSign);
return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress);
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.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> {
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<boolean> {
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<boolean> {
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),
},
/**
* 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<boolean> {
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<boolean> {
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<boolean> {
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,
);
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<ECSignature> {
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 ecSignatureRSV = parseSignatureHexAsRSV(signature);
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress);
if (isValidRSVSignature) {
return ecSignatureRSV;
}
}
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;
if (!isValidatorApproved) {
throw new Error(
`Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`,
);
}
case MessagePrefixType.Trezor: {
const msgBuff = ethUtil.toBuffer(message);
const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff);
const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
return prefixedMsgHex;
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.
*/
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;
}
},
/**
* 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<ECSignature> {
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;
}
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;
}
}
default:
throw new Error(`Unrecognized MessagePrefixType: ${messagePrefixType}`);
}
}
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
const isValidRSVSignature = signatureUtils.isValidECSignature(
prefixedMsgHashHex,
ecSignatureRSV,
normalizedSignerAddress,
);
if (isValidRSVSignature) {
return ecSignatureRSV;
}
}
/**
* 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);
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;
// tslint:disable-next-line:custom-no-magic-numbers
const vrsHex = signature.slice(0, -2);
const ecSignature = parseSignatureHexAsVRS(vrsHex);
case MessagePrefixType.EthSign: {
const msgBuff = ethUtil.toBuffer(message);
const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff);
const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
return prefixedMsgHex;
}
return ecSignature;
}
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));

View File

@ -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);
});
});

View File

@ -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<void> {
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`);
},
};