Directory / cardano-sl / hd.md
You are browsing a mirror of a file hosted on GitHub. View original
Cardano SL HD Wallets
When the user
U creates a new wallet
W, he is able to receive money from other
users. To do it,
U have to generate a new address
A for his wallet
that other users will be able to send money to
Suppose other user sent 100 ADA to address
A. Technically it means that new
transaction was created, and this transaction will be a part of some block in the
Cardano blockchain. Later, during synchronization with the blockchain, wallet
will find address
A and since this address was generated by
W, this money is
owned by user
U, so 100 ADA will be shown as a balance of wallet
The main problem is to prove that address
A was generated by wallet
HD (Hierarchical Deterministic) wallet has hierarchical, tree-based structure.
The root of this tree is a pair of user’s secret key
RootSK and corresponding
RootSK is a key we obtain during creation of the wallet:
RootSK is generating from 12-words mnemonic (or “wallet backup
RootPK are crucially important keys:
- Wallet’s identifier is based on
RootSKis using to derive new keys for generating new addresses from them (see below).
RootPKis using during synchronization with the blockchain to find out current balance of this wallet.
So, who owns the root keys - he owns the wallet and all its money.
In general case structure of the tree can be arbitrary, but currently we use two layered structure: nodes of the first layer are called “accounts”, nodes of the second layer are called “addresses” (you can think of them as leaves of the tree). Sometimes layer is called “level”. It can be represented like this:
first second layer/level layer/level Root -- `- Account  -- ` `- Address  ` `- Address  ` ` ... ` `- Address [M] ` `- Account  -- ` `- ... ` ... `- Account [N] -- `- ...
Each address belongs to one account. There can be potentially 2³² accounts (
Word32 value) and each account can have 2³² addresses (
value as well), so wallet can have 2⁶⁴ addresses in total.
Intuitively, balance of each account can be computed as a sum of balances of addresses which belong to it, and therefore balance of the wallet is a sum of balances of its accounts. The details are however much more subtle, please see the formal wallet specification.
Please note that current structure of the tree will change in some future: the number of layers will be increased.
Index and path
As was shown above, each node of the wallet tree has a unique index, it allows us to identify each node:
Root -- `- Account A  -- ` `- Address B  ` `- Address C  ` `- Account D  -- `- Address E  `- Address F  `- Address G 
In this example address
B has index
0, but address
E has index
To identify address uniquely we specify full path from the root. This path
is a list of node indexes, and since our tree is 2-layered, path contains
indexes of account and address. Thus, address
C has a path
[0, 1] and
G has a path
Derivation, parent and child
To generate a new address for the wallet (addresses in HD wallet are called
HD addresses), we have to obtain a new pair of secret/public keys and generate
new address from it. HD wallet allows us to derive new pair of keys from the
RootSK. Actually all the pairs of keys we need to generate all possible
wallet’s addresses are derived from the
RootSK. Let’s call such keys derived
dPK, so address
B from the last example was generated from
E - from
dSK_E and so on.
To specify relations between nodes of the wallet tree we define parents and children. Keys of the child node can be derived from keys of the parent node. So particular address is a child of the particular account, and all accounts are children of the root:
parent ......... child Root keys -- `--> Account [N] keys -- `--> Address [M] keys parent ................ child
That’s why full path from the tree root to the tree leaf (here it is
is called “derivation path”: to derive child’s SK from parent’s SK we need not
only parent’s SK, but child’s index (part of the path) as well:
deriveChildSK :: ChildIndex -> ParentExtSK -> ChildExtSK
Please note that
ChildExtSK are extended keys. Extended key
is a pair of ordinary key and special 32-bytes seed (it’s called “chain code”).
HD address payload
“Account” and “address” entities were mentioned, but only addresses appear in the blockchain. When we create a transaction, we specify destination address and coins for each output of transaction. So neither transaction or block knows anything about accounts and wallets.
We want to be able to track our addresses in the blockchain to compute our wallet balance (because money on our addresses is ours). To prove that particular address was generated by our wallet we need some special information stored in the address.
The idea is it: let’s store derivation path in the address. For example, when we
derived a new
dSK for the node with the path
[12456, 10], let’s generate a
dSK and store this path in the
A. To hide this path from
other parties we will encrypt it (see below). Eventually this serialized and
encrypted derivation path we call
HDAddressPayload becomes a part of the address.
You can think of this payload as a secret stamp we put on all our addresses:
everyone can see it but only we can read it. This corresponds to the current
implementation, but it is going to change in some future.
To encrypt address payload we use a special passphrase we call
We don’t need to store this passphrase because it can be derived from the
HDPassphrase is a HMAC-SHA512 hash of the
So, during synchronization with the blockchain we take some address, extract
encrypted payload from it and then try to decrypt it using our
If it’s successfully decrypted then it proves that this address belongs to our
wallet and, vice versa, address doesn’t belong to our wallet if decryption is
After successful decryption of all our addresses we get whole hierarhy of the wallet tree because decrypted derivation paths describe it.
So now it’s obviously that if you share your
RootPK - you give to anyone an
ability to reveal all your addresses in the blockchain!
Suppose that we derived
dSK_A and generated a new address
A from it. Some
OU sent 100 ADA to the address
A. Technically it means that
created a new transaction
T1 and its output
O contains an address
Now we want to send these 100 ADA to another user
AU. Technically it means
that we have to create a new transaction
T2 and its input
I will refer to
T1 T2 +--------+ +--------+ | | | | | +---+ +---+ | | | O |------>| I | | | +---+ +---+ | | | | | +--------+ +--------+
Now we must prove that we have a right to spend money from the address
(which is in
O). To do it we must prove that address
A was generated by
our wallet (because if so, money on this address is ours and we obviously
may spend it).
This is where derivation path helps us: since address
A contains this path,
we can take it and derive
dSK_A and corresponding
dPK. This is how we prove
A is ours: only we could derive
dPK, which means that only
we could generate address
Now we have to create a witness for each input of transaction. A witness is
our proof that we may spend money from corresponding address, it includes
S and public key
Sis a signature of entire transaction (technically it’s a signature of the hash of the transaction),
PKis a derived public key that corresponds to derived secret key which was used to generate our address.
In our example address
A was generated from
dSK_A, so witness for
- A signature of the transaction
- Public key
dPK_Athat corresponds to
Please note: although we can reveal all wallet’s addresses just knowing
of this wallet, to spend money
RootSK is needed.
Please note that the purpose of this document is to provide a high-level overview of HD wallets. For technical details please read Formal specification for a Cardano wallet.