From 45e9fbe8f93f68f3786629fff1861b1a66b90635 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 31 Jul 2018 17:24:19 +0800 Subject: Introduce SignerProviderType This allows the developer to indicate the nuanced signer provider. Some have different implementations (trezor, ledger) and others have different implementations (metamask). Breaking the abstraction of eth_sign. EthSign assumes a spec compliant implementation and can be used as a default --- packages/order-utils/src/signature_utils.ts | 80 ++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 25 deletions(-) (limited to 'packages/order-utils/src/signature_utils.ts') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 26fb24705..3237259c9 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, SignatureType, ValidatorSignature } from '@0xproject/types'; +import { ECSignature, SignatureType, SignerProviderType, ValidatorSignature } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; @@ -10,7 +10,7 @@ import { assert } from './assert'; import { ExchangeContract } from './generated_contract_wrappers/exchange'; import { IValidatorContract } from './generated_contract_wrappers/i_validator'; import { IWalletContract } from './generated_contract_wrappers/i_wallet'; -import { MessagePrefixOpts, MessagePrefixType, OrderError } from './types'; +import { OrderError } from './types'; import { utils } from './utils'; /** @@ -48,7 +48,7 @@ export async function isValidSignatureAsync( case SignatureType.EthSign: { const ecSignature = parseECSignature(signature); - const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.EthSign); + const prefixedMessageHex = addSignedMessagePrefix(data, SignerProviderType.EthSign); return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); } @@ -72,7 +72,7 @@ export async function isValidSignatureAsync( } case SignatureType.Trezor: { - const prefixedMessageHex = addSignedMessagePrefix(data, MessagePrefixType.Trezor); + const prefixedMessageHex = addSignedMessagePrefix(data, SignerProviderType.Trezor); const ecSignature = parseECSignature(signature); return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); } @@ -191,22 +191,22 @@ export function isValidECSignature(data: string, signature: ECSignature, signerA } /** - * Signs an orderHash and returns it's elliptic curve signature. + * Signs an orderHash and returns it's elliptic curve signature and signature type. * 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. + * @param messagePrefixOpts Different signers add/require different prefixes be prepended 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. + * @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type. */ export async function ecSignOrderHashAsync( provider: Provider, orderHash: string, signerAddress: string, - messagePrefixOpts: MessagePrefixOpts, -): Promise { + signerProviderType: SignerProviderType, +): Promise { assert.isWeb3Provider('provider', provider); assert.isHexString('orderHash', orderHash); assert.isETHAddressHex('signerAddress', signerAddress); @@ -215,8 +215,9 @@ export async function ecSignOrderHashAsync( const normalizedSignerAddress = signerAddress.toLowerCase(); let msgHashHex = orderHash; - const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, messagePrefixOpts.prefixType); - if (messagePrefixOpts.shouldAddPrefixBeforeCallingEthSign) { + const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, signerProviderType); + // Metamask incorrectly implements eth_sign and does not prefix the message as per the spec + if (signerProviderType === SignerProviderType.Metamask) { msgHashHex = prefixedMsgHashHex; } const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex); @@ -231,7 +232,8 @@ export async function ecSignOrderHashAsync( if (_.includes(validVParamValues, ecSignatureVRS.v)) { const isValidVRSSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureVRS, normalizedSignerAddress); if (isValidVRSSignature) { - return ecSignatureVRS; + const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureVRS, signerProviderType); + return convertedSignatureHex; } } @@ -239,13 +241,45 @@ export async function ecSignOrderHashAsync( if (_.includes(validVParamValues, ecSignatureRSV.v)) { const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress); if (isValidRSVSignature) { - return ecSignatureRSV; + const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureRSV, signerProviderType); + return convertedSignatureHex; } } throw new Error(OrderError.InvalidSignature); } - +/** + * Combines ECSignature with V,R,S and the relevant signature type for use in 0x protocol + * @param ecSignature The ECSignature of the signed data + * @param messagePrefixType The MessagePrefixType of the signed data + * @return Hex encoded string of signature with Signature Type + */ +export function convertECSignatureToSignatureHex( + ecSignature: ECSignature, + signerProviderType: SignerProviderType, +): string { + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ethUtil.toBuffer(ecSignature.r), + ethUtil.toBuffer(ecSignature.s), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + const signatureType = + signerProviderType === SignerProviderType.Trezor ? SignatureType.Trezor : SignatureType.EthSign; + const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); + return signatureWithType; +} +/** + * Combines the signature proof and the Signature Type. + * @param signature The hex encoded signature proof + * @param type The signature type, i.e EthSign, Trezor, Wallet etc. + * @return Hex encoded string of signature proof with Signature Type + */ +export function convertToSignatureWithType(signature: string, type: SignatureType): string { + const signatureBuffer = Buffer.concat([ethUtil.toBuffer(signature), ethUtil.toBuffer(type)]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + return signatureHex; +} /** * Adds the relevant prefix to the message being signed. * @param message Message to sign @@ -253,29 +287,25 @@ export async function ecSignOrderHashAsync( * specific message prefixes. * @return Prefixed message */ -export function addSignedMessagePrefix(message: string, messagePrefixType: MessagePrefixType): string { +export function addSignedMessagePrefix(message: string, signerProviderType: SignerProviderType): string { assert.isString('message', message); - assert.doesBelongToStringEnum('messagePrefixType', messagePrefixType, MessagePrefixType); - switch (messagePrefixType) { - case MessagePrefixType.None: - return message; - - case MessagePrefixType.EthSign: { + assert.doesBelongToStringEnum('signerProviderType', signerProviderType, SignerProviderType); + switch (signerProviderType) { + case SignerProviderType.Metamask: + case SignerProviderType.EthSign: { const msgBuff = ethUtil.toBuffer(message); const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); return prefixedMsgHex; } - - case MessagePrefixType.Trezor: { + case SignerProviderType.Trezor: { const msgBuff = ethUtil.toBuffer(message); const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff); const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); return prefixedMsgHex; } - default: - throw new Error(`Unrecognized MessagePrefixType: ${messagePrefixType}`); + throw new Error(`Unrecognized SignerProviderType: ${signerProviderType}`); } } -- cgit v1.2.3 From 9dd6ba78250d8bbde1d5023ce4ac4254884f4115 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 3 Aug 2018 11:35:03 +0800 Subject: Update jsdoc --- packages/order-utils/src/signature_utils.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'packages/order-utils/src/signature_utils.ts') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 3237259c9..07644ebe2 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -196,9 +196,8 @@ export function isValidECSignature(data: string, signature: ECSignature, signerA * @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 prepended 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) + * @param signerProviderType Different signers add/require different prefixes to be prepended to the message being signed. + * Since we cannot know ahead of time which signer you are using, you must supply a SignerProviderType. * @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type. */ export async function ecSignOrderHashAsync( @@ -251,7 +250,7 @@ export async function ecSignOrderHashAsync( /** * Combines ECSignature with V,R,S and the relevant signature type for use in 0x protocol * @param ecSignature The ECSignature of the signed data - * @param messagePrefixType The MessagePrefixType of the signed data + * @param signerProviderType The SignerProviderType of the signed data * @return Hex encoded string of signature with Signature Type */ export function convertECSignatureToSignatureHex( @@ -283,7 +282,7 @@ export function convertToSignatureWithType(signature: string, type: SignatureTyp /** * 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 + * @param signerProviderType The type of message prefix to add for a given SignerProviderType. Different signers expect * specific message prefixes. * @return Prefixed message */ -- cgit v1.2.3 From 5d4dd406f2946b43377049d7422c72b433bc64ab Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 3 Aug 2018 11:53:26 +0800 Subject: Update Changelogs. Rebased from development --- packages/order-utils/src/signature_utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'packages/order-utils/src/signature_utils.ts') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 07644ebe2..db5a35f80 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -271,11 +271,11 @@ export function convertECSignatureToSignatureHex( /** * Combines the signature proof and the Signature Type. * @param signature The hex encoded signature proof - * @param type The signature type, i.e EthSign, Trezor, Wallet etc. + * @param signatureType The signature type, i.e EthSign, Trezor, Wallet etc. * @return Hex encoded string of signature proof with Signature Type */ -export function convertToSignatureWithType(signature: string, type: SignatureType): string { - const signatureBuffer = Buffer.concat([ethUtil.toBuffer(signature), ethUtil.toBuffer(type)]); +export function convertToSignatureWithType(signature: string, signatureType: SignatureType): string { + const signatureBuffer = Buffer.concat([ethUtil.toBuffer(signature), ethUtil.toBuffer(signatureType)]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; return signatureHex; } @@ -291,6 +291,7 @@ export function addSignedMessagePrefix(message: string, signerProviderType: Sign assert.doesBelongToStringEnum('signerProviderType', signerProviderType, SignerProviderType); switch (signerProviderType) { case SignerProviderType.Metamask: + case SignerProviderType.Ledger: case SignerProviderType.EthSign: { const msgBuff = ethUtil.toBuffer(message); const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); -- cgit v1.2.3 From ca4905c3436931684d113ec66882836a4d0b265b Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 9 Aug 2018 12:24:52 +1000 Subject: Rename from SignerProviderType.EthSign to SignerType.Default --- packages/order-utils/src/signature_utils.ts | 83 ++++++++++++++++------------- 1 file changed, 46 insertions(+), 37 deletions(-) (limited to 'packages/order-utils/src/signature_utils.ts') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index db5a35f80..5a58edf38 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, SignatureType, SignerProviderType, ValidatorSignature } from '@0xproject/types'; +import { ECSignature, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; @@ -48,7 +48,7 @@ export async function isValidSignatureAsync( case SignatureType.EthSign: { const ecSignature = parseECSignature(signature); - const prefixedMessageHex = addSignedMessagePrefix(data, SignerProviderType.EthSign); + const prefixedMessageHex = addSignedMessagePrefix(data, SignerType.Default); return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); } @@ -72,7 +72,7 @@ export async function isValidSignatureAsync( } case SignatureType.Trezor: { - const prefixedMessageHex = addSignedMessagePrefix(data, SignerProviderType.Trezor); + const prefixedMessageHex = addSignedMessagePrefix(data, SignerType.Trezor); const ecSignature = parseECSignature(signature); return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); } @@ -196,15 +196,15 @@ export function isValidECSignature(data: string, signature: ECSignature, signerA * @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 signerProviderType Different signers add/require different prefixes to be prepended to the message being signed. - * Since we cannot know ahead of time which signer you are using, you must supply a SignerProviderType. + * @param signerType Different signers add/require different prefixes to be prepended to the message being signed. + * Since we cannot know ahead of time which signer you are using, you must supply a SignerType. * @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type. */ export async function ecSignOrderHashAsync( provider: Provider, orderHash: string, signerAddress: string, - signerProviderType: SignerProviderType, + signerType: SignerType, ): Promise { assert.isWeb3Provider('provider', provider); assert.isHexString('orderHash', orderHash); @@ -214,9 +214,9 @@ export async function ecSignOrderHashAsync( const normalizedSignerAddress = signerAddress.toLowerCase(); let msgHashHex = orderHash; - const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, signerProviderType); + const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, signerType); // Metamask incorrectly implements eth_sign and does not prefix the message as per the spec - if (signerProviderType === SignerProviderType.Metamask) { + if (signerType === SignerType.Metamask) { msgHashHex = prefixedMsgHashHex; } const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex); @@ -225,22 +225,22 @@ export async function ecSignOrderHashAsync( // 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. + // r + s + v is the most prevalent format from eth_sign, so we attempt this first. // 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) { - const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureVRS, signerProviderType); - return convertedSignatureHex; - } - } - const ecSignatureRSV = parseSignatureHexAsRSV(signature); if (_.includes(validVParamValues, ecSignatureRSV.v)) { const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress); if (isValidRSVSignature) { - const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureRSV, signerProviderType); + const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureRSV, signerType); + return convertedSignatureHex; + } + } + const ecSignatureVRS = parseSignatureHexAsVRS(signature); + if (_.includes(validVParamValues, ecSignatureVRS.v)) { + const isValidVRSSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureVRS, normalizedSignerAddress); + if (isValidVRSSignature) { + const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureVRS, signerType); return convertedSignatureHex; } } @@ -250,23 +250,32 @@ export async function ecSignOrderHashAsync( /** * Combines ECSignature with V,R,S and the relevant signature type for use in 0x protocol * @param ecSignature The ECSignature of the signed data - * @param signerProviderType The SignerProviderType of the signed data - * @return Hex encoded string of signature with Signature Type + * @param signerType The SignerType of the signed data + * @return Hex encoded string of signature (v,r,s) with Signature Type */ -export function convertECSignatureToSignatureHex( - ecSignature: ECSignature, - signerProviderType: SignerProviderType, -): string { +export function convertECSignatureToSignatureHex(ecSignature: ECSignature, signerType: SignerType): string { const signatureBuffer = Buffer.concat([ ethUtil.toBuffer(ecSignature.v), ethUtil.toBuffer(ecSignature.r), ethUtil.toBuffer(ecSignature.s), ]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; - const signatureType = - signerProviderType === SignerProviderType.Trezor ? SignatureType.Trezor : SignatureType.EthSign; - const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); - return signatureWithType; + switch (signerType) { + case SignerType.Metamask: + case SignerType.Ledger: + case SignerType.Default: { + const signatureType = SignatureType.EthSign; + const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); + return signatureWithType; + } + case SignerType.Trezor: { + const signatureType = SignatureType.Trezor; + const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); + return signatureWithType; + } + default: + throw new Error(`Unrecognized SignerType: ${signerType}`); + } } /** * Combines the signature proof and the Signature Type. @@ -282,30 +291,30 @@ export function convertToSignatureWithType(signature: string, signatureType: Sig /** * Adds the relevant prefix to the message being signed. * @param message Message to sign - * @param signerProviderType The type of message prefix to add for a given SignerProviderType. Different signers expect + * @param signerType The type of message prefix to add for a given SignerType. Different signers expect * specific message prefixes. * @return Prefixed message */ -export function addSignedMessagePrefix(message: string, signerProviderType: SignerProviderType): string { +export function addSignedMessagePrefix(message: string, signerType: SignerType = SignerType.Default): string { assert.isString('message', message); - assert.doesBelongToStringEnum('signerProviderType', signerProviderType, SignerProviderType); - switch (signerProviderType) { - case SignerProviderType.Metamask: - case SignerProviderType.Ledger: - case SignerProviderType.EthSign: { + assert.doesBelongToStringEnum('signerType', signerType, SignerType); + switch (signerType) { + case SignerType.Metamask: + case SignerType.Ledger: + case SignerType.Default: { const msgBuff = ethUtil.toBuffer(message); const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); return prefixedMsgHex; } - case SignerProviderType.Trezor: { + case SignerType.Trezor: { const msgBuff = ethUtil.toBuffer(message); const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff); const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); return prefixedMsgHex; } default: - throw new Error(`Unrecognized SignerProviderType: ${signerProviderType}`); + throw new Error(`Unrecognized SignerType: ${signerType}`); } } -- cgit v1.2.3 From a3517574936aa6a4911003dbff06302926b04cb4 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 14 Aug 2018 08:32:16 +1000 Subject: Update version numbers. Add source for Metamask future fix. Consolidate switch statement to one return --- packages/order-utils/src/signature_utils.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'packages/order-utils/src/signature_utils.ts') diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 5a58edf38..870aef2ed 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -216,6 +216,7 @@ export async function ecSignOrderHashAsync( let msgHashHex = orderHash; const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, signerType); // Metamask incorrectly implements eth_sign and does not prefix the message as per the spec + // Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e if (signerType === SignerType.Metamask) { msgHashHex = prefixedMsgHashHex; } @@ -260,22 +261,23 @@ export function convertECSignatureToSignatureHex(ecSignature: ECSignature, signe ethUtil.toBuffer(ecSignature.s), ]); const signatureHex = `0x${signatureBuffer.toString('hex')}`; + let signatureType; switch (signerType) { case SignerType.Metamask: case SignerType.Ledger: case SignerType.Default: { - const signatureType = SignatureType.EthSign; - const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); - return signatureWithType; + signatureType = SignatureType.EthSign; + break; } case SignerType.Trezor: { - const signatureType = SignatureType.Trezor; - const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); - return signatureWithType; + signatureType = SignatureType.Trezor; + break; } default: throw new Error(`Unrecognized SignerType: ${signerType}`); } + const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); + return signatureWithType; } /** * Combines the signature proof and the Signature Type. -- cgit v1.2.3