Class: MoneyTree::Node
Direct Known Subclasses
Master
Defined Under Namespace
Classes: ImportError, InvalidKeyForIndex, PrivatePublicMismatch, PublicDerivationFailure
Constant Summary
Constants included
from Support
Support::BASE58_CHARS, Support::INT32_MAX, Support::INT64_MAX
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Methods included from Support
base58_to_int, bytes_to_hex, bytes_to_int, decode_base58, decode_base64, digestify, encode_base58, encode_base64, from_serialized_base58, hex_to_bytes, hex_to_int, hmac_sha512, hmac_sha512_hex, int_to_base58, int_to_bytes, int_to_hex, ripemd160, sha256, to_serialized_base58
Constructor Details
#initialize(opts = {}) ⇒ Node
Returns a new instance of Node.
13
14
15
|
# File 'lib/money-tree/node.rb', line 13
def initialize(opts = {})
opts.each { |k, v| instance_variable_set "@#{k}", v }
end
|
Instance Attribute Details
#chain_code ⇒ Object
Returns the value of attribute chain_code.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def chain_code
@chain_code
end
|
#depth ⇒ Object
Returns the value of attribute depth.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def depth
@depth
end
|
#index ⇒ Object
Returns the value of attribute index.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def index
@index
end
|
#is_private ⇒ Object
Returns the value of attribute is_private.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def is_private
@is_private
end
|
#parent ⇒ Object
Returns the value of attribute parent.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def parent
@parent
end
|
#private_key ⇒ Object
Returns the value of attribute private_key.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def private_key
@private_key
end
|
#public_key ⇒ Object
Returns the value of attribute public_key.
5
6
7
|
# File 'lib/money-tree/node.rb', line 5
def public_key
@public_key
end
|
Class Method Details
.from_bip32(address, has_version: true) ⇒ Object
17
18
19
20
21
22
23
24
25
26
|
# 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) ⇒ Object
28
29
30
31
|
# 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
|
.parse_out_key(hex) ⇒ Object
33
34
35
36
37
38
39
40
41
42
43
44
45
|
# 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
|
Instance Method Details
#chain_code_hex ⇒ Object
224
225
226
|
# File 'lib/money-tree/node.rb', line 224
def chain_code_hex
int_to_hex chain_code, 64
end
|
#depth_hex(depth) ⇒ Object
59
60
61
|
# 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) ⇒ Object
75
76
77
78
79
80
81
82
83
84
|
# 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 child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
raise InvalidKeyForIndex, 'equal to zero' if child_private_key == 0 child_chain_code = right_from_hash(hash)
return child_private_key, child_chain_code
end
|
#derive_public_key(i = 0) ⇒ Object
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
# 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 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 child_chain_code = right_from_hash(hash)
return child_public_key, child_chain_code
end
|
#i_as_bytes(i) ⇒ Object
71
72
73
|
# File 'lib/money-tree/node.rb', line 71
def i_as_bytes(i)
[i].pack('N')
end
|
#index_hex(i = index) ⇒ Object
51
52
53
54
55
56
57
|
# 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? ⇒ Boolean
47
48
49
|
# File 'lib/money-tree/node.rb', line 47
def is_private?
index >= 0x80000000 || index < 0
end
|
#left_from_hash(hash) ⇒ Object
104
105
106
|
# 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) ⇒ Object
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.
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
# 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 ⇒ Object
142
143
144
145
146
147
148
|
# 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) ⇒ Object
207
208
209
210
211
212
213
214
215
216
217
218
|
# 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) ⇒ Object
63
64
65
|
# 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) ⇒ Object
67
68
69
|
# 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) ⇒ Object
108
109
110
|
# 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! ⇒ Object
220
221
222
|
# File 'lib/money-tree/node.rb', line 220
def strip_private_info!
@private_key = nil
end
|
#subnode(i = 0, opts = {}) ⇒ Object
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
# 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) ⇒ Object
150
151
152
153
|
# 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) ⇒ Object
123
124
125
126
|
# 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 ⇒ Object
138
139
140
|
# File 'lib/money-tree/node.rb', line 138
def to_fingerprint
public_key.compressed.to_fingerprint
end
|
#to_identifier(compressed = true) ⇒ Object
133
134
135
136
|
# 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) ⇒ Object
128
129
130
131
|
# 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) ⇒ Object
112
113
114
115
116
117
118
119
120
121
|
# 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] hex += depth_hex(depth) hex += parent_fingerprint hex += index_hex(index) hex += chain_code_hex
hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.compressed.to_hex
end
|