If you own cryptocurrency, you are among an estimated 8% of the US population who does. Chances are that you keep your coins in a software wallet, either at an exchange like Coinbase or using one of the many open source options available like Ethereum Wallet or Bitcoin Core. Some of you may have taken the next step and bought a hardware wallet to secure your keys.

The Trezor One, Ledger Nano S, and KeepKey are among the most popular hardware wallets on the market today. While they each have distinct advantages in terms of their architectures and the currencies they support, they all serve to store your private keys securely. Hardware wallets allow you to sign transactions directly on the device so that your keys are never exposed outside of the device. The advantage of this approach is that even if a hacker compromises your desktop system, they will not be able to get access to your crypto stash or interfere with your crypto transactions.

I recently bought a Trezor One. After transferring some currency to the wallet, however, I quickly realized there was little fun to be had with these devices apart from the initial currency transfer. There’s no point in carrying these wallets around town with you, because cryptocurrency infrastructure is pretty much non-existent at this time. So, hardware wallets sit unused on shelves or in a safes, quietly protecting keys and coins from harm.

But that’s boring. What else could we do with all these hardware wallets? How about some gaming! Given all that crypto has taken from the gaming community — driving up GPU prices by hoarding for mining rigs — it’s time crypto gave something back.

Ladies and gentlemen, I present to you an ageless classic running on the Trezor One: Pong.

While the display is rather small and there are only two buttons, The Trezor One is still a fun little device for learning how to program embedded devices. The processor is the STMicro STM32F205, which has an ARM 32-bit Cortex-M3 CPU. There’s about 1MB of flash storage available and 128KB of RAM. The device has a 128×64 bit OLED display and two buttons.

If you’d like to write your own firmware for the Trezor One, here are some instructions to get you started.

Setting up the Build Environment

These instructions assume you’re running Ubuntu 18.04 LTS, but with a little tweaking they’ll work on any box. These steps work against version v1.6.2 of the firmware.

Get the Trezor Source Code

First step is to download the Trezor source code.

$ mkdir workspace
$ cd workspace
$ git clone https://github.com/trezor/trezor-mcu.git

Install the Dependencies

Next, you’ll need to install all the dependencies required to build.

sudo apt install build-essential python python-pip libsdl2-dev

Download and extract the GNU ARM Embedded Toolchain.

$ cd workspace
$ tar -xjf ~/Downloads/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2
$ echo "export PATH=~/workspace/gcc-arm-none-eabi-7-2018-q2-update/bin:$PATH" >> ~/.bashrc
$ source ~/.bashrc

You’ll also need to install Google Protocol Buffers. Unfortunately, the packages provided by Ubuntu are too outdated, so you’ll need to get the latest version on your own here.

$ cd workspace
$ mkdir protoc
$ cd protoc
$ wget https://github.com/google/protobuf/releases/download/v3.6.0/protoc-3.6.0-linux-x86_64.zip
$ unzip protoc-3.6.0-linux-x86_64.zip
$ echo "export PATH=~/workspace/protoc/bin:$PATH" >> ~/.bashrc
$ source ~/.bashrc

Finally, install a few Python modules.

$ pip install protobuf ecdsa

Build the Firmware

Now that you’ve got all the dependencies installed, you’re ready to build the firmware. To build the firmware:

$ cd ~/workspace/trezor-mcu
$ ./script/setup
$ ./script/cibuild

The freshly built image will be stored at firmware/trezor.bin. We’ll discuss what to do with that image a little later on.

Trezor Emulator

If you plan to create your own Trezor firmware, chances are that you won’t get it right the first time you try it out. To shorten the modify code/build/load cycle, you can use the Trezor emulator to test out your changes without having to actually load the firmware on the device.

Here’s a screenshot of the emulator running. The 128×64 OLED display appears in its own window. The buttons are simulated by Left-Arrow and Right-Arrow key presses.

Trezor One Emulator
Trezor One Emulator

To build the emulator, you need to set some environment variables and run the setup script again to clear out any previous build artifacts.

$ cd ~/workspace/trezor-mcu
$ export EMULATOR=1 TREZOR_TRANSPORT_V1=1
$ ./script/setup
$ ./script/cibuild

The emulator executable shows up in the firmware directory and can be run like this:

$ ./firmware/trezor.elf

Customizing the Firmware

Okay, now you’re all setup to build the device firmware and the emulator, so you’re ready to create your own Trezor firmware!

The entry point into the Trezor app firmware is found in the main function in firmware/trezor.c. You can simply delete everything after the initialization in that function and start writing your own code.

Drawing to the Display

One of the first things you’ll want to do is draw to the display. The Trezor display is a 128×64 OLED, where the pixel (0,0) is at the top left corner of the screen. The display chip hardware is accessible via the SPI bus (see oled.c), but you won’t need to interact directly with the hardware. The Trezor firmware provides a simple API in oled.h for drawing and printing to the display.

The display API is contained in oled.h and is very straightforward.

#define OLED_WIDTH   128
#define OLED_HEIGHT 64
#define OLED_BUFSIZE (OLED_WIDTH * OLED_HEIGHT / 8) void oledInit(void);
void oledClear(void);
void oledRefresh(void);
void oledSetDebugLink(bool set);
void oledInvertDebugLink(void);
void oledSetBuffer(uint8_t *buf);
const uint8_t *oledGetBuffer(void);
void oledDrawPixel(int x, int y);
void oledClearPixel(int x, int y);
void oledInvertPixel(int x, int y);
void oledDrawChar(int x, int y, char c, int zoom);
int oledStringWidth(const char *text, int font);
void oledDrawString(int x, int y, const char* text, int font);
void oledDrawStringCenter(int y, const char* text, int font);
void oledDrawStringRight(int x, int y, const char* text, int font);
void oledDrawBitmap(int x, int y, const BITMAP *bmp);
void oledInvert(int x1, int y1, int x2, int y2);
void oledBox(int x1, int y1, int x2, int y2, bool set);
void oledHLine(int y);
void oledFrame(int x1, int y1, int x2, int y2);
void oledSwipeLeft(void);
void oledSwipeRight(void);

For example, to draw a pixel at a certain position (x,y):

oledDrawPixel(x,y);

Drawing bitmaps is simple too once you understand the format of the data required. The oledDrawBitmap function takes a pointer to a BITMAP structure, which looks like this:

typedef struct {
uint8_t width, height;
const uint8_t *data;
} BITMAP;

The “width” and “height” fields specify the dimensions of the image. The “data” array consists of the pixel data starting with the upper left corner of the image. Each byte represents 8 pixels with the least significant bit position corresponding to the leftmost pixel (i.e., little endian). The bit value 1 means white and 0 means black. If you want to display standard Windows bitmap files, you’ll have to do two things.

First, you’ll need to scale the image down to the 128×64 size display. You can do that with an image editor like Gimp, or even more easily with the convert utility on Linux:

convert title.bmp -resize 128x64 title_resize.bmp

The second step is to convert the bitmap file to a C array usable by the Trezor display API. I wrote a script to convert standard Windows bitmap files to the C array format used by the Trezor API. The input bitmap must be in 24-bit mode, which means that each pixel is stored as three bytes, one byte for each color (red, green, and blue). The script converts the middle byte (green) to either 0 or 1 since the Trezor display is black and white. You can find the Python script called bmp2trz.py here.

Converting Bitmap for Trezor API

Reading Buttons

The Trezor has two buttons which are referred to as “Yes” and “No”. The button API is exposed in buttons.h. There is a global variable holding the button state called “button”, and this structure has the following fields:

struct buttonState {
volatile bool YesUp;
volatile int YesDown;
volatile bool NoUp;
volatile int NoDown;
};

There are two ways to read the button state. You can either check to see if the button is currently held down, or you can check to see if the button was pressed and released.

Before reading from the global button variable, first call “buttonUpdate” to update the state. Then you can simply test the global button variable fields directly. For example, here’s some example code to wait indefinitely for either button to be pressed:

do
{
delay(100000);
buttonUpdate();
} while (!button.YesUp && !button.NoUp);

Installing the Firmware

To load your custom firmware onto the device, you first need to get the device into bootloader mode. Bootloader mode is entered by pressing and holding both buttons down as you plug in the Trezor to the USB port. When you’re in bootloader mode, the screen appears like this:

Trezor One in Bootloader Mode
Trezor One in Bootloader Mode

With the Trezor in bootloader mode, you can upload your custom firmware image using the trezorctl utility. To install the trezorctl utility, you’ll need to install the following:

$ sudo apt-get install python3-dev python3-pip cython3 libusb-1.0-0-dev libudev-dev
$ pip3 install --upgrade setuptools
$ pip3 install trezor[hidapi]

Then you can install your firmware as shown below.

Be careful at this step! This operation will completely erase the image along with any keys you have setup on the device. Do not proceed if you have significant value stored in your wallet and you are unsure of what you are doing. I recommend you double check that you have your seed words safely stored on paper before loading new firmware onto the device if you actually have any currency stored on your wallet.

$ trezorctl firmware_update -f firmware/trezor.binFirmware fingerprint: f5392f9b390b6381bfaa24a39a7ed0f7eba2e9cbfa21b9c224ce500b38918766Please confirm action on device...

You’ll have to acknowledge that you want to go ahead and erase the current image and load a new firmware image onto the device.

Here’s a video showing the upgrade process.

To be clear, during the upgrade process and on every subsequent boot thereafter, the bootloader detects that the upgrade image is not signed by Satoshi Labs and prompts the user if they would like to proceed with the unsigned firmware.

If you would like to get Pong running on your Trezor, you don’t have to implement the game from scratch if you don’t want to. There’s a fairly simple implementation on github by user flightcrank which you can find here. I already ported that version to the Trezor with minimal changes, so if you’d like to start with my port of flightcrank’s Pong implementation to the Trezor One, you can find that code here.

Pong Start Screen

Happy coding!