Skip to content

Commit

Permalink
Tx verification updates (#25084)
Browse files Browse the repository at this point in the history
Commits are individually reviewable.
  • Loading branch information
rekmarks authored Jun 5, 2024
1 parent 667ecb9 commit b224935
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 42 deletions.
128 changes: 89 additions & 39 deletions app/scripts/lib/tx-verification/tx-verification-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,104 @@
import { hashMessage } from '@ethersproject/hash';
import { verifyMessage } from '@ethersproject/wallet';
import type { NetworkController } from '@metamask/network-controller';
import { rpcErrors } from '@metamask/rpc-errors';
import { Json, JsonRpcParams, hasProperty, isObject } from '@metamask/utils';
import {
Json,
JsonRpcParams,
JsonRpcRequest,
JsonRpcResponse,
} from '@metamask/utils';
import {
JsonRpcEngineEndCallback,
JsonRpcEngineNextCallback,
} from 'json-rpc-engine';
import { SIG_LEN, TRUSTED_BRIDGE_SIGNER } from '../../../../shared/constants/bridge';
import { FIRST_PARTY_CONTRACT_NAMES } from '../../../../shared/constants/first-party-contracts';

export function txVerificationMiddleware(
req: JsonRpcRequest<JsonRpcParams>,
_res: JsonRpcResponse<Json>,
next: JsonRpcEngineNextCallback,
end: JsonRpcEngineEndCallback,
type TxParams = {
chainId?: `0x${string}`;
data: string;
from: string;
to: string;
value: string;
};

/**
* Creates a middleware function that verifies bridge transactions from the
* Portfolio.
*
* @param networkController - The network controller instance.
* @returns The middleware function.
*/
export function createTxVerificationMiddleware(
networkController: NetworkController,
) {
// ignore if not sendTransaction and if the params not an array
if (req.method !== 'eth_sendTransaction' || !Array.isArray(req.params)) {
return function txVerificationMiddleware(
req: JsonRpcRequest<JsonRpcParams>,
_res: JsonRpcResponse<Json>,
next: JsonRpcEngineNextCallback,
end: JsonRpcEngineEndCallback,
) {
if (
req.method !== 'eth_sendTransaction' ||
!Array.isArray(req.params) ||
!isValidParams(req.params)
) {
return next();
}

// the tx object is the first element
const params = req.params[0];

const chainId =
typeof params.chainId === 'string'
? (params.chainId.toLowerCase() as `0x${string}`)
: networkController.state.providerConfig.chainId;

// if the recipient address is not the bridge contract, skip verification
if (
params.to.toLowerCase() !==
FIRST_PARTY_CONTRACT_NAMES['MetaMask Bridge'][chainId].toLowerCase()
) {
return next();
}

const paramsToVerify = {
to: hashMessage(params.to.toLowerCase()),
from: hashMessage(params.from.toLowerCase()),
data: hashMessage(
params.data.toLowerCase().substring(0, params.data.length - SIG_LEN),
),
value: hashMessage(params.value.toLowerCase()),
};
const h = hashMessage(JSON.stringify(paramsToVerify));

// signature is 130 chars in length at the end
const signature = `0x${params.data.substring(-SIG_LEN)}`;
const addressToVerify = verifyMessage(h, signature);

if (addressToVerify.toLowerCase() !== TRUSTED_BRIDGE_SIGNER.toLowerCase()) {
return end(
rpcErrors.invalidParams('Invalid bridge transaction signature.'),
);
}
return next();
}

// 0 tx object is the first element
const params = req.params[0];
const paramsToVerify = {
to: hashMessage(params.to.toLowerCase()),
from: hashMessage(params.from.toLowerCase()),
data: hashMessage(
// strip signature from data
params.data.toLowerCase().substr(0, params.data.length - SIG_LEN),
),
value: hashMessage(params.value.toLowerCase()),
};
const h = hashMessage(JSON.stringify(paramsToVerify));
// signature is 130 chars in length at the end
const signature = `0x${params.data.substr(-SIG_LEN)}`;
const addressToVerify = verifyMessage(h, signature);
const canSubmit =
params.to.toLowerCase() ===
FIRST_PARTY_CONTRACT_NAMES['MetaMask Bridge'][params.chainId].toLowerCase()
? addressToVerify.toLowerCase() === TRUSTED_BRIDGE_SIGNER.toLowerCase()
: true;

if (!canSubmit) {
end(new Error('Validation Error'));
}

// successful validation
return next();
}

/**
* Checks if the params of a JSON-RPC request are valid `eth_sendTransaction`
* params.
*
* @param params - The params to validate.
* @returns Whether the params are valid.
*/
function isValidParams(params: Json[]): params is [TxParams] {
return (
isObject(params[0]) &&
(!hasProperty(params[0], 'chainId') ||
(typeof params[0].chainId === 'string' &&
params[0].chainId.startsWith('0x'))) &&
typeof params[0].data === 'string' &&
typeof params[0].from === 'string' &&
typeof params[0].to === 'string' &&
typeof params[0].value === 'string'
);
}
7 changes: 4 additions & 3 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ import {
getSmartTransactionsOptInStatus,
getCurrentChainSupportsSmartTransactions,
} from '../../shared/modules/selectors';
import { BaseUrl } from '../../shared/constants/urls';
import {
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
handleMMITransactionUpdate,
Expand Down Expand Up @@ -329,7 +330,7 @@ import AuthenticationController from './controllers/authentication/authenticatio
import UserStorageController from './controllers/user-storage/user-storage-controller';
import { PushPlatformNotificationsController } from './controllers/push-platform-notifications/push-platform-notifications';
import { MetamaskNotificationsController } from './controllers/metamask-notifications/metamask-notifications';
import { txVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware';
import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware';
import { updateSecurityAlertResponse } from './lib/ppom/ppom-util';

export const METAMASK_CONTROLLER_EVENTS = {
Expand Down Expand Up @@ -5119,8 +5120,8 @@ export default class MetamaskController extends EventEmitter {
engine.push(createLoggerMiddleware({ origin }));
engine.push(this.permissionLogController.createMiddleware());

if (origin === 'https://portfolio.metamask.io' || 'http://localhost:3000') {
engine.push(txVerificationMiddleware);
if (origin === BaseUrl.Portfolio) {
engine.push(createTxVerificationMiddleware(this.networkController));
}

///: BEGIN:ONLY_INCLUDE_IF(blockaid)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@
"@metamask/providers": "^14.0.2",
"@metamask/queued-request-controller": "^0.10.0",
"@metamask/rate-limit-controller": "^5.0.1",
"@metamask/rpc-errors": "^6.2.1",
"@metamask/safe-event-emitter": "^3.1.1",
"@metamask/scure-bip39": "^2.0.3",
"@metamask/selected-network-controller": "^13.0.0",
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24927,6 +24927,7 @@ __metadata:
"@metamask/providers": "npm:^14.0.2"
"@metamask/queued-request-controller": "npm:^0.10.0"
"@metamask/rate-limit-controller": "npm:^5.0.1"
"@metamask/rpc-errors": "npm:^6.2.1"
"@metamask/safe-event-emitter": "npm:^3.1.1"
"@metamask/scure-bip39": "npm:^2.0.3"
"@metamask/selected-network-controller": "npm:^13.0.0"
Expand Down

0 comments on commit b224935

Please sign in to comment.