Credit: Prooworld

So you want to build an Ethereum HD wallet?

Harsha Goli
7 min readMar 14, 2018

--

Warning: This article is not for beginners. It will explore HD wallets, how they’re built, and how to use them in Ethereum.

What exactly is an HD wallet?

Hierarchical Deterministic Wallets (or HD wallets for short) were introduced by BIP 32 and later improved by BIP 44. BIPs (if you’re not familiar with them) stand for Bitcoin Improvement Proposal. Why are we talking about Bitcoin right now? You should take a look at The History of the space for context. Although the Bitcoin and Ethereum networks are incredibly different today, there are still a lot of similarities to be found between the two currencies. Turns out BIP 32 + BIP 44 provided a network agnostic method of generating secure keys in an incredibly flexible manner, a method used by the vast majority of cryptocurrencies.

But what is an HD wallet? Put simply, an HD wallet is a public/private key tree all starting from a master node (not like the Master Nodes of DASH). Here’s a great image for visualization:

The tree is represented by derivation paths. The default for Ethereum is m/44'/60'/0'/0. Each number in that path represents a certain level in the tree above.

m / purpose' / coin_type' / account' / change / address_index

Technically speaking, Hierarchical Deterministic Wallets are a tree structure where each node has an extended private and public key. Any node can have any number of children. Meaning a master key could control 10 accounts in 10 different currencies each with a very high number of addresses. I chose 10 as an arbitrary number, the number could be as large as you’d like.

Keep in mind that Ethereum treats every address as an account, so the best purpose of an HD Wallet would be to manage many accounts from a single master private key. For more reading on the pros and cons of HD Wallets see this article by BitcoinMagazine

For further reading on HD wallets look at my article: HD Wallets Explained

How to Build an Ethereum HD Wallet in node.js

Now we know that an HD Wallet is really just a tree of nodes each with a public and private key and each depth height representing a specific function. First we will create a mnemonic, create an address out of it, and then create a raw transaction and broadcast it on the Ropsten Test Net. Time to build one!

Requirements

Note: If you are using ethereumjs-util/hdkey, you don’t necessarily have to follow the code here. This file provides a ton of awesome helper functions! You can follow along and get the same functionality however.

Master Private Key and Address generation

How do you start a tree? By beginning at the node. Now, HD Wallets are created by a random bit of data called a seed. We will create a mnemonic and convert that mnemonic to a seed. For those unaware as to what a mnemonic is, here is a quote from the bitcoin wiki.

A simplified explanation of how mnemonic phrases work is that the wallet software has a wordlist taken from a dictionary, with each word assigned to a number. The mnemonic phrase can be converted to a number which is used as the seed to a deterministic wallet that generates all the key pairs used in the wallet.

const mnemonic = bip39.generateMnemonic(); //generates string
const seed = bip39.mnemonicToSeed(mnemonic); //creates seed buffer

Now that we have our entropy in the format we need it in, it’s time to generate the root of the node tree. We can also get our masterPrivateKey and hold onto it from here as well.

const root = hdkey.fromMasterSeed(seed);
const masterPrivateKey = root.privateKey.toString('hex');

Note: Treat your root.publicKey as securely as you would treat your masterPrivateKey as you can still generate the addresses without it.

Now in Ethereum each address is considered an account. Don’t get confused by the picture above that has wallets/accounts at depth 1. Truth be told an HD wallets isn’t the best choice for a purely Ethereum wallet but who am I to judge why you’re here ¯\_(ツ)_/¯.

To create a single address we’ll derive an address node. And then extract the address out of it.

const addrNode = root.derive("m/44'/60'/0'/0/0"); //line 1
const pubKey = ethUtil.privateToPublic(addrNode._privateKey);
const addr = ethUtil.publicToAddress(pubKey).toString('hex');
const address = ethUtil.toChecksumAddress(addr);
/*
If using ethereumjs-wallet instead do after line 1:
const address = addrNode.getWallet().getChecksumAddressString();
*/

And Bob’s your uncle you have successfully created a checksum address from a mnemonic! You can verify your own results by putting your mnemonic in this website and seeing if the addresses work out correctly. Make sure to use the BIP 44 Derivation Path and select Ethereum.

Constructing, Signing, and Broadcasting Transactions

Now that we have our address and the associated private key, let’s have some fun and spend some money!

First, go to a faucet and get a top up. Metamask has a nice Ropsten faucet! We’ll be performing this transaction across the Ropsten testnet, for more information on the different testnets and why they exist take a look at this stack overflow post.

To use the Metamask faucet, make sure you have the Metamask plugin installed, the network set to “Ropsten Test Net”, log out of your current session, and import your wallet with the mnemonic seed phrase. Once there, compare the address in Metamask with the string stored in theaddress variable. If all has gone well, it should be the same. Now get an ether from the faucet!

Now that we’re loaded up it’s time to construct our transaction. Our transaction will have the following parameters

*1 ether = 1 x 10¹⁸ WEI

GasPrice and gasLimit are a bit difficult to explain concisely, but I’ll do my best. Think of the Ethereum network as a large computer. It can support advanced logic in the form of smart contracts, but even sending a simple value transaction is logic that needs to be processed, and in Ethereum every execution has a price. Since gas is a unit of measuring the computational work, the gasPrice is the cost per execution. You can think of it as the cost per gallon of fuel in a car.

If the gasPrice is the cost per gallon of fuel, then the gasLimit is the capacity of the fuel tank. It is the highest number of gas that can spent execution a transaction. This limit serves to protect people from an erroneously constructed smart contract that would endlessly execute spending ethereum from the address forever until the address is depleted.

Once you have your parameters set up, they should look a bit like this

const params = {
nonce: 0,
to: '0x4584158529818ef77D1142bEeb0b6648BD8eDb2f',
value: '0.1',
gasPrice: 5000000000,
gasLimit: 21000,
chainId: 3
};

Now to create and serialize the transaction which is what we’ll broadcast across the Ethereum testnet.

const tx = new ethTx(params);//Signing the transaction with the correct private key
tx.sign(addrNode._privateKey);
const serializedTx = tx.serialize()

We have now constructed and bundled up our signed transaction. All that’s left is to broadcast it. There are a few ways to go about this:

We’ll go with a local geth node. I’m assuming you have a mac (Boo if you don’t) but if you don’t just use that handy dandy Google.com site and look up the well documented instructions.

For mac users…

$ brew tap ethereum/ethereum
$ brew install ethereum

…and you’re good to go.

Now for everyone get your geth node up with the following flags which will start the node on testnet and boot up the WS-RPC server we’ll need for web3.

$ geth --testnet --ws

Now that we have our geth server up we can connect to it using web3.js, an api by the Ethereum Foundation that exposes a ton of functionality from the geth node.

const web3 = new Web3(
new Web3.providers.HttpProvider('http://localhost:8545')
);
//Verify connection is successful
web3.eth.net.isListening()
.then(() => console.log('is connected'))
.catch(e => console.log('Wow. Something went wrong'));

And now we can finally broadcast our transaction which we constructed by hand across the Ethereum (testnet) Universe and return the transaction hash.

Web3.eth.sendSignedTransaction(
`0x${serializedTx.toString('hex')}`,
(error, result) => {
if (error) { console.log(`Error: ${error}`); }
else { console.log(`Result: ${result}`); }
}
);

Toss the transaction hash into ropsten.etherscan.io to verify that you’ve done everything right!

Writer’s Note: It is possible to do all this for mobile using react-native although some shimming will be required to get the node modules to work. If there’s interest in it I can write up a How-To article for that.

--

--

Harsha Goli
Harsha Goli

Responses (13)