Libbitcoin: HD Keys

Modern Wallets use Hierarchical Deterministic(HD) Keys rather than simple keys in order to allow a user to use a different bitcoin address with every transaction.

Today, We’re going to program an HD keychain that generates all the necessary keys for a bitcoin wallet. This is going to allow us to see how all the special data structures are used to represent keys in libbitcoin.

HD keys are comprised of a Master Key, a Chain code, and an index all of which are used to derive child public/private keys and thereby generating unlinkable bitcoin addresses which are all cryptographically usable by the same master key. This allows the user to create a custom and complex wallet structure involving different addresses for different purposes and spendable with only with certain keys while maintain a central point of control with the master key. For example, a certain set of keys might be used as a change addresses while others are used for receiving customer funds. Additionally, certain private keys can be used as employee expense accounts by limiting access to a certain set of funds.

Our program is going to be a simple HD keychain generator which generates a brand new set of HD keys from entropy and displays all of it’s elements including a mnemonic designed to be a more human readable form of the seed to be stored.

Start out by including the necessary libraries and namespaceing libbitcoin.

#include <bitcoin/bitcoin.hpp>
#include <string.h>
#include <iostream>

using namespace bc;

Now, in our main function we are going to need to create a seed using the pseudo-randomness generator. This seed is going to be hashed into out private key so the more random the more secure it will be. For this we first need to declare a data chunk variable and declare it’s size since libbitcoin’s randomness function allows us fill it directly. Since a data chunk is a vector of bytes(8 bit structures) we are going to declare 16 bytes which allows us enough room for a 128 bit seed, the standard for a “short seed”. Then we simply call the utility function for pseudo random fill on the chunk and then call the encode base16 function on it to print it in hex format to the console.

	data_chunk seedChunk(16);
	std::cout << "\nHex Seed: " << std::endl;
	std::cout << encode_base16(seedChunk)<< std::endl;

Once we have the seed we need to generate a mnemonic in order to make it easier to write down. To do so we declare a “word_list” type variable and store the return result of our “create_mnemonic” function in it. Here, again, we need to remember to scope the wallet namespace.

Please Note: This mnemonic will not be able to retrieve the seed within the libbitcoin framework. Since we are using a simple seed to generate the private keys rather than hashing. The libbitcoin function to decode these mnemonics rely on hashed seeds. I will explain this in depth in my following post.

wallet::word_list writtenWord = wallet::create_mnemonic(seedChunk);
Now we use the validator function to make sure it worked and print out the mnemonic by iterating over the word list.
if(wallet::validate_mnemonic(writtenWord)){ for (auto i = writtenWord.begin(); i != writtenWord.end(); ++i) 
std::cout << *i << ' '; 
}else{ std::cout << "mnemonic invalid!" << std::endl; } 

Once we have the seed, we just need to insatiate the keys using the special libbitcoin objects in the same way describe in my previous post except that now we will specify the type as HD.

For the private keys take note of the member functions used to display the keys. Calling the serializer function “encoded()” displays the base58check(WIF) format of the key while calling “to_hd_key()” returns the raw byte array which can be encoded using hex for display:

	wallet::hd_private privateKey(seedChunk);
	std::cout << "\n\nMaster Private Key: " << std::endl;
	std::cout << privateKey.encoded() << std::endl;

	wallet::hd_key keys = privateKey.to_hd_key();
	std::cout << "\nHex Master Private Key: " << std::endl;
	std::cout << encode_base16(keys) << std::endl;

Next, using the master private key object’s factories we can derive the public key easily.

	wallet::hd_public publicKey = privateKey.to_public();
	std::cout << "\nMaster Public Key: " << std::endl;
	std::cout << publicKey.encoded() << std::endl;

Now that we have generated the master keys, deriving the child keys is once again relatively simple using the private key’s factory methods. Although, it is important to notice that we are passing an index as an argument to the “derive_private” method. The index is useful for keeping track of the structure of your wallet, for example, keys derived from index 1 – 5 are used as receiving addresses while 6-9 are used for chain address.

	wallet::hd_private childPrivateKey = privateKey.derive_private(1);
	std::cout << "\nChild Private Key: " << std::endl;
	std::cout << childPrivateKey.encoded() << std::endl;

Same goes for the public key.

	wallet::hd_public childPublicKey = privateKey.derive_public(1);
	std::cout << "\nChild Public Key: " << std::endl;
	std::cout << publicKey.encoded() << std::endl;

Finally, we can display a payment address spendable by our master key but not obviously associated with it.

To do this we use the child public keys accessor function “point()”, which returns an elliptic curve public key point that we will use to instantiate an “ec_public” object. Once we have the public key object we will use its factory method “to_payment_address()” and then call the returned address’s serializer function “encoded()” to print it out.

Although, this encoding can be done separately I have shown it as a one liner for the sake of style points.

	std::cout << "\nPayment Adress: " << std::endl;
	std::cout << wallet::ec_public(childPublicKey.point()).to_payment_address().encoded()<< "\n" << std::endl;

Then, just compile as usual and the output should look like this.

HDkeys output

Next, we will be looking at how to persistently manage these keys in a wallet-like environment rather than just generating them.

Leave a Reply

Your email address will not be published.