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:
- Break it into functions/class
- Take out the hard-coded elements
- 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.
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