DESOSA 2022

MetaMask - From Vision to Architecture

Now that we have discussed MetaMask’s product vision and problem analysis in our previous essay, we continue to discuss the system’s architecture. Most importantly, we aim to explain how and why the MetaMask developers made the design decisions to come to the current product. We will zoom in throughout this essay, starting with the system context, all the way to how MetaMask has realized its key scenarios and quality attributes.

Context

MetaMask communicates with various external entities. First of all, we have a user who wants to use a decentralised web app (dApp). In order to do so, he needs to be signed into the MetaMask extension, which will then provide the dApp with the wallet functionalities. Naturally, these functionalities include calls to blockchain endpoints which is also done by MetaMask.

Figure: MetaMask System Context

Containers view

The main execution environment is a browser extension. So, we only have one container, as all the code of MetaMask is executed locally in the client’s browser. This is a logical choice, because this format allows MetaMask to easily inject functionality into the browser for the dApps to use. Running locally also means that privacy is enhanced.

Figure: MetaMask Containers

Main architectural style

MetaMask uses React, which is an open-source JavaScript framework to facilitate front-end web development. React allows you to define small UI components along with their functionality, and then use these small components to build larger UI components, like pages. Furthermore, React enforces a one-way data flow, from the parent containers to the individual UI components. Additionally, React facilitates maintaining and updating state across your application. As such, using React allows for a modular, reusable and testable front end.

Besides UI components, the MetaMask browser extension uses controllers. These controllers provide the non-UI logic of the extension. Usually, the functions of these controllers are used as handlers to external events. For example, when a user logs in, submits a transaction or changes its network. In short, we have two large groups of components; the UI and the controllers. In the next sections, we will provide concrete examples of components from both groups and how these components interact.

Components view

To showcase the type of components in the two different component groups we include a sample of pages of the extension from the UI group, and a sample of controllers from the controller group. In the Connectors section we dive deeper into the underlying mechanism that connects these two folders.

In the UI group we find components that show the user interface and handle interactions of the user. The actions are processed by the components in the controller group.

Figure: Sample of components from the UI folder, these are found in ui/pages/

Figure: Sample of components from the app folder, these are found in app/scripts/

Connectors view

Due to the confidential nature of this software project there is only a limited number of connectors. It is one browser extension, without other modules deployed, except for the browser extension itself.

There is one interesting connector though, namely the process that establishes the connection between the dApp (website) and the metamask wallet itself. The inpage.js is injected into the webpage of the decentralized app by the contentscript.js. It is not clear to us why this method is chosen. At face value it seems like a website should be able to establish a connection via some sort of protocol call, like how other apps open from a browser nowadays. It could have something to do with the security and signing of messages, but on the other hand banking apps are able to do this too.

Figure: MetaMask dApp injection script

Connector between UI and controllers

MetaMask has a standard way of connecting such aforementioned UI components and controller components. The UI activates the controllers through so-called actions, which make asynchronous calls to a background object using JavaScript promises.1 This background object represents the gateway to the controllers and employs individual controllers to execute particular logic and return information to the UI.

Development view

As the MetaMask application that we are reviewing is a browser extension, it is first and foremost a web application. It is written in React, so almost the entire codebase is JavaScript (about 94% to be exact).2 However, this number is slightly misleading, as React files contain both JavaScript and HTML/CSS, and this is called JSX.3 The file and folder structure of the codebase, therefore, is also quite consistent with many other large web applications, though this structure is not enforced by React.4 Looking at the root directory of the project, there are several folders that stand out:

  • app: This folder is loaded by the browser to bootstrap the extension popup and functionality. The aforementioned controllers reside in this folder. It also serves as the static resource folder, thus it contains fonts, images, vendor scripts and most importantly, the extension manifests. These manifests define how the extension should be loaded, and with which permissions. These options are browser dependent, hence there are some JSON files for every browser supported.
  • development: The development folder contains files that are needed only while developing the extension. Hence, the grunt of this folder contains scripts that are required and invoked during the multiple build phases that are defined.
  • shared: The shared folder contains files that are required in the multiple containers and components of the extension. A great example of files and definitions that are required throughout multiple components are the constants, which are thus defined in the shared folder. Secondly, this folder contains lots of utility scripts that are used throughout the whole app. Per example the gas.utils.js file, which contains some functions to calculate gas fees and convert them with respect to different currencies.
  • ui: Compared to the app folder, this folder contains purely React JSX code. It is, therefore browser independent. In this folder, the React application is fully defined. It contains many of the elements which define the look of the app. For example, it has a components folder that defines react components (not components from the components view) such as a Button. A developer can use these components without having to think about the theme and styling of the app too much, as that is defined within these components already.
  • test: A very important aspect of writing software is testing it. This is especially true for software that handles large sums of money such as MetaMask. The test folder defines a multitude of tests, ranging from unit tests to full end-to-end and integration tests. All these tests can be run on the developer’s machine without too much hassle.

MetaMask development environment

An interesting aspect of the MetaMask development environment as a whole, not just the browser extension, is the fact that a lot of components that are used in the browser extension originate from other tiny repositories in the MetaMask organization.5 Take the KeyringController repository6 for example. This repository serves the very important task of storing private keys in a secure way. The fact that this is done in a separate repository that is then included in the final products, such as the browser extension, ensures that it is easy to check changes to security-related aspects of the products. This in turn is helpful for one of the key attributes of MetaMask, the security of a users’ private keys.

Key scenarios in Metamask

Most modern browsers (except Safari) implement the WebExtension API.7 Because of this, Metamask is cross-compatible with Brave, Chrome, Firefox and Opera, making the development process much easier.

On browser startup, Metamask loads the “background.html” page, which starts all background processes required for it to function properly. For example, it injects the metamaskProvider in every webpage, like we have seen in the connectors view. This way, webpages can interface with the extension.

Once a web app wants to interact with the extension, a function in the background.js file calls triggerUi(), which opens up the Metamask app. As discussed above, this app is simply a React application which is loaded in an extension container.

One of the core functionalities of course is interacting with the blockchain. Once Metamask has signed a transaction, it sends it to the RPC address of a given blockchain, which in turn will spread it to nodes in the network. Reading data from the blockchain happens through one of the various block explorers, such as etherscan.8 Using this block explorer, it checks whether the transaction has succeeded or failed. In the runtime view below, you can see one of the most critical operations in Metamask; sending a transaction.

Figure: Runtime view of sending a transaction in MetaMask

Key quality attributes

The most critical element of Metamask’s architecture is security. To function as a ‘signer’ of messages, the wallet must store the private keys of the user. Some of the requirements for this are;

  • Data must be recoverable based on a seed phrase
  • Data must be encrypted with a master password
  • Data must be isolated from external sources
  • Metamask uses its own keyring protocol to achieve the above. This post explains how this mechanism exactly works in-depth. Below, a visual representation is shown of the MetaMask keyring, which is taken from the same aforementioned post.9

Figure: Visual reference of a keyring structure, from wispwisp.com

Metamask stores a ‘vault’ on disk which stores all added encrypted keyrings. By default, Metamask creates a ‘HD Key Tree’ keyring which stores your local accounts. HD stands for hierarchical deterministic and is nothing more than a tree of nodes with public/private keys, which is super useful when you want to manage multiple accounts with a single private key. Metamask allows adding keyrings from vendors that most importantly allow users to incorporate hardware wallets into the browser extension.

The HD Keyring stores the private keys (encrypted) in the browser which is thus an attack vector for adversaries. The code can be found here. When a user unlocks the extension by entering its password, the HD Keyring is decrypted and stored in-memory in the state called ‘vault.memStore’. This object contains the full private keys of every connected account, so it is essential that this is not exposed to any external software.

Dependency risk

As with any open source project, relying on external dependencies brings a certain amount of risk to the table. Metamask relies on over 270 external packages, which is a serious problem if any single project decides to step into the dark side. Since the biggest risk is a package reading / interfering with the wallet credentials, Metamask attempts to prevent this by using lavamoat.10 This tool is developed for preventing supply chain attacks, which is exactly what we are talking about. Among other things, it prevents packages from changing primitives which would be a way to interfere with a downstream package.

API design principles applied

Due to the decentralized nature of MetaMask and their vision to make a permissionless system, there is no communication with a centralized server. Most of the data flow stays within the browser extension, and as such, this is already explained in the sections regarding components and connectors. There is, of course, a link between the extension and the various chains, but this is handled by the RPC calls, which is an external source. There is no standard ‘API Design Principle’ which could be explained, as this application is not really a standard client/server app.

Conclusion

The goal of this essay was to give you insight into how the world’s most used cryptowallet functions behind the scenes. If you follow any of the links in this post, you will be able to dive deeper into the world of web extensions and crypto security.


  1. Javascript Promises, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise ↩︎

  2. MetaMask Browser Extension GitHub, https://github.com/MetaMask/metamask-extension ↩︎

  3. React JS, https://reactjs.org/docs/introducing-jsx.html ↩︎

  4. React JS, https://reactjs.org/docs/faq-structure.html ↩︎

  5. MetaMask GitHub Organisation, https://github.com/MetaMask ↩︎

  6. MetaMask KeyringController repository, https://github.com/MetaMask/KeyringController ↩︎

  7. Mozilla Web Extensions API Docs, https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API ↩︎

  8. EtherScan, https://etherscan.io/ ↩︎

  9. WispWisp, https://www.wispwisp.com/index.php/2020/12/25/how-metamask-stores-your-wallet-secret/ ↩︎

  10. LavaMoat, https://github.com/LavaMoat/LavaMoat ↩︎

Metamask Browser Extension
Authors
Pieter Tolsma
Jurriaan Den Toonder
Francis Behnen
Jesse Harte