money-tree 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/money-tree/address.rb +2 -2
- data/lib/money-tree/key.rb +46 -67
- data/lib/money-tree/networks.rb +33 -26
- data/lib/money-tree/node.rb +71 -81
- data/lib/money-tree/version.rb +1 -1
- data/lib/openssl_extensions.rb +6 -0
- data/money-tree.gemspec +9 -0
- data/spec/lib/money-tree/address_spec.rb +1 -1
- data/spec/lib/money-tree/node_spec.rb +14 -14
- data/spec/lib/money-tree/private_key_spec.rb +2 -2
- data/spec/lib/money-tree/public_key_spec.rb +5 -5
- data/spec/spec_helper.rb +1 -0
- metadata +24 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b30fcb1b0feac18dde7d94b62a0ff97666209d3f
|
4
|
+
data.tar.gz: ffa45ca51da46d1d56d701f24e36d61848e261ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 859a1b5a4dfd21fafb73dba60ad6d243c3d8462bba900aed584934e463976fd0075abd5fa83b6317f54ce9801f342e3ba31ac639bb53ee9769068fd1f1cbda13
|
7
|
+
data.tar.gz: 3609f386e6fc5276f41a51221898dc0ebb10d314fd4b76a6ea4522ced950738b064167fb0f4d6a7d84c1b12823dd255494043e817b9dd7562f3f3eb4c02e093d
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/lib/money-tree/address.rb
CHANGED
data/lib/money-tree/key.rb
CHANGED
@@ -13,10 +13,10 @@ module MoneyTree
|
|
13
13
|
class KeyFormatNotFound < Exception; end
|
14
14
|
class InvalidWIFFormat < Exception; end
|
15
15
|
class InvalidBase64Format < Exception; end
|
16
|
-
|
17
|
-
attr_reader :options, :key, :raw_key
|
16
|
+
|
17
|
+
attr_reader :options, :key, :raw_key
|
18
18
|
attr_accessor :ec_key
|
19
|
-
|
19
|
+
|
20
20
|
GROUP_NAME = 'secp256k1'
|
21
21
|
ORDER = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".to_i(16)
|
22
22
|
|
@@ -24,23 +24,21 @@ module MoneyTree
|
|
24
24
|
eckey ||= ec_key
|
25
25
|
eckey.nil? ? false : eckey.check_key
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def to_bytes
|
29
29
|
hex_to_bytes to_hex
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def to_i
|
33
33
|
bytes_to_int to_bytes
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
class PrivateKey < Key
|
38
|
-
|
38
|
+
|
39
39
|
def initialize(opts = {})
|
40
40
|
@options = opts
|
41
41
|
@ec_key = PKey::EC.new GROUP_NAME
|
42
|
-
@network_key = options[:network] || :bitcoin
|
43
|
-
@network = MoneyTree::NETWORKS[network_key]
|
44
42
|
if @options[:key]
|
45
43
|
@raw_key = @options[:key]
|
46
44
|
@key = parse_raw_key
|
@@ -50,34 +48,34 @@ module MoneyTree
|
|
50
48
|
@key = to_hex
|
51
49
|
end
|
52
50
|
end
|
53
|
-
|
51
|
+
|
54
52
|
def generate
|
55
53
|
ec_key.generate_key
|
56
54
|
end
|
57
|
-
|
55
|
+
|
58
56
|
def import
|
59
57
|
ec_key.private_key = BN.new(key, 16)
|
60
58
|
set_public_key
|
61
59
|
end
|
62
|
-
|
60
|
+
|
63
61
|
def calculate_public_key(opts = {})
|
64
62
|
opts[:compressed] = true unless opts[:compressed] == false
|
65
63
|
group = ec_key.group
|
66
64
|
group.point_conversion_form = opts[:compressed] ? :compressed : :uncompressed
|
67
65
|
point = group.generator.mul ec_key.private_key
|
68
66
|
end
|
69
|
-
|
67
|
+
|
70
68
|
def set_public_key(opts = {})
|
71
69
|
ec_key.public_key = calculate_public_key(opts)
|
72
70
|
end
|
73
|
-
|
71
|
+
|
74
72
|
def parse_raw_key
|
75
73
|
result = if raw_key.is_a?(Bignum) then from_bignum
|
76
74
|
elsif hex_format? then from_hex
|
77
75
|
elsif base64_format? then from_base64
|
78
76
|
elsif compressed_wif_format? then from_wif
|
79
77
|
elsif uncompressed_wif_format? then from_wif
|
80
|
-
else
|
78
|
+
else
|
81
79
|
raise KeyFormatNotFound
|
82
80
|
end
|
83
81
|
result.downcase
|
@@ -91,25 +89,15 @@ module MoneyTree
|
|
91
89
|
def from_hex(hex = raw_key)
|
92
90
|
hex
|
93
91
|
end
|
94
|
-
|
92
|
+
|
95
93
|
def from_wif(wif = raw_key)
|
96
94
|
compressed = wif.length == 52
|
97
|
-
parse_network_from_wif(wif, compressed: compressed)
|
98
95
|
validate_wif(wif)
|
99
96
|
hex = decode_base58(wif)
|
100
97
|
last_char = compressed ? -11 : -9
|
101
98
|
hex.slice(2..last_char)
|
102
99
|
end
|
103
100
|
|
104
|
-
def parse_network_from_wif(wif, opts = {})
|
105
|
-
networks = MoneyTree::NETWORKS
|
106
|
-
chars_key = opts[:compressed] ? :compressed_wif_chars : :uncompressed_wif_chars
|
107
|
-
@network_key = networks.keys.select do |k|
|
108
|
-
networks[k][chars_key].include?(wif.slice(0))
|
109
|
-
end.first
|
110
|
-
@network = networks[network_key]
|
111
|
-
end
|
112
|
-
|
113
101
|
def from_base64(base64_key = raw_key)
|
114
102
|
raise InvalidBase64Format unless base64_format?(base64_key)
|
115
103
|
decode_base64(base64_key)
|
@@ -118,7 +106,7 @@ module MoneyTree
|
|
118
106
|
def compressed_wif_format?
|
119
107
|
wif_format?(:compressed)
|
120
108
|
end
|
121
|
-
|
109
|
+
|
122
110
|
def uncompressed_wif_format?
|
123
111
|
wif_format?(:uncompressed)
|
124
112
|
end
|
@@ -132,19 +120,18 @@ module MoneyTree
|
|
132
120
|
def base64_format?(base64_key = raw_key)
|
133
121
|
base64_key.length == 44 && base64_key =~ /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/
|
134
122
|
end
|
135
|
-
|
123
|
+
|
136
124
|
def hex_format?
|
137
125
|
raw_key.length == 64 && !raw_key[/\H/]
|
138
126
|
end
|
139
|
-
|
127
|
+
|
140
128
|
def to_hex
|
141
129
|
int_to_hex @ec_key.private_key, 64
|
142
130
|
end
|
143
|
-
|
144
|
-
def to_wif(
|
145
|
-
|
146
|
-
source
|
147
|
-
source += network[:privkey_compression_flag] if opts[:compressed]
|
131
|
+
|
132
|
+
def to_wif(compressed: true, network: :bitcoin)
|
133
|
+
source = NETWORKS[network][:privkey_version] + to_hex
|
134
|
+
source += NETWORKS[network][:privkey_compression_flag] if compressed
|
148
135
|
hash = sha256(source)
|
149
136
|
hash = sha256(hash)
|
150
137
|
checksum = hash.slice(0..7)
|
@@ -154,7 +141,6 @@ module MoneyTree
|
|
154
141
|
|
155
142
|
def wif_valid?(wif)
|
156
143
|
hex = decode_base58(wif)
|
157
|
-
return false unless hex.slice(0..1) == network[:privkey_version]
|
158
144
|
checksum = hex.chars.to_a.pop(8).join
|
159
145
|
source = hex.slice(0..-9)
|
160
146
|
hash = sha256(source)
|
@@ -162,68 +148,61 @@ module MoneyTree
|
|
162
148
|
hash_checksum = hash.slice(0..7)
|
163
149
|
checksum == hash_checksum
|
164
150
|
end
|
165
|
-
|
151
|
+
|
166
152
|
def validate_wif(wif)
|
167
153
|
raise InvalidWIFFormat unless wif_valid?(wif)
|
168
154
|
end
|
169
|
-
|
155
|
+
|
170
156
|
def to_base64
|
171
157
|
encode_base64(to_hex)
|
172
158
|
end
|
173
|
-
|
174
|
-
def to_s
|
175
|
-
to_wif
|
159
|
+
|
160
|
+
def to_s(network: :bitcoin)
|
161
|
+
to_wif(network: network)
|
176
162
|
end
|
177
|
-
|
163
|
+
|
178
164
|
end
|
179
|
-
|
165
|
+
|
180
166
|
class PublicKey < Key
|
181
167
|
attr_reader :private_key, :point, :group, :key_int
|
182
|
-
|
168
|
+
|
183
169
|
def initialize(p_key, opts = {})
|
184
170
|
@options = opts
|
185
171
|
@options[:compressed] = true if @options[:compressed].nil?
|
186
|
-
|
187
172
|
if p_key.is_a?(PrivateKey)
|
188
173
|
@private_key = p_key
|
189
|
-
@network_key = private_key.network_key
|
190
|
-
@network = MoneyTree::NETWORKS[network_key]
|
191
174
|
@point = @private_key.calculate_public_key(@options)
|
192
175
|
@group = @point.group
|
193
176
|
@key = @raw_key = to_hex
|
194
177
|
else
|
195
|
-
@network_key = @options[:network] || :bitcoin
|
196
|
-
@network = MoneyTree::NETWORKS[network_key]
|
197
178
|
@raw_key = p_key
|
198
179
|
@group = PKey::EC::Group.new GROUP_NAME
|
199
180
|
@key = parse_raw_key
|
200
181
|
end
|
201
182
|
|
202
|
-
@options[:network] = @network_key # remember for deep clone
|
203
|
-
|
204
183
|
raise ArgumentError, "Must initialize with a MoneyTree::PrivateKey or a public key value" if @key.nil?
|
205
184
|
end
|
206
|
-
|
185
|
+
|
207
186
|
def compression
|
208
187
|
@group.point_conversion_form
|
209
188
|
end
|
210
|
-
|
189
|
+
|
211
190
|
def compression=(compression_type = :compressed)
|
212
191
|
@group.point_conversion_form = compression_type
|
213
192
|
end
|
214
|
-
|
193
|
+
|
215
194
|
def compressed
|
216
195
|
compressed_key = self.class.new raw_key, options # deep clone
|
217
196
|
compressed_key.set_point to_i, compressed: true
|
218
197
|
compressed_key
|
219
198
|
end
|
220
|
-
|
199
|
+
|
221
200
|
def uncompressed
|
222
201
|
uncompressed_key = self.class.new raw_key, options # deep clone
|
223
202
|
uncompressed_key.set_point to_i, compressed: false
|
224
203
|
uncompressed_key
|
225
204
|
end
|
226
|
-
|
205
|
+
|
227
206
|
def set_point(int = to_i, opts = {})
|
228
207
|
opts = options.merge(opts)
|
229
208
|
opts[:compressed] = true if opts[:compressed].nil?
|
@@ -232,7 +211,7 @@ module MoneyTree
|
|
232
211
|
@point = PKey::EC::Point.new group, bn
|
233
212
|
raise KeyInvalid, 'point is not on the curve' unless @point.on_curve?
|
234
213
|
end
|
235
|
-
|
214
|
+
|
236
215
|
def parse_raw_key
|
237
216
|
result = if raw_key.is_a?(Bignum)
|
238
217
|
set_point raw_key
|
@@ -240,45 +219,45 @@ module MoneyTree
|
|
240
219
|
set_point hex_to_int(raw_key), compressed: false
|
241
220
|
elsif compressed_hex_format?
|
242
221
|
set_point hex_to_int(raw_key), compressed: true
|
243
|
-
else
|
222
|
+
else
|
244
223
|
raise KeyFormatNotFound
|
245
224
|
end
|
246
225
|
to_hex
|
247
226
|
end
|
248
|
-
|
227
|
+
|
249
228
|
def hex_format?
|
250
229
|
raw_key.length == 130 && !raw_key[/\H/]
|
251
230
|
end
|
252
|
-
|
231
|
+
|
253
232
|
def compressed_hex_format?
|
254
233
|
raw_key.length == 66 && !raw_key[/\H/]
|
255
234
|
end
|
256
|
-
|
235
|
+
|
257
236
|
def to_hex
|
258
237
|
int_to_hex to_i, 66
|
259
238
|
end
|
260
|
-
|
239
|
+
|
261
240
|
def to_i
|
262
241
|
point.to_bn.to_i
|
263
242
|
end
|
264
|
-
|
243
|
+
|
265
244
|
def to_ripemd160
|
266
245
|
hash = sha256 to_hex
|
267
246
|
ripemd160 hash
|
268
247
|
end
|
269
|
-
|
270
|
-
def to_address
|
248
|
+
|
249
|
+
def to_address(network: :bitcoin)
|
271
250
|
hash = to_ripemd160
|
272
|
-
address = network[:address_version] + hash
|
251
|
+
address = NETWORKS[network][:address_version] + hash
|
273
252
|
to_serialized_base58 address
|
274
253
|
end
|
275
254
|
alias :to_s :to_address
|
276
|
-
|
255
|
+
|
277
256
|
def to_fingerprint
|
278
257
|
hash = to_ripemd160
|
279
258
|
hash.slice(0..7)
|
280
259
|
end
|
281
|
-
|
260
|
+
|
282
261
|
def to_bytes
|
283
262
|
int_to_bytes to_i
|
284
263
|
end
|
data/lib/money-tree/networks.rb
CHANGED
@@ -1,28 +1,35 @@
|
|
1
1
|
module MoneyTree
|
2
|
-
NETWORKS =
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
2
|
+
NETWORKS =
|
3
|
+
begin
|
4
|
+
hsh = Hash.new do |_, key|
|
5
|
+
raise "#{key} is not a valid network!"
|
6
|
+
end.merge(
|
7
|
+
bitcoin: {
|
8
|
+
address_version: '00',
|
9
|
+
p2sh_version: '05',
|
10
|
+
p2sh_char: '3',
|
11
|
+
privkey_version: '80',
|
12
|
+
privkey_compression_flag: '01',
|
13
|
+
extended_privkey_version: "0488ade4",
|
14
|
+
extended_pubkey_version: "0488b21e",
|
15
|
+
compressed_wif_chars: %w(K L),
|
16
|
+
uncompressed_wif_chars: %w(5),
|
17
|
+
protocol_version: 70001
|
18
|
+
},
|
19
|
+
bitcoin_testnet: {
|
20
|
+
address_version: '6f',
|
21
|
+
p2sh_version: 'c4',
|
22
|
+
p2sh_char: '2',
|
23
|
+
privkey_version: 'ef',
|
24
|
+
privkey_compression_flag: '01',
|
25
|
+
extended_privkey_version: "04358394",
|
26
|
+
extended_pubkey_version: "043587cf",
|
27
|
+
compressed_wif_chars: %w(c),
|
28
|
+
uncompressed_wif_chars: %w(9),
|
29
|
+
protocol_version: 70001
|
30
|
+
}
|
31
|
+
)
|
32
|
+
hsh[:testnet3] = hsh[:bitcoin_testnet]
|
33
|
+
hsh
|
34
|
+
end
|
28
35
|
end
|
data/lib/money-tree/node.rb
CHANGED
@@ -2,62 +2,52 @@ module MoneyTree
|
|
2
2
|
class Node
|
3
3
|
include Support
|
4
4
|
extend Support
|
5
|
-
attr_reader :private_key, :public_key, :chain_code,
|
6
|
-
:is_private, :depth, :index, :parent
|
7
|
-
|
5
|
+
attr_reader :private_key, :public_key, :chain_code,
|
6
|
+
:is_private, :depth, :index, :parent
|
7
|
+
|
8
8
|
class PublicDerivationFailure < Exception; end
|
9
9
|
class InvalidKeyForIndex < Exception; end
|
10
10
|
class ImportError < Exception; end
|
11
11
|
class PrivatePublicMismatch < Exception; end
|
12
|
-
|
12
|
+
|
13
13
|
def initialize(opts = {})
|
14
|
-
@network_key = opts.delete(:network) || :bitcoin
|
15
|
-
@network = MoneyTree::NETWORKS[network_key]
|
16
14
|
opts.each { |k, v| instance_variable_set "@#{k}", v }
|
17
15
|
end
|
18
|
-
|
19
|
-
def self.
|
16
|
+
|
17
|
+
def self.from_bip32(address, has_version: true)
|
20
18
|
hex = from_serialized_base58 address
|
21
|
-
|
19
|
+
hex.slice!(0..7) if has_version
|
22
20
|
self.new({
|
23
21
|
depth: hex.slice!(0..1).to_i(16),
|
24
22
|
parent_fingerprint: hex.slice!(0..7),
|
25
23
|
index: hex.slice!(0..7).to_i(16),
|
26
24
|
chain_code: hex.slice!(0..63).to_i(16)
|
27
|
-
}.merge(
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
}.merge(parse_out_key(hex)))
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.from_serialized_address(address)
|
29
|
+
puts 'Node.from_serialized_address is DEPRECATED. Please use .from_bip32 instead.'
|
30
|
+
from_bip32(address)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.parse_out_key(hex)
|
34
|
+
if hex.slice(0..1) == '00'
|
35
|
+
private_key = MoneyTree::PrivateKey.new(key: hex.slice(2..-1))
|
36
|
+
{
|
37
|
+
private_key: private_key,
|
38
|
+
public_key: MoneyTree::PublicKey.new(private_key)
|
39
|
+
}
|
35
40
|
elsif %w(02 03).include? hex.slice(0..1)
|
36
|
-
|
41
|
+
{ public_key: MoneyTree::PublicKey.new(hex) }
|
37
42
|
else
|
38
43
|
raise ImportError, 'Public or private key data does not match version type'
|
39
44
|
end
|
40
45
|
end
|
41
|
-
|
42
|
-
def self.from_version_hex(hex)
|
43
|
-
case hex
|
44
|
-
when MoneyTree::NETWORKS[:bitcoin][:extended_privkey_version]
|
45
|
-
{ private_key: true, network: :bitcoin }
|
46
|
-
when MoneyTree::NETWORKS[:bitcoin][:extended_pubkey_version]
|
47
|
-
{ private_key: false, network: :bitcoin }
|
48
|
-
when MoneyTree::NETWORKS[:bitcoin_testnet][:extended_privkey_version]
|
49
|
-
{ private_key: true, network: :bitcoin_testnet }
|
50
|
-
when MoneyTree::NETWORKS[:bitcoin_testnet][:extended_pubkey_version]
|
51
|
-
{ private_key: false, network: :bitcoin_testnet }
|
52
|
-
else
|
53
|
-
raise ImportError, 'invalid version bytes'
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
46
|
+
|
57
47
|
def is_private?
|
58
48
|
index >= 0x80000000 || index < 0
|
59
49
|
end
|
60
|
-
|
50
|
+
|
61
51
|
def index_hex(i = index)
|
62
52
|
if i < 0
|
63
53
|
[i].pack('l>').unpack('H*').first
|
@@ -65,15 +55,15 @@ module MoneyTree
|
|
65
55
|
i.to_s(16).rjust(8, "0")
|
66
56
|
end
|
67
57
|
end
|
68
|
-
|
58
|
+
|
69
59
|
def depth_hex(depth)
|
70
60
|
depth.to_s(16).rjust(2, "0")
|
71
61
|
end
|
72
|
-
|
62
|
+
|
73
63
|
def private_derivation_message(i)
|
74
64
|
"\x00" + private_key.to_bytes + i_as_bytes(i)
|
75
65
|
end
|
76
|
-
|
66
|
+
|
77
67
|
def public_derivation_message(i)
|
78
68
|
public_key.to_bytes << i_as_bytes(i)
|
79
69
|
end
|
@@ -81,7 +71,7 @@ module MoneyTree
|
|
81
71
|
def i_as_bytes(i)
|
82
72
|
[i].pack('N')
|
83
73
|
end
|
84
|
-
|
74
|
+
|
85
75
|
def derive_private_key(i = 0)
|
86
76
|
message = i >= 0x80000000 || i < 0 ? private_derivation_message(i) : public_derivation_message(i)
|
87
77
|
hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
|
@@ -92,7 +82,7 @@ module MoneyTree
|
|
92
82
|
child_chain_code = right_from_hash(hash)
|
93
83
|
return child_private_key, child_chain_code
|
94
84
|
end
|
95
|
-
|
85
|
+
|
96
86
|
def derive_public_key(i = 0)
|
97
87
|
raise PrivatePublicMismatch if i >= 0x80000000
|
98
88
|
message = public_derivation_message(i)
|
@@ -105,40 +95,45 @@ module MoneyTree
|
|
105
95
|
child_chain_code = right_from_hash(hash)
|
106
96
|
return child_public_key, child_chain_code
|
107
97
|
end
|
108
|
-
|
98
|
+
|
109
99
|
def left_from_hash(hash)
|
110
100
|
bytes_to_int hash.bytes.to_a[0..31]
|
111
101
|
end
|
112
|
-
|
102
|
+
|
113
103
|
def right_from_hash(hash)
|
114
104
|
bytes_to_int hash.bytes.to_a[32..-1]
|
115
105
|
end
|
116
106
|
|
117
|
-
def to_serialized_hex(type = :public)
|
107
|
+
def to_serialized_hex(type = :public, network: :bitcoin)
|
118
108
|
raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
|
119
109
|
version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
|
120
|
-
hex = network[version_key] # version (4 bytes)
|
110
|
+
hex = NETWORKS[network][version_key] # version (4 bytes)
|
121
111
|
hex += depth_hex(depth) # depth (1 byte)
|
122
112
|
hex += parent_fingerprint # fingerprint of key (4 bytes)
|
123
113
|
hex += index_hex(index) # child number i (4 bytes)
|
124
114
|
hex += chain_code_hex
|
125
115
|
hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.compressed.to_hex
|
126
116
|
end
|
127
|
-
|
128
|
-
def
|
117
|
+
|
118
|
+
def to_bip32(type = :public, network: :bitcoin)
|
129
119
|
raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
|
130
|
-
to_serialized_base58 to_serialized_hex(type)
|
120
|
+
to_serialized_base58 to_serialized_hex(type, network: network)
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_serialized_address(type = :public, network: :bitcoin)
|
124
|
+
puts 'Node.to_serialized_address is DEPRECATED. Please use .to_bip32.'
|
125
|
+
to_bip32(type, network: network)
|
131
126
|
end
|
132
127
|
|
133
128
|
def to_identifier(compressed=true)
|
134
129
|
key = compressed ? public_key.compressed : public_key.uncompressed
|
135
130
|
key.to_ripemd160
|
136
131
|
end
|
137
|
-
|
132
|
+
|
138
133
|
def to_fingerprint
|
139
134
|
public_key.compressed.to_fingerprint
|
140
135
|
end
|
141
|
-
|
136
|
+
|
142
137
|
def parent_fingerprint
|
143
138
|
if @parent_fingerprint
|
144
139
|
@parent_fingerprint
|
@@ -147,40 +142,39 @@ module MoneyTree
|
|
147
142
|
end
|
148
143
|
end
|
149
144
|
|
150
|
-
def to_address(compressed=true)
|
151
|
-
address = network[:address_version] + to_identifier(compressed)
|
145
|
+
def to_address(compressed=true, network: :bitcoin)
|
146
|
+
address = NETWORKS[network][:address_version] + to_identifier(compressed)
|
152
147
|
to_serialized_base58 address
|
153
148
|
end
|
154
|
-
|
149
|
+
|
155
150
|
def subnode(i = 0, opts = {})
|
156
151
|
if private_key.nil?
|
157
152
|
child_public_key, child_chain_code = derive_public_key(i)
|
158
|
-
child_public_key = MoneyTree::PublicKey.new child_public_key
|
153
|
+
child_public_key = MoneyTree::PublicKey.new child_public_key
|
159
154
|
else
|
160
155
|
child_private_key, child_chain_code = derive_private_key(i)
|
161
|
-
child_private_key = MoneyTree::PrivateKey.new key: child_private_key
|
156
|
+
child_private_key = MoneyTree::PrivateKey.new key: child_private_key
|
162
157
|
child_public_key = MoneyTree::PublicKey.new child_private_key
|
163
158
|
end
|
164
|
-
|
165
|
-
MoneyTree::Node.new
|
166
|
-
|
167
|
-
index: i,
|
159
|
+
|
160
|
+
MoneyTree::Node.new( depth: depth+1,
|
161
|
+
index: i,
|
168
162
|
private_key: private_key.nil? ? nil : child_private_key,
|
169
163
|
public_key: child_public_key,
|
170
164
|
chain_code: child_chain_code,
|
171
|
-
parent: self
|
165
|
+
parent: self)
|
172
166
|
end
|
173
|
-
|
167
|
+
|
174
168
|
# path: a path of subkeys denoted by numbers and slashes. Use
|
175
169
|
# p or i<0 for private key derivation. End with .pub to force
|
176
170
|
# the key public.
|
177
|
-
#
|
171
|
+
#
|
178
172
|
# Examples:
|
179
173
|
# 1p/-5/2/1 would call subkey(i=1, is_prime=True).subkey(i=-5).
|
180
174
|
# subkey(i=2).subkey(i=1) and then yield the private key
|
181
175
|
# 0/0/458.pub would call subkey(i=0).subkey(i=0).subkey(i=458) and
|
182
176
|
# then yield the public key
|
183
|
-
#
|
177
|
+
#
|
184
178
|
# You should choose either the p or the negative number convention for private key derivation.
|
185
179
|
def node_for_path(path)
|
186
180
|
force_public = path[-4..-1] == '.pub'
|
@@ -204,11 +198,11 @@ module MoneyTree
|
|
204
198
|
nodes.last
|
205
199
|
end
|
206
200
|
end
|
207
|
-
|
201
|
+
|
208
202
|
def parse_index(path_part)
|
209
203
|
is_prime = %w(p ').include? path_part[-1]
|
210
204
|
i = path_part.to_i
|
211
|
-
|
205
|
+
|
212
206
|
i = if i < 0
|
213
207
|
i
|
214
208
|
elsif is_prime
|
@@ -217,16 +211,16 @@ module MoneyTree
|
|
217
211
|
i & 0x7fffffff
|
218
212
|
end
|
219
213
|
end
|
220
|
-
|
214
|
+
|
221
215
|
def strip_private_info!
|
222
216
|
@private_key = nil
|
223
217
|
end
|
224
|
-
|
218
|
+
|
225
219
|
def chain_code_hex
|
226
220
|
int_to_hex chain_code, 64
|
227
221
|
end
|
228
222
|
end
|
229
|
-
|
223
|
+
|
230
224
|
class Master < Node
|
231
225
|
module SeedGeneration
|
232
226
|
class Failure < Exception; end
|
@@ -236,18 +230,16 @@ module MoneyTree
|
|
236
230
|
class ImportError < Failure; end
|
237
231
|
class TooManyAttempts < Failure; end
|
238
232
|
end
|
239
|
-
|
233
|
+
|
240
234
|
HD_WALLET_BASE_KEY = "Bitcoin seed"
|
241
235
|
RANDOM_SEED_SIZE = 32
|
242
|
-
|
236
|
+
|
243
237
|
attr_reader :seed, :seed_hash
|
244
|
-
|
238
|
+
|
245
239
|
def initialize(opts = {})
|
246
240
|
@depth = 0
|
247
241
|
@index = 0
|
248
242
|
opts[:seed] = [opts[:seed_hex]].pack("H*") if opts[:seed_hex]
|
249
|
-
@network_key = opts[:network] || :bitcoin
|
250
|
-
@network = MoneyTree::NETWORKS[network_key]
|
251
243
|
if opts[:seed]
|
252
244
|
@seed = opts[:seed]
|
253
245
|
@seed_hash = generate_seed_hash(@seed)
|
@@ -258,14 +250,12 @@ module MoneyTree
|
|
258
250
|
@chain_code = opts[:chain_code]
|
259
251
|
if opts[:private_key]
|
260
252
|
@private_key = opts[:private_key]
|
261
|
-
@network_key = @private_key.network_key
|
262
|
-
@network = MoneyTree::NETWORKS[network_key]
|
263
253
|
@public_key = MoneyTree::PublicKey.new @private_key
|
264
254
|
else opts[:public_key]
|
265
255
|
@public_key = if opts[:public_key].is_a?(MoneyTree::PublicKey)
|
266
256
|
opts[:public_key]
|
267
257
|
else
|
268
|
-
MoneyTree::PublicKey.new(opts[:public_key]
|
258
|
+
MoneyTree::PublicKey.new(opts[:public_key])
|
269
259
|
end
|
270
260
|
end
|
271
261
|
else
|
@@ -273,29 +263,29 @@ module MoneyTree
|
|
273
263
|
set_seeded_keys
|
274
264
|
end
|
275
265
|
end
|
276
|
-
|
266
|
+
|
277
267
|
def is_private?
|
278
268
|
true
|
279
269
|
end
|
280
|
-
|
270
|
+
|
281
271
|
def generate_seed
|
282
272
|
@seed = OpenSSL::Random.random_bytes(32)
|
283
273
|
@seed_hash = generate_seed_hash(@seed)
|
284
274
|
raise SeedGeneration::ValidityError unless seed_valid?(@seed_hash)
|
285
275
|
end
|
286
|
-
|
276
|
+
|
287
277
|
def generate_seed_hash(seed)
|
288
278
|
hmac_sha512 HD_WALLET_BASE_KEY, seed
|
289
279
|
end
|
290
|
-
|
280
|
+
|
291
281
|
def seed_valid?(seed_hash)
|
292
282
|
return false unless seed_hash.bytesize == 64
|
293
283
|
master_key = left_from_hash(seed_hash)
|
294
284
|
!master_key.zero? && master_key < MoneyTree::Key::ORDER
|
295
285
|
end
|
296
|
-
|
286
|
+
|
297
287
|
def set_seeded_keys
|
298
|
-
@private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
|
288
|
+
@private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
|
299
289
|
@chain_code = right_from_hash(seed_hash)
|
300
290
|
@public_key = MoneyTree::PublicKey.new @private_key
|
301
291
|
end
|
data/lib/money-tree/version.rb
CHANGED
data/lib/openssl_extensions.rb
CHANGED
data/money-tree.gemspec
CHANGED
@@ -18,6 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
# used with gem i coin-op -P HighSecurity
|
22
|
+
spec.cert_chain = ["certs/jvergeldedios.pem"]
|
23
|
+
# Sign gem when evaluating spec with `gem` command
|
24
|
+
# unless ENV has set a SKIP_GEM_SIGNING
|
25
|
+
if ($0 =~ /gem\z/) and not ENV.include?("SKIP_GEM_SIGNING")
|
26
|
+
spec.signing_key = File.join(Gem.user_home, ".ssh", "gem-private_key.pem")
|
27
|
+
end
|
28
|
+
|
29
|
+
|
21
30
|
spec.add_dependency "ffi"
|
22
31
|
|
23
32
|
spec.add_development_dependency "bundler", "~> 1.3"
|
@@ -24,49 +24,49 @@ describe MoneyTree::Master do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it "generates testnet address" do
|
27
|
-
expect(%w(m n)).to include(@master.to_address[0])
|
27
|
+
expect(%w(m n)).to include(@master.to_address(network: :bitcoin_testnet)[0])
|
28
28
|
end
|
29
29
|
|
30
30
|
it "generates testnet compressed wif" do
|
31
|
-
expect(@master.private_key.to_wif[0]).to eql('c')
|
31
|
+
expect(@master.private_key.to_wif(network: :bitcoin_testnet)[0]).to eql('c')
|
32
32
|
end
|
33
33
|
|
34
34
|
it "generates testnet uncompressed wif" do
|
35
|
-
expect(@master.private_key.to_wif(compressed: false)[0]).to eql('9')
|
35
|
+
expect(@master.private_key.to_wif(compressed: false, network: :bitcoin_testnet)[0]).to eql('9')
|
36
36
|
end
|
37
37
|
|
38
38
|
it "generates testnet serialized private address" do
|
39
|
-
expect(@master.to_serialized_address(:private).slice(0, 4)).to eql("tprv")
|
39
|
+
expect(@master.to_serialized_address(:private, network: :bitcoin_testnet).slice(0, 4)).to eql("tprv")
|
40
40
|
end
|
41
41
|
|
42
42
|
it "generates testnet serialized public address" do
|
43
|
-
expect(@master.to_serialized_address.slice(0, 4)).to eql("tpub")
|
43
|
+
expect(@master.to_serialized_address(network: :bitcoin_testnet).slice(0, 4)).to eql("tpub")
|
44
44
|
end
|
45
45
|
|
46
46
|
it "imports from testnet serialized private address" do
|
47
47
|
node = MoneyTree::Node.from_serialized_address 'tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE'
|
48
|
-
expect(node.to_serialized_address(:private)).to eql('tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE')
|
48
|
+
expect(node.to_serialized_address(:private, network: :bitcoin_testnet)).to eql('tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE')
|
49
49
|
end
|
50
50
|
|
51
51
|
it "imports from testnet serialized public address" do
|
52
52
|
node = MoneyTree::Node.from_serialized_address 'tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT'
|
53
|
-
expect(%w(m n)).to include(node.public_key.to_s[0])
|
54
|
-
expect(node.to_serialized_address).to eql('tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT')
|
53
|
+
expect(%w(m n)).to include(node.public_key.to_s(network: :bitcoin_testnet)[0])
|
54
|
+
expect(node.to_serialized_address(network: :bitcoin_testnet)).to eql('tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT')
|
55
55
|
end
|
56
56
|
|
57
57
|
it "generates testnet subnodes from serialized private address" do
|
58
58
|
node = MoneyTree::Node.from_serialized_address 'tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE'
|
59
59
|
subnode = node.node_for_path('1/1/1')
|
60
|
-
expect(%w(m n)).to include(subnode.public_key.to_s[0])
|
61
|
-
expect(subnode.to_serialized_address(:private).slice(0,4)).to eql('tprv')
|
62
|
-
expect(subnode.to_serialized_address.slice(0,4)).to eql('tpub')
|
60
|
+
expect(%w(m n)).to include(subnode.public_key.to_s(network: :bitcoin_testnet)[0])
|
61
|
+
expect(subnode.to_serialized_address(:private, network: :bitcoin_testnet).slice(0,4)).to eql('tprv')
|
62
|
+
expect(subnode.to_serialized_address(network: :bitcoin_testnet).slice(0,4)).to eql('tpub')
|
63
63
|
end
|
64
64
|
|
65
65
|
it "generates testnet subnodes from serialized public address" do
|
66
66
|
node = MoneyTree::Node.from_serialized_address 'tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT'
|
67
67
|
subnode = node.node_for_path('1/1/1')
|
68
|
-
expect(%w(m n)).to include(subnode.public_key.to_s[0])
|
69
|
-
expect(subnode.to_serialized_address.slice(0,4)).to eql('tpub')
|
68
|
+
expect(%w(m n)).to include(subnode.public_key.to_s(network: :bitcoin_testnet)[0])
|
69
|
+
expect(subnode.to_serialized_address(network: :bitcoin_testnet).slice(0,4)).to eql('tpub')
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -799,7 +799,7 @@ describe MoneyTree::Master do
|
|
799
799
|
it "correctly derives from a node with a chain code represented in 31 bytes" do
|
800
800
|
@node = MoneyTree::Node.from_serialized_address "tpubD6NzVbkrYhZ4WM42MZZmUZ7LjxyjBf5bGjEeLf9nJnMZqocGJWu94drvpqWsE9jE7k3h22v6gjpPGnqgBrqwGsRYwDXVRfQ2M9dfHbXP5zA"
|
801
801
|
@subnode = @node.node_for_path('m/1')
|
802
|
-
expect(@subnode.to_serialized_address).to eql("tpubDA7bCxb3Nrcz2ChXyPqXxbG4q5oiAZUHR7wD3LAiXukuxmT65weWw84XYmjhkJTkJEM6LhNWioWTpKEkQp7j2fgVccj3PPc271xHDeMsaTY")
|
802
|
+
expect(@subnode.to_serialized_address(network: :bitcoin_testnet)).to eql("tpubDA7bCxb3Nrcz2ChXyPqXxbG4q5oiAZUHR7wD3LAiXukuxmT65weWw84XYmjhkJTkJEM6LhNWioWTpKEkQp7j2fgVccj3PPc271xHDeMsaTY")
|
803
803
|
end
|
804
804
|
end
|
805
805
|
end
|
@@ -45,7 +45,7 @@ describe MoneyTree::PrivateKey do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it "is valid" do
|
48
|
-
expect(@key.to_wif(compressed: false)).to eql('5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa'
|
48
|
+
expect(@key.to_wif(compressed: false)).to eql('5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa')
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -104,7 +104,7 @@ describe MoneyTree::PrivateKey do
|
|
104
104
|
|
105
105
|
describe "to_wif" do
|
106
106
|
it "returns same wif" do
|
107
|
-
expect(@key.to_wif).to eql('cRhes8SBnsF6WizphaRKQKZZfDniDa9Bxcw31yKeEC1KDExhxFgD')
|
107
|
+
expect(@key.to_wif(network: :bitcoin_testnet)).to eql('cRhes8SBnsF6WizphaRKQKZZfDniDa9Bxcw31yKeEC1KDExhxFgD')
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
@@ -161,26 +161,26 @@ describe MoneyTree::PublicKey do
|
|
161
161
|
context "testnet" do
|
162
162
|
context 'with private key' do
|
163
163
|
before do
|
164
|
-
@private_key = MoneyTree::PrivateKey.new
|
164
|
+
@private_key = MoneyTree::PrivateKey.new
|
165
165
|
@key = MoneyTree::PublicKey.new(@private_key)
|
166
166
|
end
|
167
167
|
|
168
168
|
it "should have an address starting with m or n" do
|
169
|
-
expect(%w(m n)).to include(@key.to_s[0])
|
169
|
+
expect(%w(m n)).to include(@key.to_s(network: :bitcoin_testnet)[0])
|
170
170
|
end
|
171
171
|
|
172
172
|
it "should have an uncompressed address starting with m or n" do
|
173
|
-
expect(%w(m n)).to include(@key.uncompressed.to_s[0])
|
173
|
+
expect(%w(m n)).to include(@key.uncompressed.to_s(network: :bitcoin_testnet)[0])
|
174
174
|
end
|
175
175
|
end
|
176
176
|
|
177
177
|
context 'without private key' do
|
178
178
|
before do
|
179
|
-
@key = MoneyTree::PublicKey.new('0297b033ba894611345a0e777861237ef1632370fbd58ebe644eb9f3714e8fe2bc'
|
179
|
+
@key = MoneyTree::PublicKey.new('0297b033ba894611345a0e777861237ef1632370fbd58ebe644eb9f3714e8fe2bc')
|
180
180
|
end
|
181
181
|
|
182
182
|
it "should have an address starting with m or n" do
|
183
|
-
expect(%w(m n)).to include(@key.to_s[0])
|
183
|
+
expect(%w(m n)).to include(@key.to_s(network: :bitcoin_testnet)[0])
|
184
184
|
end
|
185
185
|
end
|
186
186
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money-tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micah Winkelspecht
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
|
-
cert_chain:
|
11
|
-
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDXDCCAkSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA6MQ8wDQYDVQQDDAZqdWxp
|
14
|
+
YW4xEzARBgoJkiaJk/IsZAEZFgNnZW0xEjAQBgoJkiaJk/IsZAEZFgJjbzAeFw0x
|
15
|
+
NTA0MDMwODAwMTdaFw0xNjA0MDIwODAwMTdaMDoxDzANBgNVBAMMBmp1bGlhbjET
|
16
|
+
MBEGCgmSJomT8ixkARkWA2dlbTESMBAGCgmSJomT8ixkARkWAmNvMIIBIjANBgkq
|
17
|
+
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0qCsOUQyTRA4f7WxoU2ctpUO5+eLZw8
|
18
|
+
ILvHJy/0dC2nnIgib+FaFA8TRIGw6fjX2hQ43QJyO36zkjUhAwNu/+TBCfG+Grut
|
19
|
+
2dI9XmqU5Z6PvvXRj6Gu5IkDeIVDKILZv3bDugHJalre4BUKnwPYv5WpZ/e/c6+z
|
20
|
+
E4fwe4ZQzqslSXZo0o/wFvs5dGuIoP93bazSeqddre0JKFFiEP/SNGP9e/lXEd2V
|
21
|
+
rLFYAY409no9J+VQOHP0Nu9ShlCZp8M45abKd2ykuSDaT6jH9YcUHBr3/IEsA4+f
|
22
|
+
DypeS1ySVvad+o8iTnfz1Hyohz4ORm3spf0BOtGI/Swbv3LObZJqkwIDAQABo20w
|
23
|
+
azAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUERhBDW/kkq7tz2hN
|
24
|
+
hPNHtounnkowGAYDVR0RBBEwD4ENanVsaWFuQGdlbS5jbzAYBgNVHRIEETAPgQ1q
|
25
|
+
dWxpYW5AZ2VtLmNvMA0GCSqGSIb3DQEBBQUAA4IBAQAgnumhg8ST8JohYWcoDoQt
|
26
|
+
3BUG5rbfJ/qE0utOt6esi9d6Vz6YHpiT08woaj68OWl9U9N4vjox+ckkTRs93KBd
|
27
|
+
y3thnK9cIEAzoEZs3BBguXYOoFLughGD7hEuLlRYbwZzyIdzx/XdLgsy5Di8Gqaa
|
28
|
+
RKurfXP+dERQww34CUhmhOLO4/rYGqaD88so0MzCImgS+OX+G4ppqd38iQpaxCHL
|
29
|
+
tdc4VS7IlSRxlZ3dBOgiigy9GXpJ+7F831AqjxL39EPwdr7RguTNz+pi//RKaT/U
|
30
|
+
IlpVB+Xfk0vQdP7iYfjGxDzUf0FACMjsR95waJmadKW1Iy6STw2hwPhYIQz1Hu1A
|
31
|
+
-----END CERTIFICATE-----
|
32
|
+
date: 2015-05-19 00:00:00.000000000 Z
|
12
33
|
dependencies:
|
13
34
|
- !ruby/object:Gem::Dependency
|
14
35
|
name: ffi
|
metadata.gz.sig
ADDED
Binary file
|