diff options
Diffstat (limited to 'packages/order-utils/src/signature_utils.ts')
-rw-r--r-- | packages/order-utils/src/signature_utils.ts | 417 |
1 files changed, 0 insertions, 417 deletions
diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts deleted file mode 100644 index 131144d48..000000000 --- a/packages/order-utils/src/signature_utils.ts +++ /dev/null @@ -1,417 +0,0 @@ -import { ExchangeContract, IValidatorContract, IWalletContract } from '@0x/abi-gen-wrappers'; -import * as artifacts from '@0x/contract-artifacts'; -import { schemas } from '@0x/json-schemas'; -import { ECSignature, Order, SignatureType, SignedOrder, ValidatorSignature } from '@0x/types'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { assert } from './assert'; -import { eip712Utils } from './eip712_utils'; -import { orderHashUtils } from './order_hash'; -import { OrderError } from './types'; -import { utils } from './utils'; - -export const signatureUtils = { - /** - * 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. - */ - 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}`); - } - - 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); - return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); - } - - 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); - } - - default: - throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); - } - }, - /** - * 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(0, -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, - ); - 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. - */ - isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean { - assert.isHexString('data', data); - assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddress', signerAddress); - const normalizedSignerAddress = signerAddress.toLowerCase(); - - const 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)); - const normalizedRetrievedAddress = retrievedAddress.toLowerCase(); - return normalizedRetrievedAddress === normalizedSignerAddress; - } catch (err) { - return false; - } - }, - /** - * Signs an order and returns a SignedOrder. First `eth_signTypedData` is requested - * then a fallback to `eth_sign` if not available on the supplied provider. - * @param order The Order to sign. - * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address - * must be available via the supplied Provider. - * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. - */ - async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> { - assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); - try { - const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); - return signedOrder; - } catch (err) { - // HACK: We are unable to handle specific errors thrown since provider is not an object - // under our control. It could be Metamask Web3, Ethers, or any general RPC provider. - // We check for a user denying the signature request in a way that supports Metamask and - // Coinbase Wallet. Unfortunately for signers with a different error message, - // they will receive two signature requests. - if (err.message.includes('User denied message signature')) { - throw err; - } - const orderHash = orderHashUtils.getOrderHashHex(order); - const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress); - const signedOrder = { - ...order, - signature: signatureHex, - }; - return signedOrder; - } - }, - /** - * Signs an order using `eth_signTypedData` and returns a SignedOrder. - * @param order The Order to sign. - * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address - * must be available via the supplied Provider. - * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. - */ - async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> { - assert.isWeb3Provider('provider', provider); - assert.isETHAddressHex('signerAddress', signerAddress); - assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); - const web3Wrapper = new Web3Wrapper(provider); - await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); - const normalizedSignerAddress = signerAddress.toLowerCase(); - const typedData = eip712Utils.createOrderTypedData(order); - try { - const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); - const ecSignatureRSV = parseSignatureHexAsRSV(signature); - const signatureBuffer = Buffer.concat([ - ethUtil.toBuffer(ecSignatureRSV.v), - ethUtil.toBuffer(ecSignatureRSV.r), - ethUtil.toBuffer(ecSignatureRSV.s), - ethUtil.toBuffer(SignatureType.EIP712), - ]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - return { - ...order, - signature: signatureHex, - }; - } catch (err) { - // Detect if Metamask to transition users to the MetamaskSubprovider - if ((provider as any).isMetaMask) { - throw new Error(OrderError.InvalidMetamaskSigner); - } else { - throw err; - } - } - }, - /** - * Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type. - * @param msgHash Hex encoded message to sign. - * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address - * must be available via the supplied Provider. - * @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type. - */ - async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise<string> { - assert.isWeb3Provider('provider', provider); - assert.isHexString('msgHash', msgHash); - assert.isETHAddressHex('signerAddress', signerAddress); - const web3Wrapper = new Web3Wrapper(provider); - await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); - const normalizedSignerAddress = signerAddress.toLowerCase(); - const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash); - const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash); - - // 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. - // 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 ecSignatureRSV = parseSignatureHexAsRSV(signature); - if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = signatureUtils.isValidECSignature( - prefixedMsgHashHex, - ecSignatureRSV, - normalizedSignerAddress, - ); - if (isValidRSVSignature) { - const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureRSV); - return convertedSignatureHex; - } - } - const ecSignatureVRS = parseSignatureHexAsVRS(signature); - if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = signatureUtils.isValidECSignature( - prefixedMsgHashHex, - ecSignatureVRS, - normalizedSignerAddress, - ); - if (isValidVRSSignature) { - const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureVRS); - return convertedSignatureHex; - } - } - // Detect if Metamask to transition users to the MetamaskSubprovider - if ((provider as any).isMetaMask) { - throw new Error(OrderError.InvalidMetamaskSigner); - } else { - throw new Error(OrderError.InvalidSignature); - } - }, - /** - * Combines ECSignature with V,R,S and the EthSign signature type for use in 0x protocol - * @param ecSignature The ECSignature of the signed data - * @return Hex encoded string of signature (v,r,s) with Signature Type - */ - convertECSignatureToSignatureHex(ecSignature: ECSignature): string { - const signatureBuffer = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ethUtil.toBuffer(ecSignature.r), - ethUtil.toBuffer(ecSignature.s), - ]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign); - return signatureWithType; - }, - /** - * Combines the signature proof and the Signature Type. - * @param signature The hex encoded signature proof - * @param signatureType The signature type, i.e EthSign, Wallet etc. - * @return Hex encoded string of signature proof with Signature Type - */ - convertToSignatureWithType(signature: string, signatureType: SignatureType): string { - const signatureBuffer = Buffer.concat([ethUtil.toBuffer(signature), ethUtil.toBuffer(signatureType)]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - return signatureHex; - }, - /** - * Adds the relevant prefix to the message being signed. - * @param message Message to sign - * @return Prefixed message - */ - addSignedMessagePrefix(message: string): string { - assert.isString('message', message); - const msgBuff = ethUtil.toBuffer(message); - const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); - const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); - return prefixedMsgHex; - }, - /** - * Parse a 0x protocol hex-encoded signature string into its 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]; - 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 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 parseSignatureHexAsVRS(signatureHex: string): ECSignature { - const signatureBuffer = ethUtil.toBuffer(signatureHex); - let v = signatureBuffer[0]; - // HACK: Sometimes v is returned as [0, 1] and sometimes as [27, 28] - // If it is returned as [0, 1], add 27 to both so it becomes [27, 28] - const lowestValidV = 27; - const isProperlyFormattedV = v >= lowestValidV; - if (!isProperlyFormattedV) { - v += lowestValidV; - } - // signatureBuffer contains vrs - const vEndIndex = 1; - const rsIndex = 33; - const r = signatureBuffer.slice(vEndIndex, rsIndex); - const sEndIndex = 65; - const s = signatureBuffer.slice(rsIndex, sEndIndex); - const ecSignature: ECSignature = { - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }; - return ecSignature; -} - -function parseSignatureHexAsRSV(signatureHex: string): ECSignature { - const { v, r, s } = ethUtil.fromRpcSig(signatureHex); - const ecSignature: ECSignature = { - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }; - return ecSignature; -} |