Create A Bitcoin Hardware Wallet With Golang And A Raspberry Pi Zero

Over the past month or so, in my free time, I’ve been working towards creating an affordable hardware wallet for various cryptocurrencies such as Bitcoin. Right now many cryptocurrency enthusiasts are using the Ledger Nano S hardware wallet, but those are very expensive and rarely in supply.

I own several Raspberry Pi Zero and thought it would be a perfect opportunity to take what I know about Raspberry Pi and Golang to create a wallet for a fraction of the price as the industry leading wallets.

We’re going to see how to create a hardware wallet, which I’m calling the Open Ledger Micro, using Golang and a Raspberry Pi Zero.

Disclaimer: I am a developer and Bitcoin enthusiast and not a cryptocurrency or cryptography expert. There may be bugs in my logic or code. I advise you to use this tutorial at your own risk. If you’ve found a bug or think you can improve on something, share it in the comments.

The Project Goals

Before we get into the code, we should probably figure out what we’re going to build. We are going to build an application with Go and Angular to be installed on a Raspberry Pi Zero. It will look something like the following animated image.

The point of hardware wallets is that they hold encrypted private keys, never expose the private keys, and operate in an offline environment. We are using a Raspberry Pi Zero because it has neither WiFi or Bluetooth which makes it much more difficult to compromise.

As seen in my previous tutorial, Connect to a Raspberry Pi Zero with a USB Cable and SSH, I demonstrated how to emulate ethernet over USB. We’re going to do this to serve a RESTful API over USB so that only the host computer can access the data. The Go application will serve an API and the Angular application will be a nice front-end. We’ll never need to connect to the Linux OS in this example.

Creating a RESTful API with Go and a Multiplexer

The Go side of things will be doing all of the heavy lifting. We’ll be managing keys, encrypting our data, signing transactions, and doing this entirely through a RESTful API.

Defining the Project Files and Downloading the Dependencies

We need to create a fresh Go project somewhere within our $GOPATH. I’m going to be referencing open-ledger-micro as a project within my $GOPATH.

The project should have the following structure:

main.go
transaction.go
wallet.go
coin.go

You could create tests, but we’re going to skip them in this particular tutorial. There are a few package dependencies that we need to obtain to make our lives just a little easier.

From the command line, execute the following:

go get github.com/GeertJohan/go.rice
go get github.com/gorilla/handlers
go get github.com/gorilla/mux
go get github.com/btcsuite/btcutil
go get github.com/btcsuite/btcd

The gorilla/handlers package isn’t absolutely necessary, but it will help when testing locally because we can manage cross-origin resource sharing (CORS) with it. You can read more about CORS and Golang in a previous article I wrote since it will not be emphasized here.

For any given coin, we know exactly what information is necessary. We’re going to want to know the name of the coin, the symbol, the private key in WIF format, and the public address in both compressed and uncompressed format so that way we don’t have to continuously generate it. This information is summed up in the Coin data structure.

We won’t be able to generate or even manage coins without knowing the network information behind the coin. For this reason, we need to maintain some network parameters as defined in the Network data structure. The xpubkey value is the byte prefix for public keys and the xprivatekey value is the byte prefix for private keys. The magic value is the information about the actual network.

Given a particular network, we can generate a new private key and store it in wallet import format (WIF). Using the WIF key, we can generate uncompressed and compressed public addresses. We’re storing both because we don’t know what a user might want to use.

Given a WIF key, we try to decode it. If the key is formatted incorrectly, we’ll throw an error. If the key is correctly formatted, we need to validate it for a given network. If the key is valid for a particular network, generate the uncompressed and compressed public addresses.

Encrypting and Decrypting Wallet Data with AES Ciphers

Now that we can create coins, we need to be able to store them in a wallet as ciphertext. If the Raspberry Pi Zero is stolen, we don’t want to make it easy for people to retrieve your private keys.

The plan is to have a wallet with any number of coins. When interacting with the wallet, it will be stored on disk and encrypted with a passphrase or retrieved from disk and decrypted with a passphrase. For simplicity, we’re going to manage our wallet in a key-value fashion where encrypted JSON exists in a file rather than trying to implement some other database like SQLite.

There is more to the wallet code, but our focus for now is on the encryption and decryption side of things.

Notice that we have a Wallet data structure that maintains a slice of the Coin data structure that we had created previously. We’re just planning to keep track of our coin data, nothing fancy.

When it comes to ciphers in Golang, we can’t just use any passphrase as our key. The key must be of a certain byte length which is more trouble than it is worth to try to match in a passphrase. Instead, we can actually hash a passphrase using an algorithm that produces a hash of the appropriate length.

In the encryption function, we are creating a new AES cipher from our hashed passphrase. An AES cipher requires a nonce to be used and since we have no intention of storing it as a separate entity on our disk, we’re going to store it prefixed to our ciphertext data. The decryption process requires the same nonce used in encryption, so as long as we know the size of the nonce, we can extract it from the prefix.

Take the Decrypt function now that we have a slice of bytes that represent our ciphertext data:

Notice that the setup of our decryption function is nearly the same as our encryption function. The difference is that instead of creating a nonce and prefixing it, we’re separating the nonce and ciphertext, then decrypting it with our hashed passphrase.

Instead of returning the plaintext data, we’re marshaling it into our wallet so that it can be used seamlessly throughout the project.

With the core encryption and decryption functionality implemented, we need to take it a step further and take it to disk.

When we wish to encrypt our wallet, we are creating and opening a file called wallet.dat that exists on the disk of the Raspberry Pi Zero. The standard Encrypt function is called and we take the slice of byte that is returned and we write it to the opened file. When we’re done, we close the file and move on with our day.

In the above function, we are reading from a wallet.dat file on disk and storing the data in a slice of byte. Our Decrypt function takes a slice of byte which we can decrypt to plaintext using a passphrase. The plaintext is marshaled into the wallet for later use.

Essentially, we’re trying to decrypt a wallet. If there are errors in the decryption process, either the wallet doesn’t exist, or the passphrase is incorrect. We can use this function when trying to sign in via the Angular application.

Assuming that we’ve decrypted our wallet somewhere, we can try to import a coin into it:

Remember the content we added to the coin.go file? It doesn’t matter if this is a coin we’re importing or a coin we’re creating, it will be imported into the decrypted wallet and then the wallet will be encrypted and saved on disk.

If we want to get the addresses that exist in our wallet, we can call the GetAddresses function:

What we’re doing is decrypting our wallet, but removing all the WIF information. We’re not encrypting so our WIF information is safe, but we’re not returning it. Remember, if you want a truly secure hardware wallet, the WIF information should never be exposed or leave the Raspberry Pi.

To go everything I just said, if you really want to retrieve everything, you could create a Dump function:

Use the Dump function with care if it exists. You wouldn’t want your private keys to be exposed by accident.

Create and Sign Transactions for Broadcasting on the Blockchain

Now that we can interact with our wallet and coins safely, we should probably worry about creating and signing transactions that can later be broadcasted to the blockchain. While not lengthy, transactions are probably the most complicated part of this tutorial.

Create an origin transaction for the sender which represents UTXO data being sent.

Create a redeem transaction that includes information from the origin transaction.

Sign the redeem transaction so funds can truly be transferred.

Validate the signed transaction for errors before it can be broadcasted.

We do not plan to broadcast the transaction from our hardware wallet. If you wish to broadcast the transaction, copy and paste the signed output and take it to a blockchain explorer to broadcast. For example, BitPay has a popular explorer for Bitcoin.

Not only are we planning to create a transaction, but we need something to store it in. This is useful for returning data back to the client in a formatted fashion, but it could also be useful for storing on the disk in the future. What is truly important to us is the SignedTx property in the Transaction data structure.

Using the source and destination public addresses we can create transaction output. The output for our sender transaction will use the public key script for the sender as well as the amount in satoshi that are available from the previous transaction hash. With the previous transaction hash, the amount, and the sender’s public key script, we can finalize our sending transaction which will be used in the redeem transaction.

A few more things to note about the transaction logic that we’ve implemented. First, we’re using a single input. This means that the value being sent must exist in full for a particular UTXO rather than chaining several UTXO inputs. Second, we’ve not implemented a change address output, meaning we must send everything in our input, otherwise the remainder is used for the mining fee. Third, our input amount and output amount are the same, meaning zero allocation for fees. You may want to add some logic to use around 667 satoshi as the fee.

Like I said previously, if you wanted more depth to how transactions work in Golang, check out my previous article on the subject. Transactions are tricky and it is in my opinion the most difficult part about cryptocurrencies.

Developing and Serving API Endpoints with Gorilla Mux

We now have all of our driving Golang logic. We only have to see it in action now, through the serving of RESTful API endpoints. We can create an API using gorilla/mux that will call each of the wallet functions that we had created.

We find ourself in a tricky position. We are going to have an API and a front-end all from the same application. This means that we need to use a sub-router to distinguish what is API and what is front-end.

Our sub-router will be the API and all endpoints will be prefixed with /api. The root endpoint will be our Angular application. We’ll be bundling it into the binary later in the tutorial, so don’t think too hard on what our commands are doing yet. The Angular application will have its own routing.

We’ll go through the flow of events a typical user might do with our application.

To start, we don’t have a wallet yet. It needs to be created, so we would probably hit the /api/wallet endpoint with a POST request:

When the CreateWalletEndpoint function is called, we set the response type and we check the request for query parameters. A key must exist in the query parameters and it will be used to create a wallet using the previous Create method. The empty wallet will be encoded and sent in the response.

If we ever needed to destroy our wallet, we could do something similar:

The request requires that a key exists as a query parameter and that symbol exists in the request body. The symbol property tells us what kind of key pair we want to generate based on our list of networks. After the coin is generated, the wallet is decrypted, the key is imported, and the coin is returned in the response. For safety reasons, you may want to remove the WIF from the coin before encoding and returning it back to the user.

The ImportCoinEndpoint function behaves similar to the CreateCoinEndpoint function:

Not only are we expecting a symbol to be in the request body, but we’re expecting a wif property to be in the request body. With that information, we can decrypt the wallet, import the coin, and return the coin as a response. Remember, you probably want to remove the WIF string from the response.

We had previously created an Authenticate function, so we need to finish things off with a simple endpoint:

Given a key in the query parameters, we check to see if it is valid and return a boolean response back to the user. If you’d like, you can harden this endpoint to restrict attempts and prevent brute force password attacks.

Let’s say that as a user, we’ve signed into the wallet and wish to see all of our addresses. We would call the GetAddressesEndpoint like so:

Creating transactions in an endpoint is just as complicated as the underlying function code that we previously saw. In a request, we’re expecting a key in the query parameters as well as a txid, source_address, destination_address, and amount in the request body. Once we’ve decrypted the wallet we can search for the source address and obtain the corresponding private key. Remember, we don’t want to expose the private key to the user after it has been added to our wallet.

After we’ve created and signed a transaction, we can return it in a response.

Our last two endpoints are optional and should be used with care. Let’s say we wanted to dump all of our wallet data in plain text, we could create a DumpWalletEndpoint like so:

If the key is correct, the entire wallet, including WIF keys are returned to the client. Probably good for debugging, but bad for production. Similarly, if we ever wish to backup our encrypted wallet, we might have an ExportWalletEndpoint like so:

The above endpoint behaves a bit different. Instead of returning JSON data, we are returning binary data that will be downloaded. The wallet will be encrypted, however, if someone has your encrypted wallet file, they could potentially break into it with the correct skills and willpower.

Technically, if you wanted to, you could test this Golang application on your host machine. Just comment the line that refers to the Angular front-end and execute:

go run *.go

Use cURL or a tool like Postman to hit each of the endpoints. If you want to learn more about creating RESTful APIs with Golang, check out my previous tutorial titled, Create a Simple RESTful API with Golang.

Creating an Attractive Front-End with Angular, TypeScript, and Bootstrap

While we could deploy the Go application to our Raspberry Pi and have a fully functional hardware wallet, it isn’t the most attractive of solutions. Instead it would be a good idea to create a nice front-end application to interact with the RESTful API that we had just created. It doesn’t really matter what JavaScript technology we use, but for this example we’ll be using Angular.

Create a New Angular Project with the Dependencies

To be successful with the Angular development, you’ll need the Angular CLI. With the Angular CLI installed, we can create a new project by executing the following command:

ng new ui

Make sure that the above command is executed within your Go project. As part of the build process we’ll be bundling it with Go.

If you plan to have an export or download feature within your hardware wallet, you’ll need an extra dependency that will allow us to save binary data as a file in Angular. Within the Angular project, using the CLI, execute:

npm install file-saver --save

If you don’t plan to download the wallet from the Raspberry Pi, you won’t need the dependency. To make our application attractive, we are going to be using Bootstrap and jQuery.

Download Bootstrap and extract the content to the project’s src/assets directory. Download jQuery and place the minified JavaScript file in the project’s src/assets/js directory. We’ll also need Font Awesome for a few icons. Download Font Awesome and place the font and CSS data in the project’s src/assets directory.

The downloaded files will need to be imported into our project’s src/index.html file. This file should look like the following:

Before we jump into each of the functions, notice our constructor method. We’re injecting the Http service and the Router service because our WalletService will handle HTTP requests and navigation. Also notice the hosts that were initialized. If we wish to test on our host machine we’ll be using localhost, otherwise we’ll be using the hostname of our Raspberry Pi.

After a request is made to our Golang API, we make use of an EventEmitter for changing the authenticated status. Each of the pages will listen to this emitter and respond appropriately as things change.

We can subscribe to the emitter by calling the isAuthenticated function:

public isAuthenticated() {
return this.authenticated;
}

If we ever need to sign out, we would call disconnect which contains the following:

The create function will issue a POST request where the results can be subscribed to. This request along with all other requests will contain a passphrase that is saved in our service after authenticating.

When we need to create or import a coin, we execute the import function:

If a WIF key is provided, we’ll send an HTTP request to our /api/import-coin endpoint, otherwise we’ll make a request to our /api/create-coin endpoint. At a minimum we require the symbol so we know which type of coin we’re working with.

When we have coins in our wallet, we’ll probably want to be able to view our public addresses. We can do that with the getAddresses function:

Finally, and these are optional, if we wanted to dump or download our wallet data we could create two more functions. If we wish to backup our wallet, in other words download the encrypted file, we could create a function called backup like this:

Notice that we’re injecting the WalletService and Router in the constructor method. We’ll be using both in the unlock method. When the user chooses to unlock the application, we check to see if the password exists and we try to authenticate. If authentication is successful, we navigate to the dashboard.

The HTML that pairs with the UnlockComponent TypeScript is found in the project’s src/app/unlock/unlock.component.html and it looks like the following:

The behavior of the GenerateComponent class is similar to the UnlockComponent class. We’re looking at the passwords entered by the user and creating a wallet using our service. If successful, we’ll navigate back to the unlock screen.

The HTML that pairs to this TypeScript is found in the src/app/generate/generate.component.html file and it looks like the following:

When the page initializes, the ngOnInit method is called which makes a request to get our addresses. The result is added to our public array to be rendered onto the screen. Not much in terms of logic is happening here.

Now open the project’s src/app/dashboard/dashboard.component.html file and include the following:

In the core of our HTML, we have a loop that loops through our public array of coins. Each coin is rendered on the screen. Remember, we are not receiving any sensitive data nor are we trying to display it on the screen. If the import button is pressed we are navigated to the screen for importing coins and if either of the addresses is clicked, we are navigated to the transaction screen. Neither of those components are created yet, but we’ll get there.

Since we don’t have any coins yet, we need to create our component for importing coins. From the Angular CLI, execute the following:

ng g component import

Of the new files, open the project’s src/app/import/import.component.ts TypeScript file and include the following:

After the user has entered a symbol, a WIF string, or both, we’ll call the function for creating or importing coins into our wallet file. After the coin has been added, we navigate back to the dashboard.

The HTML found in src/app/import/import.component.html will look like the following:

Just to reiterate, most of the HTML is related to Bootstrap. All we really care about is the form bindings to our TypeScript and the functions that exist on our button. Remember, our frontend isn’t doing anything beyond collecting and displaying data. The Go application does all the heavy lifting.

This brings us to our final component. We want to be able to create and sign transactions to be broadcasted on the blockchain network.

From the Angular CLI, execute the following:

ng g component transaction

Open the project’s src/app/transaction/transaction.component.ts file and include the following TypeScript logic:

Notice the ngOnInit method in the above code. We’re subscribing to the route parameters because we need to obtain the address selected on the previous screen. It is convenient and prevents our user from having to fill all form fields.

If all the fields are filled and the user executes the create function, the data is sent to our API and the transaction is returned.

The HTML for this component can be found in the src/app/transaction/transaction.component.html file and it contains:

Remember our authentication logic? We never actually set it up to secure the other components we created. That is because our parent component will take care of it for us.

In the ngOnInit method we check to see if we’re authenticated and if we’re not, navigate to the unlock screen. We’re also subscribing to the emitter from our service and if we are no longer authenticated navigate as well. In the parent component we have a few buttons that will allow us to backup our wallet or disconnect fro our session.

The HTML for this component is found in the src/app/app.component.html file and it looks like the following:

Take particular notice of the useHash property in our router. Remember our multiplexer for Go? We are using a sub-router for our API and loading our Angular application for the root route. By default each Angular route is handled by simple slashes. The problem with this is that Go is a nightmare when it comes to playing nice with the Angular router. The trick is to change how the Angular router behaves by using a pound symbol. Here are what two of the same routes might look like:

http://localhost:12345/dashboard
http://localhost:12345/#/dashboard

That simple trick saves us a lot of pain.

If we wanted to, we could run our application by executing the following command:

ng serve

Our Go application is potentially serving at http://localhost:12345 and our Angular application is potentially serving at http://localhost:4200. Remember the CORS that we set up? This is where it is valuable because we’re using two different ports for now.

Bundling and Building the Golang and Angular Application

Ideally we’re not going to want to serve the Angular application on our Raspberry Pi Zero because that adds a Node.js dependency and possibly other things. Instead we want to bundle the Angular application, cross-compile the binary, and enjoy a single application file with no dependencies.

Package Web Resources to be Included in the Binary

When bundling HTML, CSS, and JavaScript files, we want to encode them and add them to a Go file. There are many tools to do this, but I’ve had most success with GeertJohan/go.rice. Before we can do this, we need to build the Angular project.

Within the ui directory and using the Angular CLI, execute the following:

npm run build

The above command will create a dist directory with Webpack optimized HTML, CSS, and JavaScript files that can be run without any kind of server. Think back to what we had in our main.go file:

Cross-Compiling the Go Application for ARM Architectures

If you’re doing all your development directly on the Raspberry Pi Zero, I tip my hat to you, but if you’re like most people, you’re developing from a host machine. To cross-compile for the Raspberry Pi, we can do so without ever breaking a sweat.

Just add it to a build.sh file or similar and it will take care of building the Angular project, bundling it, and cross-compiling it.

Configuring a Raspberry Pi Zero for Emulating Ethernet with USB

One of the major points of this tutorial is that we’re using a Raspberry Pi Zero with no WiFi and no Bluetooth as our hardware wallet. This leaves us with little opportunity for communicating with the Raspberry Pi Zero at all. By enabling ethernet emulation over USB, not only could we SSH to the device from the host computer, but we can also listen for things coming from the other direction, hence our API and Angular application. Only the host computer can access the application, not other computers on the network.

Installing Raspbian Lite on the Raspberry Pi Zero

Raspbian Linux is the only Linux distribution that I’ve ever used when it comes to the Raspberry Pi Zero, so it is a requirement for this tutorial. Other distributions might work, but I have no idea how to enable ethernet emulation.

If you’re using a Mac, mount an SD card and execute the following command:

diskutil list

Figure out the correct drive from the list and unmount it using something like this:

diskutil unmount /dev/disk4s1

Remember to choose the correct disk, do not just copy and paste my commands otherwise you might destroy something important.

Now you can install Raspbian Linux to your unmounted drive:

sudo dd bs=1 if=/path/to/raspbian.img of=/dev/rdisk4

In the above command you’re going to want to use the correct image file and path as well as the correct disk for your SD card. By prefixing ‘r’ in the disk, the command will complete a lot faster. However, either will work.

If you’re on Windows or Linux the commands will be a bit different. Unfortunately, I’m using a Mac and don’t have a solution for the other operating systems.

Emulate Ethernet over USB

Leave your formatted SD card connected and mounted to your host computer. We need to change some settings before we place it in the Raspberry Pi Zero. If you’re on a Mac, navigate to /Volumes/boot.

The first thing to do is place an ssh file in this path. The file should have no extension and no data. By placing this file we’ll be able to SSH into the Pi Zero. To emulate ethernet over USB, we need to alter two files.

Open the /Volumes/boot/config.txt file and include the following line at the bottom:

dtoverlay=dwc2

Next, open the /Volumes/boot/cmdline.txt file and include modules-load=dwc2,g_ether after the rootwait parameter. This cmdline.txt file is space delimited and formatting is very important.

Securing Connections to the Raspberry Pi Zero

With a fresh installation of Raspbian, you’re left with a default user account with pi as the username and raspberry as the password. For a sensitive project like this, it probably isn’t a good idea to keep the defaults. Instead we’re going to want to use SSH keys.

Connect the Raspberry Pi Zero to a computer via the USB port, not the power port.

Assuming you’re using Mac or Linux which has Bonjour installed, execute the following:

If you’re on Windows, make sure you install Bonjour-like software to be able to discover the Pi Zero by its hostname. After you connect, execute the following command:

sudo raspi-config

Change the hostname and any other settings you feel to be appropriate. My hostname is coin.local as seen in my Angular code.

Without going into detail on how to add a public key on Linux, add your public key to ~/.ssh/authorized_keys so that way we don’t need a plaintext password to connect.

With a public key added, open the /etc/ssh/sshd_config file on the Raspberry Pi and set PasswordAuthentication to no. Don’t do this until you’ve confirmed that you can connect with your SSH key.

After you’re good, you need to restart the SSH service:

sudo service sshd restart

The Raspberry Pi Zero is now a little more hardened than when it started. Could you do better? Absolutely, but you might want to do some Linux research. The more locked down your Raspberry Pi, the better for your hardware wallet.

Deploying the Application and Configuring a Linux Service on the Raspberry Pi

We’re approaching the conclusion of our Raspberry Pi Zero hardware wallet tutorial. We just need to deploy what we’ve done so it can be used in production.

Create Systemd Scripts for an Auto-Starting Linux Service

It is a hassle to have to sign into the Raspberry Pi via SSH every time we plug it in to be able to access our wallet. Instead, we should create an auto-starting service for our application.

Create a /lib/systemd/system/open-ledger.service on the Raspberry Pi and include the following:

The above command assumes that your binary is called open-ledger-micro and that you’re using coin.local as your Raspberry Pi hostname.

Restart your Raspberry Pi Zero and enter http://coin.local:12345 in your web browser. If everything went smooth, you’ll have a fancy little hardware wallet with encryption at a fraction of the price of the Ledger Nano S and similar.

Conclusion

You just learned how to create a cryptocurrency hardware wallet using Golang, Angular, and a Raspberry Pi Zero. If you completed the tutorial, I applaud you as it was quite lengthy. Most of the tutorial took bits and pieces from previous tutorials that I wrote and used them in a realistic scenario. Most, if not all, of the previous tutorials include videos in case you have trouble getting things set up.

Some closing thoughts on the project:

Again, I am an enthusiast, not an expert when it comes to cryptocurrencies and cryptography. Use anything I created at your own risk, but definitely report back if you’ve found a bug.

Make sure you’re using the data port on the Raspberry Pi Zero and you’re using a charge and sync cable, not a charge cable. I prefer Anker brand USB cables.

We chose Go for this project because there wouldn’t be any further dependencies for the Raspberry Pi Zero which would potentially open it to further risk.

The hostname coin.local or whatever you chose will be accessible if you have Bonjour or similar on your host computer. Anything that can detect network devices by their hostname.

This project can be found on GitHub. I encourage you to contribute to it to make it better.

This article includes affiliate links to the Raspberry Pi Zero and accessories. Please use them to support me if you plan on buying anything used in this particular project.

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in Java, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Apache Cordova. Nic writes about his development experiences related to making web and mobile development easier to understand.