Skip to content

Commit

Permalink
fix: Add validation for types sign message primary type (#350)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpuri authored Dec 18, 2024
1 parent 4de470d commit 126626d
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/utils/common.test.ts
Original file line number Diff line number Diff line change
@@ -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 []');
});
});
});
12 changes: 12 additions & 0 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -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;
};
57 changes: 57 additions & 0 deletions src/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
24 changes: 24 additions & 0 deletions src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@metamask/utils';

import type { Block } from './types';
import { stripArrayTypeIfPresent } from './utils/common';
import { normalizeTypedMessage, parseTypedMessage } from './utils/normalize';

/*
Expand Down Expand Up @@ -243,6 +244,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware<any, Block> {

const address = await validateAndNormalizeKeyholder(params[0], req);
const message = normalizeTypedMessage(params[1]);
validatePrimaryType(message);
validateVerifyingContract(message);
const version = 'V3';
const msgParams: TypedMessageParams = {
Expand Down Expand Up @@ -274,6 +276,7 @@ WalletMiddlewareOptions): JsonRpcMiddleware<any, Block> {

const address = await validateAndNormalizeKeyholder(params[0], req);
const message = normalizeTypedMessage(params[1]);
validatePrimaryType(message);
validateVerifyingContract(message);
const version = 'V4';
const msgParams: TypedMessageParams = {
Expand Down Expand Up @@ -457,6 +460,27 @@ WalletMiddlewareOptions): JsonRpcMiddleware<any, Block> {
}
}

/**
* 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.
*
Expand Down

0 comments on commit 126626d

Please sign in to comment.