Class: MoneyTree::Node
Direct Known Subclasses
Master
Defined Under Namespace
Classes: ImportError, InvalidKeyForIndex, PrivatePublicMismatch, PublicDerivationFailure
Constant Summary
collapse
- PRIVATE_RANGE_LIMIT =
0x80000000
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, convert_p2wpkh_p2sh, custom_hash_160, decode_base58, decode_base64, digestify, encode_base58, encode_base64, encode_p2wpkh_p2sh, 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, to_serialized_bech32
Constructor Details
#initialize(opts = {}) ⇒ Node
Returns a new instance of Node.
21
22
23
|
# File 'lib/money-tree/node.rb', line 21
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.
8
9
10
|
# File 'lib/money-tree/node.rb', line 8
def chain_code
@chain_code
end
|
#depth ⇒ Object
Returns the value of attribute depth.
10
11
12
|
# File 'lib/money-tree/node.rb', line 10
def depth
@depth
end
|
#index ⇒ Object
Returns the value of attribute index.
11
12
13
|
# File 'lib/money-tree/node.rb', line 11
def index
@index
end
|
#is_private ⇒ Object
Returns the value of attribute is_private.
9
10
11
|
# File 'lib/money-tree/node.rb', line 9
def is_private
@is_private
end
|
#parent ⇒ Object
Returns the value of attribute parent.
12
13
14
|
# File 'lib/money-tree/node.rb', line 12
def parent
@parent
end
|
#private_key ⇒ Object
Returns the value of attribute private_key.
6
7
8
|
# File 'lib/money-tree/node.rb', line 6
def private_key
@private_key
end
|
#public_key ⇒ Object
Returns the value of attribute public_key.
7
8
9
|
# File 'lib/money-tree/node.rb', line 7
def public_key
@public_key
end
|
Class Method Details
.from_bip32(address, has_version: true) ⇒ Object
25
26
27
28
29
30
31
32
33
34
|
# File 'lib/money-tree/node.rb', line 25
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
|
.parse_out_key(hex) ⇒ Object
36
37
38
39
40
41
42
43
44
45
46
47
48
|
# File 'lib/money-tree/node.rb', line 36
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
245
246
247
|
# File 'lib/money-tree/node.rb', line 245
def chain_code_hex
int_to_hex chain_code, 64
end
|
#depth_hex(depth) ⇒ Object
62
63
64
|
# File 'lib/money-tree/node.rb', line 62
def depth_hex(depth)
depth.to_s(16).rjust(2, "0")
end
|
#derive_parent_node(parent_key) ⇒ Object
89
90
91
92
93
94
95
96
97
98
|
# File 'lib/money-tree/node.rb', line 89
def derive_parent_node(parent_key)
message = parent_key.public_key.to_bytes << i_as_bytes(index)
hash = hmac_sha512 hex_to_bytes(parent_key.chain_code_hex), message
priv = (private_key.to_i - left_from_hash(hash)) % MoneyTree::Key::ORDER
MoneyTree::Node.new(depth: parent_key.depth,
index: parent_key.index,
private_key: MoneyTree::PrivateKey.new(key: priv),
public_key: parent_key.public_key,
chain_code: parent_key.chain_code)
end
|
#derive_private_key(i = 0) ⇒ Object
78
79
80
81
82
83
84
85
86
87
|
# File 'lib/money-tree/node.rb', line 78
def derive_private_key(i = 0)
message = i >= PRIVATE_RANGE_LIMIT || 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
# File 'lib/money-tree/node.rb', line 100
def derive_public_key(i = 0)
raise PrivatePublicMismatch if i >= PRIVATE_RANGE_LIMIT
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
74
75
76
|
# File 'lib/money-tree/node.rb', line 74
def i_as_bytes(i)
[i].pack("N")
end
|
#index_hex(i = index) ⇒ Object
54
55
56
57
58
59
60
|
# File 'lib/money-tree/node.rb', line 54
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
50
51
52
|
# File 'lib/money-tree/node.rb', line 50
def is_private?
index >= PRIVATE_RANGE_LIMIT || index < 0
end
|
#left_from_hash(hash) ⇒ Object
118
119
120
|
# File 'lib/money-tree/node.rb', line 118
def left_from_hash(hash)
bytes_to_int hash.bytes.to_a[0..31]
end
|
#negative?(path_part) ⇒ Boolean
226
227
228
229
230
231
232
233
|
# File 'lib/money-tree/node.rb', line 226
def negative?(path_part)
relative_index = path_part.to_i
prime_symbol_present = %w(p ').include?(path_part[-1])
minus_present = path_part[0] == "-"
private_range = relative_index >= PRIVATE_RANGE_LIMIT
negative_value = relative_index < 0
prime_symbol_present || minus_present || private_range || negative_value
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.
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
# File 'lib/money-tree/node.rb', line 203
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
151
152
153
154
155
156
157
|
# File 'lib/money-tree/node.rb', line 151
def parent_fingerprint
if @parent_fingerprint
@parent_fingerprint
else
depth.zero? ? "00000000" : parent.to_fingerprint
end
end
|
#parse_index(path_part) ⇒ Object
235
236
237
238
239
|
# File 'lib/money-tree/node.rb', line 235
def parse_index(path_part)
index = path_part.to_i
return index | PRIVATE_RANGE_LIMIT if negative?(path_part)
index
end
|
#private_derivation_message(i) ⇒ Object
66
67
68
|
# File 'lib/money-tree/node.rb', line 66
def private_derivation_message(i)
"\x00" + private_key.to_bytes + i_as_bytes(i)
end
|
#public_derivation_message(i) ⇒ Object
70
71
72
|
# File 'lib/money-tree/node.rb', line 70
def public_derivation_message(i)
public_key.to_bytes << i_as_bytes(i)
end
|
#right_from_hash(hash) ⇒ Object
122
123
124
|
# File 'lib/money-tree/node.rb', line 122
def right_from_hash(hash)
bytes_to_int hash.bytes.to_a[32..-1]
end
|
#strip_private_info! ⇒ Object
241
242
243
|
# File 'lib/money-tree/node.rb', line 241
def strip_private_info!
@private_key = nil
end
|
#subnode(i = 0, opts = {}) ⇒ Object
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
# File 'lib/money-tree/node.rb', line 174
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
159
160
161
162
|
# File 'lib/money-tree/node.rb', line 159
def to_address(compressed = true, network: :bitcoin)
address = NETWORKS[network][:address_version] + to_identifier(compressed)
to_serialized_base58 address
end
|
#to_bech32_address(network: :bitcoin) ⇒ Object
168
169
170
171
172
|
# File 'lib/money-tree/node.rb', line 168
def to_bech32_address(network: :bitcoin)
hrp = NETWORKS[network][:human_readable_part]
witprog = to_identifier
to_serialized_bech32(hrp, witprog)
end
|
#to_bip32(type = :public, network: :bitcoin) ⇒ Object
137
138
139
140
|
# File 'lib/money-tree/node.rb', line 137
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
147
148
149
|
# File 'lib/money-tree/node.rb', line 147
def to_fingerprint
public_key.compressed.to_fingerprint
end
|
#to_identifier(compressed = true) ⇒ Object
142
143
144
145
|
# File 'lib/money-tree/node.rb', line 142
def to_identifier(compressed = true)
key = compressed ? public_key.compressed : public_key.uncompressed
key.to_ripemd160
end
|
#to_p2wpkh_p2sh(network: :bitcoin) ⇒ Object
164
165
166
|
# File 'lib/money-tree/node.rb', line 164
def to_p2wpkh_p2sh(network: :bitcoin)
public_key.to_p2wpkh_p2sh(network: network)
end
|
#to_serialized_hex(type = :public, network: :bitcoin) ⇒ Object
126
127
128
129
130
131
132
133
134
135
|
# File 'lib/money-tree/node.rb', line 126
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
|