class ExoCrypto::Bip44WalletProvider

see: github.com/wuminzhe/bip44

Constants

TESTNET_TICKER

Public Class Methods

address_details(sub_wallet, is_testnet, include_private) click to toggle source

see: github.com/citizen010/bitcoin-prefixes-address-list

# File lib/exocrypto/bip44_wallet_provider.rb, line 60
def self.address_details(sub_wallet, is_testnet, include_private)
  if include_private
    {
      :bitcoin_address => sub_wallet.bitcoin_address(testnet: is_testnet),
      :ethereum_address => sub_wallet.ethereum_address,
      :public_hex => sub_wallet.public_key,
      :private_hex => sub_wallet.private_key,
      :private_wif => sub_wallet.wif(testnet: is_testnet)
    }
  else
    {
      :bitcoin_address => sub_wallet.bitcoin_address(testnet: is_testnet),
      :ethereum_address => sub_wallet.ethereum_address,
      :public_hex => sub_wallet.public_key
    }
  end
end
btc_address_details_of(seed, path, is_testnet=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 126
def self.btc_address_details_of(seed, path, is_testnet=false)
  wallet  = Bip44::Wallet.from_seed(seed, path)
  details = Bip44WalletProvider.address_details(wallet, is_testnet, true)
  details[:address] = details[:bitcoin_address]
  details.delete(:bitcoin_address)
  details.delete(:ethereum_address)
  details
end
btc_base58_to_int(base58_val) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 189
def self.btc_base58_to_int(base58_val)
  alpha         = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
  int_val, base = 0, alpha.size
  base58_val.reverse.split(//).each_with_index do |char, index|
    char_index = alpha.index(char)
    int_val   += char_index * base**index
  end

  int_val
end
btc_decode_base58(base58_val) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 200
def self.btc_decode_base58(base58_val)
  nzeroes = base58_val.chars.find_index { |c| c != '1' } || base58_val.length - 1
  prefix  = nzeroes < 0 ? '' : '00' * nzeroes
  decoded = Bip44WalletProvider.int_to_hex(Bip44WalletProvider.btc_base58_to_int(base58_val))

  [prefix + decoded].pack('H*')
end
btc_encode_base58(hex) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 184
def self.btc_encode_base58(hex)
  leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
  ('1' * leading_zero_bytes) + Bip44WalletProvider.btc_int_to_base58(hex.to_i(16))
end
btc_int_to_base58(int_val, leading_zero_bytes=0) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 173
def self.btc_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
btc_privkey_of_wif(wif, is_compressed=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 218
def self.btc_privkey_of_wif(wif, is_compressed=false)
  result = Bip44WalletProvider.btc_decode_base58(wif)[1..-5].unpack('H*').first
  if is_compressed
    result = result[0...-2]
  end
  result
end
btc_pubkey_of_privkey(hex, is_compressed=false, is_testnet=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 310
def self.btc_pubkey_of_privkey(hex, is_compressed=false, is_testnet=false)
  priv = Bip44WalletProvider.btc_wif_of_privkey(hex, is_compressed, is_testnet)

  Bip44WalletProvider.btc_pubkey_of_wif(priv, is_compressed, is_testnet)
end
btc_pubkey_of_wif(wif, is_compressed=false, is_testnet=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 226
def self.btc_pubkey_of_wif(wif, is_compressed=false, is_testnet=false)
  priv_key                    = Bip44WalletProvider.btc_privkey_of_wif(wif, is_compressed)
  group                       = OpenSSL::PKey::EC::Group.new('secp256k1')
  public_key                  = group.generator.mul(OpenSSL::BN.new(priv_key, 16))
  decompressed_public_key_hex = public_key.to_bn.to_s(16)

  # NOTE: for compression
  public_key_hex = decompressed_public_key_hex
  if is_compressed
    x              = decompressed_public_key_hex[2..-1][0..63]
    y              = decompressed_public_key_hex[2..-1][64..-1]
    leader         = y.to_i(16) % 2 == 0 ? '02' : '03'
    public_key_hex = leader + x
  end

  public_key_hash = Bip44WalletProvider.ripemd160(Bip44WalletProvider.sha256(public_key_hex))
  network         = is_testnet ? '6f' : '00'
  data            = network + public_key_hash

  public_address =
    Bip44WalletProvider.btc_encode_base58(data + Bip44WalletProvider.checksum(data))

  {
    :decompressed_public_key => decompressed_public_key_hex,
    :public_key => public_key_hex,
    :bitcoin_address => public_address
  }
end
btc_pubkey_of_wif_ecdsa(wif, is_compressed=false, is_testnet=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 254
def self.btc_pubkey_of_wif_ecdsa(wif, is_compressed=false, is_testnet=false)
  priv_key = Bip44WalletProvider.btc_privkey_of_wif(wif, is_compressed)

  # kpub = kpriv * G
  curve         = ECDSA::Group::Secp256k1
  pub_key_point = curve.generator.multiply_by_scalar(priv_key.to_i(16))

  #
  # uncompressed, pub key is in form 0x04 + x + y
  # compressed, pub key is in form
  #   0x02 + x if y is even
  #   0x03 + x if y is odd
  # Getting encodings right is difficult...pub.x is a Bignum.  Bignum + Bignum is
  # a biggernum, which isn't really what we want.  We want string concatenation. To do that,
  # we translate to hex, concatenate the hex, and pack it back into a string to
  # concatenate with our leading byte
  #
  # pub.x is a Bignum,
  # so we must concatenate our compression byte with the hex representation of pub_key.x
  #
  decompressed_pub_key = "\x04" +
                         [pub_key_point.x.to_s(16)].pack('H*') +
                         [pub_key_point.y.to_s(16)].pack('H*')
  pub_key              = decompressed_pub_key
  if is_compressed
    leader  = pub_key_point.y % 2 == 0 ? "\x02" : "\x03"
    pub_key = leader + [pub_key_point.x.to_s(16)].pack('H*')
  end

  # ripe160(sha256(pub_key))
  pub_key_hash = Digest::RMD160.digest(Digest::SHA256.digest(pub_key))

  #
  # prepend version to our double hashed pub key, append checksum
  # Bitcoin: 0x00
  # Testnet: 0x6f
  #
  network                  = is_testnet ? "\x6f" : "\x00"
  pub_key_hash_and_version = network + pub_key_hash

  # add checksum
  pub_key_hash_and_version_str          = pub_key_hash_and_version.unpack('H*').first
  pub_key_hash_and_version_and_checksum = pub_key_hash_and_version_str +
                                          Bip44WalletProvider.checksum(
                                            pub_key_hash_and_version_str)

  # base58 encode version, hash, checksum
  pub_addr = Bip44WalletProvider.btc_encode_base58(pub_key_hash_and_version_and_checksum)

  {
    :decompressed_public_key => decompressed_pub_key.unpack("H*").first,
    :public_key => pub_key.unpack("H*").first,
    :bitcoin_address => pub_addr
  }
end
btc_wif_of_privkey(hex, is_compressed=false, is_testnet=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 208
def self.btc_wif_of_privkey(hex, is_compressed=false, is_testnet=false)
  priv_key_version = is_testnet ? 'ef' : '80'
  data = priv_key_version + hex
  if is_compressed
    data += '01'
  end

  Bip44WalletProvider.btc_encode_base58(data + Bip44WalletProvider.checksum(data))
end
checksum(hex) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 160
def self.checksum(hex)
  Bip44WalletProvider.sha256(Bip44WalletProvider.sha256(hex))[0...8]
end
coins() click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 51
def self.coins
  Bip44WalletProvider.ticker_map.keys.to_set.delete(Bip44WalletProvider::TESTNET_TICKER)
end
create_subwallet(xpub, sub_path, is_testnet=false) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 105
def self.create_subwallet(xpub, sub_path, is_testnet=false)
  wallet     = Bip44::Wallet.from_xpub(xpub)
  sub_wallet = wallet.sub_wallet(sub_path)

  {
    :obj => sub_wallet,
    :path => sub_path,
    :details => Bip44WalletProvider.address_details(sub_wallet, is_testnet, false)
  }
end
create_wallet(coin_ticker='BTC') click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 88
def self.create_wallet(coin_ticker='BTC')
  words = BipMnemonic.to_mnemonic(bits: 128)
  seed  = BipMnemonic.to_seed(mnemonic: words)

  coin    = ticker_map[coin_ticker][:slip]
  path    = "m/44'/#{coin}'/0'"
  wallet  = Bip44::Wallet.from_seed(seed, path)
  testnet = coin_ticker == Bip44WalletProvider::TESTNET_TICKER

  {
    :obj => wallet,
    :seed_hex => seed,
    :path => path,
    :details => Bip44WalletProvider.master_details(words, wallet, testnet)
  }
end
from_absolute_path(path) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 135
def self.from_absolute_path(path)
  elements   = path.split('/')
  derivation = "m/#{elements.take(4).drop(1).join('/')}"
  relative   = "m/#{elements.drop(4).join('/')}"

  {
    :absolute_path => path,
    :derivation => derivation,
    :relative_path => relative
  }
end
int_to_hex(int) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 164
def self.int_to_hex(int)
  hex = int.to_s(16)
  #
  # The hex string must always consist of an even number of characters,
  # otherwise the pack() parsing will be misaligned.
  #
  (hex.length % 2 == 0) ? hex : ('0' + hex)
end
master_details(mnemonic, wallet, is_testnet) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 78
def self.master_details(mnemonic, wallet, is_testnet)
  {
    :mnemonic => mnemonic,
    :seed_hex => BipMnemonic.to_seed(mnemonic: mnemonic),
    :xpub => wallet.xpub(testnet: is_testnet),
    :xprv => wallet.xprv(testnet: is_testnet),
    :details => Bip44WalletProvider.address_details(wallet, is_testnet, true)
  }
end
precision_of(ticker) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 55
def self.precision_of(ticker)
  Bip44WalletProvider.ticker_map[ticker][:precision]
end
ripemd160(hex) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 156
def self.ripemd160(hex)
  Digest::RMD160.hexdigest([hex].pack('H*'))
end
seed_of(mnemonic, password=nil) click to toggle source
# File lib/exocrypto/bip44_wallet_provider.rb, line 116
def self.seed_of(mnemonic, password=nil)
  #BipMnemonic.to_seed(mnemonic: mnemonic)
  OpenSSL::PKCS5.pbkdf2_hmac(mnemonic,
                             "mnemonic#{password}",
                             2048,
                             64,
                             OpenSSL::Digest::SHA512.new)
                .unpack('H*').first
end
sha256(hex) click to toggle source

see: gobittest.appspot.com/PrivateKey

https://github.com/dougal/base58/blob/master/lib/base58.rb
http://royalforkblog.github.io/2014/07/31/address-gen
# File lib/exocrypto/bip44_wallet_provider.rb, line 152
def self.sha256(hex)
  Digest::SHA256.hexdigest([hex].pack('H*'))
end
ticker_map() click to toggle source

see: github.com/satoshilabs/slips/blob/master/slip-0044.md

# File lib/exocrypto/bip44_wallet_provider.rb, line 22
def self.ticker_map
  {
    'TESTNET' => {
      :slip => 1,
      :precision => 8
    },
    'BTC' => {
      :slip => 0,
      :precision => 8
    },
    'ETH' => {
      :slip => 60,
      :precision => 18
    },
    'DASH' => {
      :slip => 5,
      :precision => 8
    },
    'DOGE' => {
      :slip => 3,
      :precision => 8
    },
    'LTC' => {
      :slip => 2,
      :precision => 8
    }
  }
end