money-tree 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|