class MoneyTree::Node

Attributes

chain_code[R]
depth[R]
index[R]
is_private[R]
parent[R]
private_key[R]
public_key[R]

Public Class Methods

from_bip32(address, has_version: true) click to toggle source
# File lib/money-tree/node.rb, line 17
def self.from_bip32(address, has_version: true)
  hex = from_serialized_base58 address
  hex.slice!(0..7) if has_version
  self.new({
    depth: hex.slice!(0..1).to_i(16),
    parent_fingerprint: hex.slice!(0..7),
    index: hex.slice!(0..7).to_i(16),
    chain_code: hex.slice!(0..63).to_i(16)
  }.merge(parse_out_key(hex)))
end
from_serialized_address(address) click to toggle source
# File lib/money-tree/node.rb, line 28
def self.from_serialized_address(address)
  puts 'Node.from_serialized_address is DEPRECATED. Please use .from_bip32 instead.'
  from_bip32(address)
end
new(opts = {}) click to toggle source
# File lib/money-tree/node.rb, line 13
def initialize(opts = {})
  opts.each { |k, v| instance_variable_set "@#{k}", v }
end
parse_out_key(hex) click to toggle source
# File lib/money-tree/node.rb, line 33
def self.parse_out_key(hex)
  if hex.slice(0..1) == '00'
    private_key = MoneyTree::PrivateKey.new(key: hex.slice(2..-1))
    {
      private_key: private_key,
      public_key: MoneyTree::PublicKey.new(private_key)
    }
  elsif %w(02 03).include? hex.slice(0..1)
    { public_key: MoneyTree::PublicKey.new(hex) }
  else
    raise ImportError, 'Public or private key data does not match version type'
  end
end

Public Instance Methods

chain_code_hex() click to toggle source
# File lib/money-tree/node.rb, line 224
def chain_code_hex
  int_to_hex chain_code, 64
end
depth_hex(depth) click to toggle source
# File lib/money-tree/node.rb, line 59
def depth_hex(depth)
  depth.to_s(16).rjust(2, "0")
end
derive_private_key(i = 0) click to toggle source
# File lib/money-tree/node.rb, line 75
def derive_private_key(i = 0)
  message = i >= 0x80000000 || i < 0 ? private_derivation_message(i) : public_derivation_message(i)
  hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
  left_int = left_from_hash(hash)
  raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
  child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
  raise InvalidKeyForIndex, 'equal to zero' if child_private_key == 0 # very low probability
  child_chain_code = right_from_hash(hash)
  return child_private_key, child_chain_code
end
derive_public_key(i = 0) click to toggle source
# File lib/money-tree/node.rb, line 86
def derive_public_key(i = 0)
  raise PrivatePublicMismatch if i >= 0x80000000
  message = public_derivation_message(i)
  hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
  left_int = left_from_hash(hash)
  raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
  factor = BN.new left_int.to_s

  gen_point = public_key.uncompressed.group.generator.mul(factor)

  sum_point_hex = MoneyTree::OpenSSLExtensions.add(gen_point, public_key.uncompressed.point)
  child_public_key = OpenSSL::PKey::EC::Point.new(public_key.group, OpenSSL::BN.new(sum_point_hex, 16)).to_bn.to_i

  raise InvalidKeyForIndex, 'at infinity' if child_public_key == 1/0.0 # very low probability
  child_chain_code = right_from_hash(hash)
  return child_public_key, child_chain_code
end
i_as_bytes(i) click to toggle source
# File lib/money-tree/node.rb, line 71
def i_as_bytes(i)
  [i].pack('N')
end
index_hex(i = index) click to toggle source
# File lib/money-tree/node.rb, line 51
def index_hex(i = index)
  if i < 0
    [i].pack('l>').unpack('H*').first
  else
    i.to_s(16).rjust(8, "0")
  end
end
is_private?() click to toggle source
# File lib/money-tree/node.rb, line 47
def is_private?
  index >= 0x80000000 || index < 0
end
left_from_hash(hash) click to toggle source
# File lib/money-tree/node.rb, line 104
def left_from_hash(hash)
  bytes_to_int hash.bytes.to_a[0..31]
end
node_for_path(path) click to toggle source

path: a path of subkeys denoted by numbers and slashes. Use

p or i<0 for private key derivation. End with .pub to force
the key public.

Examples:

1p/-5/2/1 would call subkey(i=1, is_prime=True).subkey(i=-5).
    subkey(i=2).subkey(i=1) and then yield the private key
0/0/458.pub would call subkey(i=0).subkey(i=0).subkey(i=458) and
    then yield the public key

You should choose either the p or the negative number convention for private key derivation.

# File lib/money-tree/node.rb, line 184
def node_for_path(path)
  force_public = path[-4..-1] == '.pub'
  path = path[0..-5] if force_public
  parts = path.split('/')
  nodes = []
  parts.each_with_index do |part, depth|
    if part =~ /m/i
      nodes << self
    else
      i = parse_index(part)
      node = nodes.last || self
      nodes << node.subnode(i)
    end
  end
  if force_public or parts.first == 'M'
    node = nodes.last
    node.strip_private_info!
    node
  else
    nodes.last
  end
end
parent_fingerprint() click to toggle source
# File lib/money-tree/node.rb, line 142
def parent_fingerprint
  if @parent_fingerprint
    @parent_fingerprint
  else
    depth.zero? ? '00000000' : parent.to_fingerprint
  end
end
parse_index(path_part) click to toggle source
# File lib/money-tree/node.rb, line 207
def parse_index(path_part)
  is_prime = %w(p ').include? path_part[-1]
  i = path_part.to_i

  i = if i < 0
    i
  elsif is_prime
    i | 0x80000000
  else
    i & 0x7fffffff
  end
end
private_derivation_message(i) click to toggle source
# File lib/money-tree/node.rb, line 63
def private_derivation_message(i)
  "\x00" + private_key.to_bytes + i_as_bytes(i)
end
public_derivation_message(i) click to toggle source
# File lib/money-tree/node.rb, line 67
def public_derivation_message(i)
  public_key.to_bytes << i_as_bytes(i)
end
right_from_hash(hash) click to toggle source
# File lib/money-tree/node.rb, line 108
def right_from_hash(hash)
  bytes_to_int hash.bytes.to_a[32..-1]
end
strip_private_info!() click to toggle source
# File lib/money-tree/node.rb, line 220
def strip_private_info!
  @private_key = nil
end
subnode(i = 0, opts = {}) click to toggle source
# File lib/money-tree/node.rb, line 155
def subnode(i = 0, opts = {})
  if private_key.nil?
    child_public_key, child_chain_code = derive_public_key(i)
    child_public_key = MoneyTree::PublicKey.new child_public_key
  else
    child_private_key, child_chain_code = derive_private_key(i)
    child_private_key = MoneyTree::PrivateKey.new key: child_private_key
    child_public_key = MoneyTree::PublicKey.new child_private_key
  end

  MoneyTree::Node.new( depth: depth+1,
                      index: i,
                      private_key: private_key.nil? ? nil : child_private_key,
                      public_key: child_public_key,
                      chain_code: child_chain_code,
                      parent: self)
end
to_address(compressed=true, network: :bitcoin) click to toggle source
# File lib/money-tree/node.rb, line 150
def to_address(compressed=true, network: :bitcoin)
  address = NETWORKS[network][:address_version] + to_identifier(compressed)
  to_serialized_base58 address
end
to_bip32(type = :public, network: :bitcoin) click to toggle source
# File lib/money-tree/node.rb, line 123
def to_bip32(type = :public, network: :bitcoin)
  raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
  to_serialized_base58 to_serialized_hex(type, network: network)
end
to_fingerprint() click to toggle source
# File lib/money-tree/node.rb, line 138
def to_fingerprint
  public_key.compressed.to_fingerprint
end
to_identifier(compressed=true) click to toggle source
# File lib/money-tree/node.rb, line 133
def to_identifier(compressed=true)
  key = compressed ? public_key.compressed : public_key.uncompressed
  key.to_ripemd160
end
to_serialized_address(type = :public, network: :bitcoin) click to toggle source
# File lib/money-tree/node.rb, line 128
def to_serialized_address(type = :public, network: :bitcoin)
  puts 'Node.to_serialized_address is DEPRECATED. Please use .to_bip32.'
  to_bip32(type, network: network)
end
to_serialized_hex(type = :public, network: :bitcoin) click to toggle source
# File lib/money-tree/node.rb, line 112
def to_serialized_hex(type = :public, network: :bitcoin)
  raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
  version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
  hex = NETWORKS[network][version_key] # version (4 bytes)
  hex += depth_hex(depth) # depth (1 byte)
  hex += parent_fingerprint # fingerprint of key (4 bytes)
  hex += index_hex(index) # child number i (4 bytes)
  hex += chain_code_hex
  hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.compressed.to_hex
end