diff --git a/src/utils/common.test.ts b/src/utils/common.test.ts new file mode 100644 index 00000000..ba583226 --- /dev/null +++ b/src/utils/common.test.ts @@ -0,0 +1,15 @@ +import { stripArrayTypeIfPresent } from './common'; + +describe('CommonUtils', () => { + describe('stripArrayTypeIfPresent', () => { + it('remove array brackets from the type if present', () => { + expect(stripArrayTypeIfPresent('string[]')).toBe('string'); + expect(stripArrayTypeIfPresent('string[5]')).toBe('string'); + }); + + it('return types which are not array without any change', () => { + expect(stripArrayTypeIfPresent('string')).toBe('string'); + expect(stripArrayTypeIfPresent('string []')).toBe('string []'); + }); + }); +}); diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 00000000..ae1b297c --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,12 @@ +/** + * Function to stripe array brackets if string defining the type has it. + * + * @param typeString - String defining type from which array brackets are required to be removed. + * @returns Parameter string with array brackets [] removed. + */ +export const stripArrayTypeIfPresent = (typeString: string) => { + if (typeString?.match(/\S\[\d*\]$/u) !== null) { + return typeString.replace(/\[\d*\]$/gu, '').trim(); + } + return typeString; +}; diff --git a/src/wallet.test.ts b/src/wallet.test.ts index 27cd1fa8..4fab72f2 100644 --- a/src/wallet.test.ts +++ b/src/wallet.test.ts @@ -626,6 +626,63 @@ describe('wallet', () => { '0x68dc980608bceb5f99f691e62c32caccaee05317309015e9454eba1a14c3cd4505d1dd098b8339801239c9bcaac3c4df95569dcf307108b92f68711379be14d81c', }); }); + + it('should throw if message does not have types defined', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV4 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV4 }), + ); + + const messageParams = getMsgParams(); + const payload = { + method: 'eth_signTypedData_v4', + params: [ + testAddresses[0], + JSON.stringify({ ...messageParams, types: undefined }), + ], + }; + + const promise = pify(engine.handle).call(engine, payload); + await expect(promise).rejects.toThrow('Invalid input.'); + }); + + it('should throw if type of primaryType is not defined', async () => { + const { engine } = createTestSetup(); + const getAccounts = async () => testAddresses.slice(); + const witnessedMsgParams: TypedMessageParams[] = []; + const processTypedMessageV4 = async (msgParams: TypedMessageParams) => { + witnessedMsgParams.push(msgParams); + // Assume testMsgSig is the expected signature result + return testMsgSig; + }; + + engine.push( + createWalletMiddleware({ getAccounts, processTypedMessageV4 }), + ); + + const messageParams = getMsgParams(); + const payload = { + method: 'eth_signTypedData_v4', + params: [ + testAddresses[0], + JSON.stringify({ + ...messageParams, + types: { ...messageParams.types, Permit: undefined }, + }), + ], + }; + + const promise = pify(engine.handle).call(engine, payload); + await expect(promise).rejects.toThrow('Invalid input.'); + }); }); describe('sign', () => { diff --git a/src/wallet.ts b/src/wallet.ts index e24f4086..5216a89a 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -13,6 +13,7 @@ import { } from '@metamask/utils'; import type { Block } from './types'; +import { stripArrayTypeIfPresent } from './utils/common'; import { normalizeTypedMessage, parseTypedMessage } from './utils/normalize'; /* @@ -243,6 +244,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware { const address = await validateAndNormalizeKeyholder(params[0], req); const message = normalizeTypedMessage(params[1]); + validatePrimaryType(message); validateVerifyingContract(message); const version = 'V3'; const msgParams: TypedMessageParams = { @@ -274,6 +276,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware { const address = await validateAndNormalizeKeyholder(params[0], req); const message = normalizeTypedMessage(params[1]); + validatePrimaryType(message); validateVerifyingContract(message); const version = 'V4'; const msgParams: TypedMessageParams = { @@ -457,6 +460,27 @@ WalletMiddlewareOptions): JsonRpcMiddleware { } } +/** + * Validates primary of typedSignMessage, to ensure that it's type definition is present in message. + * + * @param data - The data passed in typedSign request. + */ +function validatePrimaryType(data: string) { + const { primaryType, types } = parseTypedMessage(data); + if (!types) { + throw rpcErrors.invalidInput(); + } + + // Primary type can be an array. + const baseType = stripArrayTypeIfPresent(primaryType); + + // Return if the base type is not defined in the types + const baseTypeDefinitions = types[baseType]; + if (!baseTypeDefinitions) { + throw rpcErrors.invalidInput(); + } +} + /** * Validates verifyingContract of typedSignMessage. *