Generating Bitcoin Keys From Scratch With Ruby
15 Mar 2015I’ve recently been looking into some newer features in the bitcoin protocol and feeling a little overwhelmed. I’ve only ever worked with higher level abstractions of the protocol via libraries. In order to get a better understanding of the fundamentals I started reading as much as I could. I found a lot of articles but I felt like each one was missing pieces that helped me understand what was going on. I decided I would try to write a bitcoin transaction from scratch (no bitcoin related libraries) and document the process. I used ruby. I like ruby and I knew I wouldnt get much help from Google with it (which is good). My goal was create a bitcoin address, send some BTC to it, then spend that BTC by creating a new transaction and publishing to the network. So far I have only successfully generated keys so consider this part I.
Bitcoin primitives
We aren’t going to talk about mining and the bitcoin network. It’s not really important to understanding how to make a transaction. I expect you to understand bitcoin on a high level. These are the core primitives we are going to explore.
- Private Key
- Public Key
- Address
- Transaction (eventually)
Private Keys
The most important primitive is the private key. All other keys are derived from the private key. Here is a diagram explaining the process for creating keys:
The private key is, in essence, just a really big random number (256-bits) which can easily be generated offline with a sufficient source of randomness. Because of that, the public key and the address can also be generated offline. Of course, due to the laws of public key cryptography, it is impossible* to derive the private key from the public key or the address. Bitcoins are tied directly to the private key. The owner of the private key cotrols the bitcoins. Because of this, secrecy and security of the private key is of utmost importance. In general, the only thing that you should ever willingly expose is the Address. You don’t even want to reveal your public key until you have to.
Generating Private Keys
Like I said before, the private key is just a 256-bit (32 byte) random number. You can create a private key pretty much any way you can generate random numbers. If you have openssl
installed on your machine, try running this command a few times:
$ openssl rand 32 -hex
f9a9c3825fe4f90385763d3a95b85a3c081410367bcb6c15bb550214bf1faf1a
These are valid private keys. You could even flip a coin 256 times, record each result as 0 or 1, then turn that bit array into a hex string that would look just like one from openssl. You can do pretty much anything you can think of as long as your source of randomness is cryptographically secure.
You may be wondering how you can do this offline since if someone were to somehow accidentally generate a private key that you have also generated, they could control the funds on that address. Believe it or not, every time you generate one of these numbers you can very safely bet your life this is the first time the world has seen that number because the keyspace is so massive even when considering the birthday paradox. For this reason, you can safely generate a key offline and be sure that it does not collide with someone else’s key.
Let’s generate a private key in ruby using securerandom
. We are going to use another method later but I’ll explain when we get there. This will spit out 256-bit (32 byte) hex strings much like the openssl command above did.
Private Key WIF
WIF, the wallet import format is a format for encoding a private key into ASCII space. It uses Satoshi’s own base58check encoding. You may wonder why Satoshi wouldn’t just use base64. Here is the explanation:
This encoding also includes a checksum using SHA-256 which can very quickly verify the integrity of an Address and potentially even allow you to correct it. This is a pretty responsible implementation considering one misplaced character could cost you all your bitcoins.
Let’s look at some ruby code to do base58. I took this from the utils in the bitcoin-ruby project:
In order to do the checksum we will need a function capable of creating a SHA-256 hash from a hex string. Ruby’s digest
module comes with this capability:
Now the checksum is simply the first 4 bytes of the twice SHA’d hex string (The WIF format requires doing the double SHA).
The WIF format also requires that the first byte be the WIF version. Here we have set it to 0x80
. It would be 0xef
if we were using testnet
.
Now let’s make some WIFs!
According to the spec, a base58 encoded private key must start with either '5H', '5J', or '5K'
and these all do. These are valid WIFs you could import into your wallet software now if you wanted! Please don’t do that though :)
Deriving Public Keys
The next step is deriving the public key. Bitcoin uses ECDSA (Elliptic Curve Digital Signature Algorithm) for it’s cryptographic signatures. I suggest you either know a little about or research the following topics before continuing:
- Public Key Cryptography
- Digital Signatures
- Eliptic Curve Cryptography
Ruby doesn’t really make doing this part easy for us. But, I knew this would be the case coming in and decided that might make using it a good learning experience. There is a great, simple ecdsa library for python but not really one for ruby. I did find this library but the idea of someone implementing their own crypto, and in ruby of all languages, kind of freaked me out. To be fair, he does state in his readme that his library is for learning purposes. My code is also only for learning purposes so I am less concerned about security but I am instead concerned about an experimental library generating something in a weird way. The reason is that, since bitcoin is a cryptographic protocol, it’s very opaque and if even one bit is off it could result in errors with no explanation of what the problem is.
Because of these reasons, I chose to stick with openssl
. It has a bad reputation right now (and an even worse API) but it at least has a lot of eyes on it and I can trust that it isn’t going to do anything too weird. It’s also default with ruby so there is no need to install any third party libraries.
We’re going to be using the EC
class in the OpenSSL::PKey
module. Because it generates it’s own private key we will no longer be using our generate_key
method.
We now have our public and private key but they aren’t really in a good format for us to work with. We’d prefer hex strings.
The private_key
is of the type OpenSSL::BN
or (Big Number). It is what it sounds like, a big integer. Here is an example:
Ruby’s to_s
takes a base
. So to get the hex string we just have to ask for base 16 and we get back our 32 byte key in hex format:
The public_key
is of the type OpenSSL::PKey::EC::Point
because in elliptic curve crypto, the public key represents a point on the curve. The Point
class has a method to_bn
we can use to convert it to a Big Number. This will be a 65 byte number. The first byte will be 0x04
. The next 32 will represent x
, the next 32 will represent y
. Fortunately this is the format Bitcoin wants it in so we just need to turn it into a hex string:
Signing and verifying messages
Before we move on to generating the address let’s explore the properties of this curve
object. As you should know, a digital signature allows us to sign and verify data. Let’s say we have some data in string format:
I want to pass this message on to someone else but I want to provide proof that:
- I am the actual author of the message (someone has not just forged my signature and pretended to be me)
- The message has not been altered in transit
This is generally what would be called authentication
in crypto and using a signature algorithm can help provide this proof. “Signing” our data is a function of our private key and the data. In this case the curve
object has abstracted that away. Just know that it uses the private key under the hood:
The result here is a signature in ASN1 format.
Now, given our public key, the message, and the signature, a reciever of the message can verify that the message was in fact signed by our private key and has not been tampered with. We call this “verification” of the message.
First suppose we export the big number of the public key.
Also suppose the receiver of the message already has this on his computer and we have verified it is my public key.
In order to drive this home, let’s see what happens if we modify the message:
I leave it to the reader as an exercise to see that modifying the public key or the signature also results in a failure to verify the message.
Generating an Address
The public key is the address for all intents and purposes but for convenience and some security reasons we must first compute the Public Key Hash
[a 160 bit hash of the public key]. Then we base58check encode that for integrity and readability. This base85 encoded result is the Address
.
The Public Key Hash
is just a SHA-256 hash of the public key which is then run through RIPEMD-160
. We already have a sha256 function, so let’s make one for RIPEMD-160. Ruby provides this hashdigest in the same Digest
module we used with SHA-256.
Now the public key hash:
Using our public key from earlier, the result should look something like this:
Now that we base58check encode the public key hash to get the address:
And there is our bitcoin address. Our address version here is 0x00
. For testnet
it’s 0x6f
. The spec says that version zero addresses should start with 1.