Bitcoin key derivation in Elixir

How to derive public and private child keys in Bitcoin wallet?

As the final step of building our Bitcoin offline wallet, we need to be able to generate public and private child keys from our master key created previously:

As you already know, Hierarchical Deterministic Wallets (or HD wallets), which we’re gonna use for that purpose, were firstly introduced by BIP 32 and improved by BIP 44 later on.

An HD wallet is a public/private key tree structure with a common parent (root) node usually called a master node.

Moreover, each node has its own extended private and public key and can have any number of children. The HD wallet is designed to be able to generate many public/private key pairs from a single seed or mnemonic.

Extended key

The extended keys are a Base58 encoded version of serialized data that is concatenated in a specific order:

[version][depth][fingerprint][index][chain_code][serial]

Version

4 bytes indicating:

the network a key belongs to: test (t) or main (x) ,

or , and the type of the key: public (pub) or private (prv) .

So, in Bitcoin, these version bytes are tprv / xprv for private keys and

tpub / xpub for public keys for test and main network correspondingly.

Depth

The depth is 1 byte telling how deep an xprv or xpub is in a path, starting from 0 as the master key, and incremented each time we derive one level down.

Parent Fingerprint

The first 4 bytes of hash160 of the compressed parent public key. Each xprv and xpub key at the same path has the same fingerprint, because they both have the same parent public key.

The implementation looks as follows:

And, as you remember, the hashing is done like that:

Index

The index is the number of the child key we are deriving. It is helpful to think of it as a leaf on a branch, where each leaf has a number and they are ordered from the beginning to the end of the branch.

A range of the index can be from 0 to 4294967295 (or 2³²-1 ) where anything in 0..2147483647 follows a non-hardened derivation, and indexes in 2147483648..4294967295 follow the hardened derivation. We can define guards like that:

Chain Code

The code generated in the previous article as a part of extended key.

Serial

Either public or private 65 bytes-long key with the version prefix, 32 bytes are from the x coordinate of the elliptic curve graph and the following 32 bytes come from the y coordinate there.

To serialize a public key you need to change its prefix from 0x04 to 0x02 or 0x03 depending on whether the y point of the public key (on the elliptic curve graph) is an even or odd number. The implementation is pretty simple:

The private key on the other hand is a bit different. Because the private key size is 32 bytes, it should be concatenated with 0x00 byte prefix. This is done so that the size of the private key is exactly 33 bytes, the same as the compressed public key.

Every Bitcoin private key is simply an integer between number 1 and 115792089237316195423570985008687907852837564279074904382605163141518161494337 or HEX: from 1 to 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 . The integer range of valid private keys is governed by the secp256k1 ECDSA standard used by Bitcoin.

Pathlist

An HD wallet hierarchy is represented by a derivation path to the first address. A path is a couple of elements separated by a slash / .

derivation_type / purpose' / coin_type' / account' / chain / index

To give you an example, a path could look like this: m/0/13'/3 , where

' indicates that BIP32 hardened derivation is used.

Converting such path into a list of integers can be done very easily:

Each level in that path has its own meaning:

Derivation type tells what type of key we are deriving: m for xprv and M for xpub .

tells what type of key we are deriving: for and for . Purpose is a constant set to 44' (or 0x8000002C ) following the BIP43 recommendation. It indicates that the subtree of this node is used according to this specification.

is a constant set to (or ) following the recommendation. It indicates that the subtree of this node is used according to this specification. Coin type is a constant set for each cryptocoin. Cryptocoin developers may register an unused number for their project. The list of already allocated coin types can be find in “Registered coin types”.

is a constant set for each cryptocoin. Cryptocoin developers may register an unused number for their project. The list of already allocated coin types can be find in “Registered coin types”. Accounts are numbered from index 0 in sequentially increasing manner. Users can use these accounts to organize the funds in the same fashion as bank accounts: for donation purposes, for savings, for common expenses etc.

are numbered from index in sequentially increasing manner. Users can use these accounts to organize the funds in the same fashion as bank accounts: for donation purposes, for savings, for common expenses etc. Constant 0 is used for external chain and constant 1 for internal chain . External chain is used for addresses that are meant to be visible outside of the wallet (e.g. for receiving payments). Internal chain is used for addresses which are not meant to be visible outside of the wallet and is used for return transaction change.

is used for external and constant for internal . External chain is used for addresses that are meant to be visible outside of the wallet (e.g. for receiving payments). Internal chain is used for addresses which are not meant to be visible outside of the wallet and is used for return transaction change. Addresses are numbered from index 0 in a sequentially increasing manner.

Ideally, every parent should have one child until you reach the chain node, at which point an unlimited number of children should be allowed. These children are the address nodes.

/-- 0

/--- 1

root -- 44 -- 0 -- 0 -- 0 ---- 2

\--- 3

\-- 4

private derivation

derivation BIP44 compliant

compliant Bitcoin cryptocurrency

cryptocurrency account 0

chain 0

indexes 0, 1, 2, 3, 4

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

Derivation rules

There are a couple of rules of thumb worth to mention:

Changing an index number creates a different, non-linked, child key from the same parent

Using the same procedure for child keys with child chain code creates non-linked grandchild keys

Changing a chain code creates a new node

From an extended private key we can generate both child private and public keys, i.e. create a complete wallet

From an extended public key we can create only child public keys which can be useful only for generating addresses

+-----------+------------+----------+

| from \ to | private | public |

+-----------+------------+----------+

| private | possible | possible |

+-----------+------------+----------+

| public | impossible | possible |

+-----------+------------+----------+

These rules can be implemented like:

Summary

This article completes the series of blogposts about building an offline Bitcoin wallet in Elixir.

You finally learned what a derivation path is, how to derive child keys in general and what are the rules regarding derivation. You are now able to create a complete Bitcoin wallet in Elixir for your own purposes, backup it and restore if necessary. You can also generate multiple addresses and create many keys form the master one for any of your transactions.

If you are interested in the entire project, it’s available right here:

You will find there useful examples and the corresponding test suite. Enjoy!