Computing a Bitcoin Address, Part 2: Public Key to (Hex) Address

:: public key, Bitcoin addresses, hashes, SHA256, RIPEMD160, OpenSSL, C, Racket, FFI

In a previous post, we derived a Bitcoin public key from a private key. This post explores how to convert that public key into a (hexadecimal) Bitcoin address. I’ll be using the Racket language to help me.

This is the second post in a four-part series titled “Computing a Bitcoin Address”. Here are all the articles in the series:

To convert from a public key to a Bitcoin address, we need an implementation of the SHA–256 and RIPEMD–160 hash functions. Racket doesn’t come with these functions but we can easily call to OpenSSL’s implementation of these functions via Racket’s C FFI.

Conveniently, the standard Racket distribution already defines a hook into the libcrypto library, also named libcrypto. Racket comes with wrapper functions for some libcrypto C functions, but not SHA256 or RIPEMD160 so let’s create those.

Here’s the header for the SHA256 C function:

1
unsigned char *SHA256( const unsigned char *d, size_t n, unsigned char *md );

We use the Racket get-ffi-obj function to create a Racket wrapper for SHA256. Here’s a Racket sha256 function that calls the C SHA256 function:

1
2
3
4
5
6
7
8
(define SHA256-DIGEST-LEN 32) ; bytes

(define sha256
  (get-ffi-obj 'SHA256 libcrypto
    (_fun [input     : _bytes]
          [input-len : _ulong = (bytes-length input)]
          [output    : (_bytes o SHA256-DIGEST-LEN)]
          -> (_bytes o SHA256-DIGEST-LEN))))

The first argument to get-ffi-obj is the name of the C function and the second argument is the hook into the appropriate library. The third argument is the type, which specifies how to mediate between Racket and C values. _fun is the function type and in this case the function has three arguments (each delimited with brackets by convention).

Examining the types of the arguments:

  1. The first argument to the SHA256 C function is an array of input bytes. Accordingly, we give get-ffi-obj a _bytes type for this argument.

  2. The second argument is the length of the input byte array. The = and the expression following it describe how to calculate this argument automatically. Thus a caller of sha256 does not provide this argument.

  3. The third argument is the output byte array. The o indicates a return pointer and is followed by the expected length of the output array, which should be 32 bytes here. We define a constant SHA256-DIGEST-LEN which is analogous to the SHA256_DIGEST_LENGTH constant in the C library.

Similarly, here’s a definition for a Racket ripemd160 function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(define RIPEMD160-DIGEST-LEN 20) ; bytes

; from crypto/ripemd/ripemd.h:
;  unsigned char *RIPEMD160(const unsigned char *d, size_t n, unsigned char *md);
(define ripemd160
  (get-ffi-obj 'RIPEMD160 libcrypto
    (_fun [input     : _bytes]
          [input-len : _ulong = (bytes-length input)]
          [output    : (_bytes o RIPEMD160-DIGEST-LEN)]
          -> (_bytes o RIPEMD160-DIGEST-LEN))))

Testing

To test our wrapper functions, let’s see if we can duplicate this example from the Bitcoin wiki, which shows how to convert a Bitcoin private key into a public address. We covered how to derive a public key from a private key in a previous post, so we start with the public key here.

For ease of comparison, here’s the sequence of expected hashes, copied from the Bitcoin wiki example:

  1. public key: 0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
  2. SHA–256: 600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408
  3. RIPEMD–160: 010966776006953D5567439E5E39F86A0D273BEE
  4. prepend 0x00: 00010966776006953D5567439E5E39F86A0D273BEE
  5. SHA–256: 445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094
  6. SHA–256 (checksum is first 4 bytes): D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30
  7. step 4 result + checksum = (hex) address: 00010966776006953D5567439E5E39F86A0D273BEED61967F6

The hashes are all in hexdecimal form so we extend our hash functions to convert to and from hex strings (bytes->hex-string and hex-string->bytes are built-in Racket functions):

1
2
3
4
5
(define (sha256/hex input)
  (bytes->hex-string (sha256 (hex-string->bytes input))))

(define (ripemd160/hex input)
  (bytes->hex-string (ripemd160 (hex-string->bytes input))))

Now we can duplicate the sequence of hashes from the example, using the code from this post (saved to a file crypto.rkt) and the Racket (extended) REPL (the ^ token in the REPL represents the last printed result):

$ racket
Welcome to Racket v6.0.0.3.
-> (require "crypto.rkt")
-> (define pub-key "0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6")
-> (sha256/hex pub-key)
"600ffe422b4e00731a59557a5cca46cc183944191006324a447bdb2d98d4b408"
-> (ripemd160/hex ^)
"010966776006953d5567439e5e39f86a0d273bee"
-> (string-append "00" ^)
"00010966776006953d5567439e5e39f86a0d273bee"
-> (define version0+hash160 ^)
-> (sha256/hex ^)
"445c7a8007a93d8733188288bb320a8fe2debd2ae1b47f0f50bc10bae845c094"
-> (sha256/hex ^)
"d61967f63c7dd183914a4ae452c9f6ad5d462ce3d277798075b107615c1a8a30"
-> (substring ^ 0 8) ; checksum
"d61967f6"
-> (string-append version0+hash160 ^)
"00010966776006953d5567439e5e39f86a0d273beed61967f6"
   

The final result is the Bitcoin address from the example, in hexadecimal format. The Bitcoin wiki article performs one more step to convert to Base58Check encoding, which is the standard representation for Bitcoin addresses. We’ll look at Base58Check encoding in the next post!

Software

All the code from this post is available here. In this post, I’m using OpenSSL 1.0.1e with Racket 6.0.0.3, running in Debian 7.0.