Notes on deriving Bitcoin addresses using Libbitcoin

Motivation:

  • Derive publicly shareable address
  • Deterministically derive new address from HD keys

Address anatomy

The serialized bitcoin address consists of

  • 1-Byte version prefix (mainnet or testNet)
    0x00 or 0x6F
  • 20-Byte hash digest (double hashed public key)
  • 4-Byte checksum

    The serialized format is encoded in base58, the hash of the public key can be compressed or uncompressed

Derive publicly shareable address

House keeping

Generate random entropy and valid ec private and public points

    bc::data_chunk entropy(bc::ec_secret_size);
    bc::pseudo_random_fill(entropy);
    bc::ec_secret ecPrvKey(bc::to_array<bc::ec_secret_size>(entropy));
    bc::ec_compressed ecPubKey;
    bc::secret_to_public(ecPubKey, ecPrvKey);

Lets create a bitcoin address using libbitcoin according to the above serialized format.

std::string btc_utils::derive_address(bc::wallet::ec_public ecPublic, uint8_t network) {
// 1 byte network main or test
// 20 bytes double hash publicKey
// 4 bytes digest
// encode base58
    bc::one_byte prefix{{network}};
    bc::data_chunk prefix_pubkey_checksum(bc::to_chunk(prefix));
    auto pubkeyHash = bc::bitcoin_short_hash(bc::to_array(ecPublic));
    bc::extend_data(prefix_pubkey_checksum, pubkeyHash);
    bc::append_checksum(prefix_pubkey_checksum);
    return bc::encode_base58(prefix_pubkey_checksum);
}

Print addresses to the console

std::cout << "crafted address\t" << btc_utils::derive_address(ecPubKey, 0x00) << std::endl;
std::cout << "crafted address\t" << btc_utils::derive_address(ecPubKey, 0x6F) << std::endl;

Deterministically derive new address from HD keys

The Hierarchical Deterministic (HD) wallets allow the use to derive an arbitrary number of private keys from a single master root seed that can be easily controlled or backed up.
The root seed can have lengths of 128,256,512 bits. The seed shall be encoded into a mnemonic (list of words)
Libbitcoin offers hd_private that can be used to instantiate the root master private key.

bc::wallet::hd_private btc_utils::generate_hd_wallet(bc::wallet::word_list mnemonic, uint64_t network) {
    if (!bc::wallet::validate_mnemonic(mnemonic)) {
        throw "invalid mnemonic";
    }
    auto hd_seed = bc::wallet::decode_mnemonic(mnemonic);
    return bc::wallet::hd_private(bc::to_chunk(hd_seed));
}
bc::wallet::hd_public btc_utils::derive_hd_master_public(bc::wallet::hd_private &hdPrivate) {
    return hdPrivate.to_public();
}

Derive child private key from the master hd_private or hd_public

bc::wallet::hd_public btc_utils::derive_hd_child_public(bc::wallet::hd_public &hdPublic, int index) {
    return hdPublic.derive_public(index);
}
bc::wallet::hd_private btc_utils::derive_hd_child_private(bc::wallet::hd_private &hdPrivate, int index) {
    return hdPrivate.derive_private(index);
}

Derive grand child private keys

auto m0 = btc_utils::derive_hd_child_public(m,0); // first child, depth 1
auto m01 = btc_utils::derive_hd_child_public(m0,0); second child, depth 2

the to_public() will give the hd_public of the corresponding child depth.

Generate an address from the public/child key

    bc::wallet::word_list words = bc::wallet::create_mnemonic(entropy);
    auto hdPrivate = btc_utils::generate_hd_master_private(words, bc::wallet::hd_private::testnet);
    auto hdPublic = btc_utils::derive_hd_master_public(hdPrivate);
    auto m0 = btc_utils::derive_hd_child_private(hdPrivate, 0);
    auto m00 = btc_utils::derive_hd_child_private(m0, 0);
    std::cout << btc_utils::derive_payment_address(m00) << std::endl;
    auto M0 = btc_utils::derive_hd_child_public(hdPublic,0);
    auto M00 = btc_utils::derive_hd_child_public(M0,0);
    std::cout << btc_utils::derive_payment_address(M00) << std::endl;
0