Categories
Bitcoin

Building Bitcoin: Generating Bitcoin Addresses

At first I was going to build this in PHP. I write code the most in PHP and I just figured, “Eh, what the heck?”

I spent 2 hrs trying to figure out how to generate private keys in PHP. I wanted to do it, but it was weird and really obfuscated.

Instead, I looked up how to do it in Ruby. It took me 10 seconds, and three lines of code. This isn’t the first time this has happened with me and PHP. I really think Ruby is a much better ecosystem than PHP.

At any rate, lets get into the actual code.

Generating Bitcoin Addresses with Ruby

There were a couple tools I used in order to help me build this code. I’ll refactor it tomorrow probably, this is just a proof of concept to get the ball rolling.

The first tool is reading this wiki entry. It enabled me to understand the technical backside of generating Bitcoin addresses.

Also, I used this online tool that allows you to compare each step in the process to your output, to make sure you have it right. (Just hard code the private key and public key after you generate them).

Anyways, this is my code that I wrote for the first step.

require 'openssl'
require 'base58'
require 'digest'
#how do i create a transaction?
#generate private key
#generate public key
#hask public key
#A bitcoin address is a 160-bit has of the public portion of a public/private key ECDSA keypair

key = OpenSSL::PKey::EC.new("secp256k1").generate_key
#string encoding

#STEP 0: generate private key
#to big number, to string representation
private_key = key.private_key.to_s(16)

#STEP 1: generate public key
public_key = key.public_key.to_bn.to_s(16)

#STEP 2: SHA-256 hash public key
sha_256_hash_first = Digest::SHA2.new(256).hexdigest([public_key].pack("H*"))

#STEP 3: RIPMD 160 hash on hashed public key
sha256_RIPMD = Digest::RMD160.new.hexdigest([sha_256_hash_first].pack("H*"))

#STEP 4: Append Version
ripMD = "00" + sha256_RIPMD
public_key = "00" + sha256_RIPMD

#STEP 5: Append Version
public_key =  Digest::SHA2.new(256).hexdigest([public_key].pack("H*"))

#STEP 6: SHA-256 hash again
public_key = Digest::SHA2.new(256).hexdigest([public_key].pack("H*"))

#STEP 7: Take checksum
checksum = public_key[0..7]

#STEP 8:  Add 5 checksum
public_key = ripMD + checksum

#puts "ripMD + checksum: " + public_key
#STEP 9:
address = Base58.encode(public_key.hex(), :bitcoin)

puts "My Bitcoin Address: " + 1.to_s + address

This will output a valid Bitcoin address.

There are some obvious refactors we need to take:

  1. Break it into functions/class
  2. Take out the hard-coded elements
  3. Build out custom base58check encode to append the 1

Those are things I will hopefully implement by tomorrow, then push a refactored version, then build a way to create and send transactions.

Updated Code

Ok, I couldn’t wait until tomorrow, so I rewrote it into a neat little class that generates a Bitcoin address. Honestly, I’m not 100% sure how this all fits into grand scheme of everything, but I was pumped that I was able to knock this out real quick.

require 'openssl'
require 'base58'
require 'digest'
#how do i create a transaction?
#generate private key
#generate public key
#hask public key
#A bitcoin address is a 160-bit has of the public portion of a public/private key ECDSA keypair

class BitcoinAddress

  def self.generate_private_key
    key = OpenSSL::PKey::EC.new("secp256k1").generate_key
    return key
  end

  def self.generate_public_key(private_key)
    public_key = private_key.public_key.to_bn.to_s(16)
    return public_key
  end

  def self.generate_address
    private_key = generate_private_key
    public_key = generate_public_key(private_key)
    ripmd_hash = ripmd160_hash((sha256_hash(public_key)))
    rip_generate_address = ripmd160_hash((sha256_hash(public_key)))

    ripmd_hash = "00" + ripmd_hash
    rip_generate_address = "00" + rip_generate_address

    ripmd_hash = return_checksum(sha256_hash(sha256_hash(ripmd_hash)))
    return "1" + generate_base58_address(rip_generate_address + ripmd_hash)
  end

  def self.sha256_hash(key)
    return Digest::SHA2.new(256).hexdigest([key].pack("H*"))
  end

  def self.ripmd160_hash(key)
    return Digest::RMD160.new.hexdigest([key].pack("H*"))
  end

  def self.return_checksum(data)
    return data[0..7]
  end

  def self.generate_base58_address(key)
    return Base58.encode(key.hex(), :bitcoin)
  end
end

bitcoin = BitcoinAddress
puts bitcoin.generate_address

Leave a Reply