module BTC::Base58

Base58 is used for compact human-friendly representation of Bitcoin addresses and private keys. Typically Base58-encoded text also contains a checksum (so-called “Base58Check”). Addresses look like 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ. Private keys look like 5KQntKuhYWSRXNqp2yhdXzjekYAR7US3MT1715Mbv5CyUKV6hVe.

Here is what Satoshi said about Base58: Why base-58 instead of standard base-64 encoding?

Constants

ALPHABET

Public Instance Methods

base58_from_data(data) click to toggle source

Converts binary string into its Base58 representation. If string is empty returns an empty string. If string is nil raises ArgumentError

# File lib/btc/base58.rb, line 25
def base58_from_data(data)
  raise ArgumentError, 'Data is missing' unless data
  leading_zeroes = 0
  int = 0
  base = 1
  data.bytes.reverse_each do |byte|
    if byte == 0
      leading_zeroes += 1
    else
      leading_zeroes = 0
      int += base * byte
    end
    base *= 256
  end
  ('1' * leading_zeroes) + base58_from_int(int)
end
base58check_from_data(data) click to toggle source
# File lib/btc/base58.rb, line 63
def base58check_from_data(data)
  raise ArgumentError, 'Data is missing' unless data
  base58_from_data(data + BTC.hash256(data)[0, 4])
end
data_from_base58(string) click to toggle source

Converts binary string into its Base58 representation. If string is empty returns an empty string. If string is nil raises ArgumentError.

# File lib/btc/base58.rb, line 45
def data_from_base58(string)
  raise ArgumentError, 'String is missing' unless string
  int = int_from_base58(string)
  bytes = []
  while int > 0
    remainder = int % 256
    int /= 256
    bytes.unshift(remainder)
  end
  data = BTC::Data.data_from_bytes(bytes)
  byte_for_1 = '1'.bytes.first
  BTC::Data.ensure_ascii_compatible_encoding(string).bytes.each do |byte|
    break if byte != byte_for_1
    data = "\x00" + data
  end
  data
end
data_from_base58check(string) click to toggle source
# File lib/btc/base58.rb, line 68
def data_from_base58check(string)
  data = data_from_base58(string)
  if data.bytesize < 4
    raise FormatError, "Invalid Base58Check string: too short string #{string.inspect}"
  end
  payload_size = data.bytesize - 4
  payload = data[0, payload_size]
  checksum = data[payload_size, 4]
  if checksum != BTC.hash256(payload)[0, 4]
    raise FormatError, "Invalid Base58Check string: checksum invalid in #{string.inspect}"
  end
  payload
end

Private Instance Methods

base58_from_int(int) click to toggle source
# File lib/btc/base58.rb, line 84
def base58_from_int(int)
  raise ArgumentError, 'Integer is missing' unless int
  string = ''
  base = ALPHABET.size
  while int > 0
    int, remainder = int.divmod(base)
    string = ALPHABET[remainder] + string
  end
  string
end
int_from_base58(string) click to toggle source
# File lib/btc/base58.rb, line 95
def int_from_base58(string)
  raise ArgumentError, 'String is missing' unless string
  int = 0
  base = ALPHABET.size
  string.reverse.each_char.with_index do |char, index|
    char_index = ALPHABET.index(char)
    unless char_index
      raise FormatError, "Invalid Base58 character: #{char.inspect} at index #{index} (full string: #{string.inspect})"
    end
    int += char_index * (base**index)
  end
  int
end