One key consideration in designing wallets is balancing convenience and privacy: one key for every transaction vs one key for one transaction. The wallet holds only keys. The ether or other tokens are recorded on the Ethereum blockchain. Users control the tokens on the network by signing transactions with the keys in their wallets.
2 HD Wallets
There are three BIPs related to Hierarchical Deterministic (HD) wallet:
- BIP 39: Mnemonic code for generating deterministic keys.
- BIP 32: Hierarchical Deterministic Wallets
- BIP 44: Multi-Account Hierarchy for Deterministic Wallets
The BIP-39 standard allows the use of an optional passphrase in the derivation of the seed. A passphrase can be used to create a duress wallet derivated from a main wallet. A duress wallet is intended as a personal safety feature: you should put money you are willing to lose into the duress wallet, and should you even be forced to reveal a PIN, you may provide the duress PIN in place of the “real” PIN. The Coldcard operates as normal when unlocked by the duress PIN code so that your attackers are not alerted. However, if they are technical enough, there are ways to detect the difference.
BIP 32 is a specification for creating Hierarchical Deterministic (HD) wallets. What this refers to is that 1) all accounts stem from one root key (hierarchical) and 2) given that root key, all child accounts can be reliably recalculated (deterministic).
Each hierarchical depth has some implied meaning. It turns out those meanings are worth standardizing, too, which lead to the creation of BIP 44.
3 Root Key
The wallet creates a 512-bit rood seed from mnemonic cord words and an optional passphrase for a Hierarchical deterministic (HD) wallet
The root seed is input into the HMAC-SHA512 algorithm and the resulting 512-bit hash is divided into a left 256-bit master private key (m) and a right 256-bit master chain code (c). The master private key then generates the master public key (M). The root key is made from the master private key with a depth of 0 is typically represented as a 78-byte extended private key (
xprv). Defined in the BIP 32 spec, extended private keys are a Base58 encoding of the private key, chain code, and some additional metadata. Per the spec, 78 bytes are encoded to derive extended keys:
- 4 bytes: version bytes. mainnet:
- 1 byte: depth.
0x00for master nodes,
0x01for level-1 derived keys, etc.
- 4 bytes: the fingerprint of the parent key. It is the first 4 bytes of the hash160 of the public key of the parent.
- 4 bytes: the child index number.
- 32 bytes: the chain code.
- 33 bytes: the public or private key data. The private/public key is only 32, so the front is padded with one empty byte.
For the root key, the values are:
- depth = 0
- parent_fingerprint = None
- child_number = None
- private_key = master_private_key
- chain_code = master_chain_code
Here is an example of a root key:
xprv prefix is for public extended key while
xpub is for private extended key.
4 Wallet Path
The typical hierarchy is:
seed -> root key -> account keys -> change keys -> addresses. BIP 44 codifies the purpose of each depth level:
m / purpose’ / coin_type’ / account’ / change / address_index. By default, most users of Ethereum are utilizing an address with a derivation path of
m/44'/60'/0'/0/0. Briefly explained:
- The apostrophes (e.g., in the first three levels) indicate that the value is hardened.
- m: is a convention; nothing to read into here.
- 44: purpose, this path follows the BIP 44 standard.
- 60: coin_type,
60indicates the Ethereum network. It is defined in the list of registered coin types.
- 0: account, intended to represent different types of wallet users. For example, a business may have one branch of accounts for an accounting department and another for a sales team. It’s a zero-based index.
- 0: change, use mostly by Bitcoin. Constant
0is used for external chain and constant
1for internal chain (also known as change addresses). Typically remains 0 for Ethereum addresses.
- 0: address_index, finally, the index of the account you’re using. This is also a zero-based index, so index 0 is the first available account. If you have ten accounts, the last’s derivation path is
Each extended key has
2³¹ normal child keys, and
2³¹ hardened child keys. Each of these child keys has an index. The normal child keys use indices
2³¹–1. The hardened child keys use indices
So, if the value is hardened, then we simply add
2³¹ to that number. For example:
You’re probably more likely to see the
h notation as a caret
' instead, so
1' == 1h.
What does a hardened path imply? It’s a security feature; private keys are used in the generation of hardened keys, while non-hardened (or normal) keys are generated using public keys. This has implications on what keys are capable of. Normal extended public keys (xpub) may be used to create child public keys, for example. A commonly cited use case is providing a storefront with an extended public key, which can be used to derive a new child public address to receive funds for each sale. No private keys ever live on the server, so there’s no risk of them leaking if the server is compromised.
5 Child Key Derivation Functions
The child key derivation functions are based on a one-way hash function that combines three parts:
- A parent private or public key (ECDSA compressed key);
- A seed called a chain code (256 bits)
- An index number (32 bits)
The chain code is used to introduce deterministic random data to the process, so that knowing the index and a child key is not sufficient to derive other child keys. The initial chain code seed (at the root of the tree) is made from the seed, while subsequent child chain codes are derived from each parent chain code.
The parent private (for hardend key) or public key (for normal key), chain code, and the index number are combined and hashed with the HMAC-SHA512 algorithm to produce a 512-bit hash. This 512-bit hash is split into two 256-bit halves. The right-half 256 bits of the hash output become the child chain code. The two possible keys are generated as the following:
- The left-half 256 bits of the hash are added to the parent private key, then the
sum modulo ECC-orderis the child private key.
- The left-half 256 bits of the hash are added to the parent public key, then the
sum modulo ECC-orderis the child public key. This is a 33 bytes key.
A very useful characteristic of HD wallets is the ability to derive all public child keys from a public parent key, without having the private parent key. This gives us two ways to derive a child public key: either from the child private key, or directly from the parent public key.
The ability to derive a branch of public keys from an xpub is very useful, but it comes with a potential risk. Because the
xpub contains the chain code, if a child private key is known, or somehow leaked, it can be used with the chain code to derive all the other child private keys and the parent private key.
To counter this risk, HD wallets use an alternative derivation function called hardened derivation, which “breaks” the relationship between parent public key and child chain code. The hardened derivation function looks almost identical to the normal child private key derivation, except that the parent private key is used as input to the hash function, instead of the parent public key. When the hardened private derivation function is used, the resulting child private key and chain code are completely different from what would result from the normal derivation function. The resulting “branch” of keys can be used to produce extended public keys that are not vulnerable, because the chain code they contain cannot be exploited to reveal any private keys.
In simple terms, if you want to use the convenience of an xpub to derive branches of public keys, without exposing yourself to the risk of a leaked chain code, you should derive it from a hardened parent key, rather than a normal (non-hardened) parent key. As a best practice, the level-1 children of the master keys are always derived through the hardened derivation, to prevent compromise of the master keys.