Lately I’ve been working my way through Mastering Bitcoin, implementing as many of the examples in the book in Elixir as I can.

I’ve been amazed at how well Elixir has fared with implementing the algorithms involved in working with Bitcoin keys and addresses. Elixir ships with all the tools required to generate a cryptographically secure private key and transform it into a public address string.

Let’s walk through the process step by step and build our our own Elixir module to generate private keys and public addresses.

## What are Private Keys and Public Addresses?

A Bitcoin private key is really just a random two hundred fifty six bit number. As the name implies, this number is intended to be kept private.

From each private key, a public-facing Bitcoin address can be generated. Bitcoin can be sent to this public address by anyone in the world. However, only the keeper of the private key can produce a signature that allows them to access the Bitcoin stored there.

Let’s use Elixir to generate a cryptographically secure private key and then generate its most basic corresponding public address so we can receive some Bitcoin!

## Pulling a Private Key Out of Thin Air

As I mentioned earlier, a Bitcoin private key is really just a random two hundred and fifty six bit number. In other words, a private key can be any number between `0` and `2^256`.

However, not all random numbers are created equally. We need to be sure that we’re generating our random number from a cryptographically secure source of entropy. Thankfully, Elixir exposes Erlang’s `:crypto.strong_rand_bytes/1` function which lets us easily generate a list of truly random bytes.

Let’s use `:crypto.strong_rand_bytes/1` as the basis for our private key generator. We’ll start by creating a new `PrivateKey` module and a `generate/0` function that takes no arguments:

``````
defmodule PrivateKey do
def generate
end
``````

Inside our `generate/0` function, we’ll request `32` random bytes (or `256` bits) from `:crypto.strong_rand_bytes/1`:

``````
def generate do
:crypto.strong_rand_bytes(32)
end
``````

This gives us a random set of `32` bytes that, when viewed as an unsigned integer, ranges between `0` and `2^256 - 1`.

Unfortunately, we’re not quite done.

## Validating our Private Key

To ensure that our private key is difficult to guess, the Standards for Efficient Cryptography Group recommends that we pick a private key between the number `1` and a number slightly smaller than `1.158e77`:

An excerpt of the SECG guidelines.

We can add this validation check fairly easily by adding the SECG-provided upper bound as an attribute to our `PrivateKey` module:

``````
@n :binary.decode_unsigned(<<
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41
>>)
``````

Next, we’ll add a `valid?/1` function to our module that returns `true` if the provided secret key falls within this range, and `false` if it does not:

``````
defp valid?(key) when key > 1 and key < @n, do: true
defp valid?(_), do: false
``````

Before we pass our private key into our `valid?/1` function, we’ll need to convert it from a thirty two byte binary into an unsigned integer. Let’s add a third `valid?/1` function head that does just that:

``````
defp valid?(key) when is_binary(key) do
key
|> :binary.decode_unsigned
|> valid?
end
``````

We’ll finish off our validation by passing our generated private key into our new `valid?/1` function. If the key is valid, we’ll return it. Otherwise, we’ll generate a new private key and try again:

``````
def generate do
private_key = :crypto.strong_rand_bytes(32)
case valid?(private_key) do
true  -> private_key
false -> generate
end
end
``````

Now we can call `PrivateKey.generate` to generate a new Bitcoin private key!

## From Private Key to Public Key …

The most basic process for turning a Bitcoin private key into a sharable public address involves three basic steps. The first step is to transform our private key into a public key with the help of elliptic curve cryptography.

We’ll start by adding a new `to_public_key/1` function to our `PrivateKey` module:

``````
def to_public_key(private_key)
``````

In our `to_public_key/1` function, we’ll use Erlang’s `:crypto.generate_key` function to sign our `private_key` using an elliptic curve. We’ll specifically use the `:secp256k1` curve:

``````
:crypto.generate_key(:ecdh, :crypto.ec_curve(:secp256k1), private_key)
``````

We’re using the elliptic curve key generation as a trapdoor function to ensure our private key’s secrecy. It’s easy for us to generate our public key from our private key, but reversing the computation and generating our private key from our public key is nearly impossible.

The `:crypto.generate_key` function returns a two-element tuple. The first element in this tuple is our Bitcoin public key. We’ll pull it out using Elixir’s `elem/1` function:

``````
:crypto.generate_key(:ecdh, :crypto.ec_curve(:secp256k1), private_key)
|> elem(0)
``````

The returned value is a sixty five byte binary representing our public key!

## … Public Key to Public Hash …

Once we have our public key in memory, our next step in transforming it into a public address is to hash it. This gives us what’s called the “public hash” of our public key.

Let’s make a new function, `to_public_hash/1` that takes our `private_key` as an argument:

``````
def to_public_hash(private_key)
``````

We’ll start the hashing process by turning our `private_key` into a public key with a call to `to_public_key`:

``````
private_key
|> to_public_key
``````

Next, we pipe our public key through two hashing functions: SHA-256, followed by RIPEMD-160:

``````
private_key
|> to_public_key
|> hash(:sha256)
|> hash(:ripemd160)
``````

Bitcoin uses the RIPEMD-160 hashing algorithm because it produces a short hash. The intermediate SHA-256 hashing is used to prevent insecurities through unexpected interactions between our elliptic curve signing algorithm and the RIPEMD algorithm.

In this example, `hash/1` is a helper function that wraps Erlang’s `:crypto.hash`.

``````
defp hash(data, algorithm), do: :crypto.hash(algorithm, data)
``````

Flipping the arguments to `:crypto.hash` in this way lets us easily pipe our data through the `hash/1` helper.

## … And Public Hash to Public Address

Lastly, we can convert our public hash into a full-fledged Bitcoin address by Base58Check encoding the hash with a version byte corresponding to the network where we’re using the address.

Let’s add a `to_public_address/2` function to our `PrivateKey` module:

``````
``````

The `to_public_address/2` function takes a `private_key` and a `version` byte as its arguments. The `version` defaults to `<<0x00>>`, indicating that this address will be used on the live Bitcoin network.

To create a Bitcoin address, we start by converting our `private_key` into a public hash with a call to `to_public_hash/1`:

``````
private_key
|> to_public_hash
``````

All that’s left to do is Base58Check encode the resulting hash with the provided `version` byte:

``````
private_key
|> to_public_hash
|> Base58Check.encode(version)
``````

After laying the groundwork, the final pieces of the puzzle effortlessly fall into place.

## Putting Our Creation to Use

Now that we can generate cryptographically secure private keys and transform them into publishable public addresses, we’re in business.

Literally!

Let’s generate a new private key, transform it into its corresponding public address, and try out on the Bitcoin testnet. We’ll start by generating our private key:

``````
private_key = PrivateKey.generate
``````

This gives us a thirty two byte binary. If we wanted, we could Base58Check encode this with a testnet `version` byte of `0xEF`. This is known as the “Wallet Import Format”, or WIF, of our Bitcoin private key:

``````
Base58Check.encode(private_key, <<0xEF>>)
``````

As its name suggests, converting our private key into a WIF allows us to easily import it into most Bitcoin wallet software:

Importing our test private key.

Next, let’s convert our private key into a testnet public address using a `version` byte of `0x6F`:

``````
``````

Now that we have our public address, let’s find a testnet faucet and send a few tBTC to our newly generated address! After initiating the transaction with our faucet, we should see our Bitcoin arrive at our address on either a blockchain explorer, or within our wallet software.

Our tBTC has arrived.

Victory!

## Final Thoughts

Elixir, thanks to its Erlang heritage, ships with a wealth of tools that make this kind of hashing, signing, and byte mashing a walk in the park.

I encourage you to check our the `PrivateKey` module on Github to get a better feel for the simplicity of the code we wrote today. Overall, I’m very happy with the result.

If you enjoyed this article, I highly recommend you check out the Mastering Bitcoin book. If you really enjoyed this article, feel free to send a few Bitcoin to this address I generated using our new `PrivateKey` module:

``````
1HKz4XU7ENT46ztEzsT83jRezyiDjvnBV8
``````

Stay tuned for more Bitcoin-related content as I work my way through Mastering Bitcoin!

For the past three years, I’ve been writing and speaking about Meteor security, building and deploying secure Meteor applications, working with amazing teams to better secure their applications, and building security-focused packages and tools for the Meteor ecosystem.

Needless to say, I’ve learned a lot over that time.

I’m excited to announce that I’ve started work on a new project called Secure Meteor in an attempt to capture and distill everything I know about Meteor security into an easily understandable, actionable guide to building secure Meteor applications.

Secure Meteor is still very much in its early days, so there’s not much to share yet. That said, as a teaser and a token of thanks for showing interest in the project, I want to give you the most detailed Meteor security checklist available anywhere, for free!

Be sure to sign up to receive your free security checklist, and let me know what you’d like to see in the project.

An important piece of the process of transforming a Bitcoin private key into a public address, as outlined in the fantastic Mastering Bitcoin book, is the Base58Check encoding algorithm.

The Bitcoin wiki has a great article on Base58Check encoding, and even gives an example implementation of the underlying Base58 encoding algorithm in C.

This algorithm seems especially well-suited to Elixir, so I thought it’d be a fun and useful exercise to build out `Base58` and `Base58Check` modules to use in future Bitcoin and Elixir experiments.

## Like Base64, but Less Confusing

Base58 is a binary-to-text encoding algorithm that’s designed to encode a blob of arbitrary binary data into human readable text, much like the more well known Base64 algorithm.

Unlike Base64 encoding, Bitcoin’s Base58 encoding algorithm omits characters that can be potentially confusing or ambiguous to a human reader. For example, the characters `O` and `0`, or `I` and `l` can look similar or identical to some readers or users of certain fonts.

To avoid that ambiguity, Base58 simply removes those characters from its alphabet.

Shrinking the length of the alphabet we map our binary data onto from sixty four characters down to fifty eight characters means that we can’t simply group our binary into six-bit chunks and map each chunk onto its corresponding letter in our alphabet.

Instead, our Base58 encoding algorithm works by treating our binary as a single large number. We repeatedly divide that number by the size of our alphabet (fifty eight), and use the remainder of that division to map onto a character in our alphabet.

## Implementing Base58 in Elixir

This kind of algorithm can neatly be expressed in Elixir. We’ll start by creating a `Base58` module and adding our alphabet as a module attribute:

``````
defmodule Base58 do
@alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
end
``````

Inside our `Base58` module, we’ll define an `encode/2` function. If we pass `encode` a binary, we want to convert it into a number using Erlang’s `:binary.decode_unsigned`:

``````
def encode(data, hash \\ "")
def encode(data, hash) when is_binary(data) do
encode(:binary.decode_unsigned(data), hash)
end
``````

Once converted, we pass our binary-come-number into a recursive call to `encode/2` along with the beginning of our hash, an empty string.

For each recursive call to `encode/2`, we use `div` and `rem` to divide our number by `58` and find the reminder. We use that remainder to map into our `@alphabet`, and prepend the resulting character onto our `hash`:

``````
def encode(data, hash) do
character = <<Enum.at(@alphabet, rem(data, 58))>>
encode(div(data, 58), hash <> character)
end
``````

We’ll continue recursing until we’ve divided our `data` down to `0`. In that case, we’ll return the `hash` string we’ve built up:

``````
def encode(0, hash), do: hash
``````

This implementation of our Base58 encoded mostly works. We can encode any text string and receive correct results:

``````
iex(1)> Base58.encode("hello")
"Cn8eVZg"
``````

However when we try to encode binaries with leading zero bytes, those bytes vanish from our resulting hash:

``````
iex(1)> Base58.encode(<<0x00>> <> "hello")
"Cn8eVZg"
``````

That zero should become a leading `"1"` in our resulting hash, but our process of converting the initial binary into a number is truncating those leading bytes. We’ll need to count those leading zeros, encode them manually, and prepend them to our final hash.

Let’s start by writing a function that counts the number of leading zeros in our initial binary:

``````
:binary.bin_to_list(data)
|> Enum.find_index(&(&1 != 0))
end
``````

We use Erlang’s `:binary.bin_to_list` to convert our binary into a list of bytes, and `Enum.find_index` to find the first byte in our list that isn’t zero. This index value is equivalent to the number of leading zero bytes in our binary.

Next, we’ll write a function to manually encode those leading zeros:

``````
defp encode_zeros(data) do
<<Enum.at(@alphabet, 0)>>
end
``````

We simply grab the character in our alphabet that maps to a zero byte (`"1"`), and duplicate it as many times as we need.

Finally, we’ll update our initial `encode/2` function to prepend these leading zeros onto our resulting hash:

``````
def encode(data, hash) when is_binary(data) do
encode_zeros(data) <> encode(:binary.decode_unsigned(data), hash)
end
``````

Now we should be able to encode binaries with leading zero bytes and see their resulting `"1"` values in our final hash:

``````
iex(1)> Base58.encode(<<0x00>> <> "hello")
"1Cn8eVZg"
``````

Great!

## Base58 + Checksum = Base58Check

Now that we have a working implementation of the Base58 encoding algorithm, we can implement our Base58Check algorithm!

Base58Check encoding is really just Base58 with an added checksum. This checksum is important to in the Bitcoin world to ensure that public addresses aren’t mistyped or corrupted before funds are exchanged.

At a high level, the process of Base58Check encoding a blob of binary data involves hashing that data, taking the first four bytes of the resulting hash and appending them to the end of the binary, and Base58 encoding the result.

We can implement Base58Check fairly easily using our newly written `Base58` module. We’ll start by creating a new `Base58Check` module:

``````
defmodule Base58Check do
end
``````

In our module, we’ll define a new `encode/2` function that takes a version byte and the binary we want to encode:

``````
def encode(version, data)
``````

Bitcoin uses the `version` byte to specify the type of address being encoded. A version byte of `0x00` means that we’re encoding a regular Bitcoin address to be used on the live Bitcoin network.

The first thing we’ll need to do is generate our checksum from our `version` and our `data`. We’ll do that in a new function:

``````
defp checksum(version, data) do
version <> data
|> sha256
|> sha256
|> split
end
``````

We concatenate our `version` and `data` binaries together, hash them twice using a `sha256/1` helper function, and then returning the first four bytes of the resulting hash with a call to `split/1`.

`split/1` is a helper function that pulls the first four bytes out of the resulting hash using binary pattern matching:

``````
defp split(<< hash :: bytes-size(4), _ :: bits >>), do: hash
``````

Our `sha256/1` helper function uses Erlang’s `:crypto.hash` function to SHA-256 hash its argument:

``````
defp sha256(data), do: :crypto.hash(:sha256, data)
``````

We’ve wrapped this in a helper function to facilitate Elixir-style piping.

Now that we have our four-byte checksum, we can flesh out our original `encode/2` function:

``````
def encode(version, data) do
version <> data <> checksum(version, data)
|> Base58.encode
end
``````

We concatenate our `version`, `data`, and the result of our `checksum` function together, and Base58 encode the result. That’s it!

Base58Check encoding our `"hello"` string with a `version` of `<<0x00>>` should give us a result of `"12L5B5yqsf7vwb"`. We can go further and verify our implementation with an example pulled from the Bitcoin wiki:

``````
iex(1)> Base58Check.encode(<<0x00>>,
<<0x01, 0x09, 0x66, 0x77, 0x60,
0x06, 0x95, 0x3D, 0x55, 0x67,
0x43, 0x9E, 0x5E, 0x39, 0xF8,
0x6A, 0x0D, 0x27, 0x3B, 0xEE>>)
"16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"
``````

Perfect!

## Wrapping Up

If you’d like to see both modules in their full glory, I’ve included them in my `hello_bitcoin` repository on Github. Here’s a direct link to the `Base58` module, and the `Base58Check` module, along with a simple unit test. If that repository looks familiar, it’s because it was used in a previous article on controlling a Bitcoin node with Elixir.

I highly suggest you read through Andreas Antonopoulos’ Mastering Bitcoin book if you’re at all interested in how the Bitcoin blockchain works, or Bitcoin development in general. His book has been my primary source of inspiration and information for every Bitcoin article I’ve written to date.