Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Generate a Bitcoin Address in Ruby

I'm trying to generate Bitcoin addresses in ruby by using this guide:

https://bhelx.simst.im/articles/generating-bitcoin-keys-from-scratch-with-ruby/

But something isn't quite right, because the addresses being generated aren't coming out quite right.

Here's the class I'm using:

require 'openssl'
require 'ecdsa'
require 'securerandom'
require 'base58'

class BitcoinAddressGenerator

  ADDRESS_VERSION = '00'

  def self.generate_address
    # Bitcoin uses the secp256k1 curve
    curve = OpenSSL::PKey::EC.new('secp256k1')

    # Now we generate the public and private key together
    curve.generate_key

    private_key_hex = curve.private_key.to_s(16)
    puts "private_key_hex: #{private_key_hex}"
    public_key_hex = curve.public_key.to_bn.to_s(16)
    puts "public_key_hex: #{public_key_hex}"

    pub_key_hash = public_key_hash(public_key_hex)
    puts "pub_key_hash: #{pub_key_hash}"

    address = generate_address_from_public_key_hash(public_key_hash(public_key_hex))

    puts "address: #{address}"
  end

  def self.generate_address_from_public_key_hash(pub_key_hash)
    pk = ADDRESS_VERSION + pub_key_hash
    encode_base58(pub_key_hash + checksum(pub_key_hash))
  end

  def self.int_to_base58(int_val, leading_zero_bytes=0)
    alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    base58_val, base = '', alpha.size
    while int_val > 0
      int_val, remainder = int_val.divmod(base)
      base58_val = alpha[remainder] + base58_val
    end
    base58_val
  end

  def self.encode_base58(hex)
    leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
    ("1"*leading_zero_bytes) + int_to_base58( hex.to_i(16) )
  end

  def self.checksum(hex)
    sha256(sha256(hex))[0...8]
  end

  # RIPEMD-160 (160 bit) hash
  def self.rmd160(hex)
    Digest::RMD160.hexdigest([hex].pack("H*"))
  end

  def self.sha256(hex)
   Digest::SHA256.hexdigest([hex].pack("H*"))
  end

  # Turns public key into the 160 bit public key hash
  def self.public_key_hash(hex)
    rmd160(sha256(hex))
  end

end

It outputs something like:

private_key_hex: C96DE079BAE4877E086288DEDD6F9F70B671862B7E6E4FC0EC401CADB81EDF45
public_key_hex: 0422435DF80F62E643D3CFBA66194052EC9ED0DFB47A1B26A4731079A5FF84FBF98FF0A540B6981D75BA789E6192F3B38BABEF6B0286CAEB4CAFCB51BB96D97B46
public_key_hash: db34927cc5ec0066411f366d9a95f9c6369c6e1d
address: Lz3xnxx6Uh79PEzPpWSMMZJVWR36hJgVL

If I plug this address into blockchain.info and similar tools it says that it's an invalid address.

Any help would be greatly appreciated.

like image 278
railsguy42 Avatar asked Dec 05 '25 07:12

railsguy42


1 Answers

In your generate_address_from_public_key_hash method, the checksum should be over the hash including the address prefix. You’re not actually using the pk variable at all at the moment after you assign it. The code should look something like:

def self.generate_address_from_public_key_hash(pub_key_hash)
  pk = ADDRESS_VERSION + pub_key_hash
  encode_base58(pk + checksum(pk)) # Using pk here, not pub_key_hash
end

The mistake seems to also be on the page you link to, I guess the author must have made a copy/paste error.


As an aside, keeping everything in hex strings and decoding back and forth seems an odd way of doing this. I would have thought it would be easier to use raw binary strings, and only encode to hex when printing out values.

like image 111
matt Avatar answered Dec 07 '25 06:12

matt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!