Let’s keep in mind the problems that we want to resolve:
Prevent outdated backups.
Delegating key / address generation to an untrusted peer.
A “Deterministic” wallet would fix our backup problem. With such a wallet, you would have to save only the seed. From this seed, you can generate the same series of private keys over and over.
This is what the “Deterministic” stands for. As you can see, from the master key, I can generate new keys:
ExtKey masterKey =newExtKey();Console.WriteLine("Master key : "+masterKey.ToString(Network.Main));for (int i =0; i <5; i++){ExtKey key =masterKey.Derive((uint)i);Console.WriteLine("Key "+ i +" : "+key.ToString(Network.Main));}
The base58 type equivalent of ExtKey is called BitcoinExtKey.
But how can we solve our second problem: delegating address creation to a peer that can potentially be hacked (like a payment server)?
The trick is that you can “neuter” your master key, then you have a public (without private key) version of the master key. From this neutered version, a third party can generate public keys without knowing the private key.
ExtPubKey masterPubKey =masterKey.Neuter();for (int i =0 ; i <5 ; i++){ExtPubKey pubkey =masterPubKey.Derive((uint)i);Console.WriteLine("PubKey "+ i +" : "+pubkey.ToString(Network.Main));}
So imagine that your payment server generates pubkey1, you can get the corresponding private key with your private master key.
masterKey =newExtKey();masterPubKey =masterKey.Neuter();//The payment server generate pubkey1ExtPubKey pubkey1 =masterPubKey.Derive(1);//You get the private key of pubkey1ExtKey key1 =masterKey.Derive(1);//Check it is legitConsole.WriteLine("Generated address : "+pubkey1.PubKey.GetAddress(ScriptPubKeyType.Legacy,Network.Main));Console.WriteLine("Expected address : "+key1.PrivateKey.PubKey.GetAddress(ScriptPubKeyType.Legacy,Network.Main));
ExtPubKey is similar to ExtKey except that it holds a PubKey and not a Key.
Now we have seen how Deterministic keys solve our problems, let’s speak about what the “hierarchical” is for.
In the previous exercise, we have seen that by combining master key + index we could generate another key. We call this process Derivation, the master key is the parent key, and any generated keys are called child keys.
However, you can also derivate children from the child key. This is what the “hierarchical” stands for.
This is why conceptually more generally you can say: Parent Key + KeyPath => Child Key.
In this diagram, you can derivate Child(1,1) from parent in two different way:
Why do you need hierarchical keys? Because it might be a nice way to classify the type of your keys for multiple accounts. More on BIP44.
It also permits segmenting account rights across an organization.
Imagine you are CEO of a company. You want control over all wallets, but you don’t want the Accounting department to spend the money from the Marketing department.
So your first idea would be to generate one hierarchy for each department.
However, in such a case, Accounting and Marketing would be able to recover the CEO’s private key.
We define such child keys as non-hardened.
ExtKey ceoKey =newExtKey();Console.WriteLine("CEO: "+ceoKey.ToString(Network.Main));ExtKey accountingKey =ceoKey.Derive(0, hardened:false);ExtPubKey ceoPubkey =ceoKey.Neuter();//Recover ceo key with accounting private key and ceo public keyExtKey ceoKeyRecovered =accountingKey.GetParentExtKey(ceoPubkey);Console.WriteLine("CEO recovered: "+ceoKeyRecovered.ToString(Network.Main));
CEO: xprv9s21ZrQH143K2XcJU89thgkBehaMqvcj4A6JFxwPs6ZzGYHYT8dTchd87TC4NHSwvDuexuFVFpYaAt3gztYtZyXmy2hCVyVyxumdxfDBpoC
CEO recovered: xprv9s21ZrQH143K2XcJU89thgkBehaMqvcj4A6JFxwPs6ZzGYHYT8dTchd87TC4NHSwvDuexuFVFpYaAt3gztYtZyXmy2hCVyVyxumdxfDBpoC
In other words, a non-hardened key can “climb” the hierarchy. Non-hardened keys should only be used for categorizing accounts that belongs to a point of single control.
So in our case, the CEO should create a hardened key, so the accounting department will not be able to climb the hierarchy.