money-tree 0.0.2 → 0.0.3
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
- data/.rspec +1 -0
- data/README.md +1 -1
- data/lib/money-tree/key.rb +82 -29
- data/lib/money-tree/node.rb +65 -33
- data/lib/money-tree/support.rb +16 -12
- data/lib/money-tree/version.rb +1 -1
- data/lib/money-tree.rb +1 -0
- data/lib/openssl_extensions.rb +52 -0
- data/money-tree.gemspec +2 -0
- data/spec/lib/money-tree/node_spec.rb +49 -1
- data/spec/lib/money-tree/public_key_spec.rb +87 -32
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd86557ea3684c83ffae3308afb125c6bcea3af7
|
4
|
+
data.tar.gz: 3d74748c57a91fff5b027f11a9f8eaa16f0740e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d996d51da2dc16fe070bb5d68f192c56fb21709d8da2f80a6f952346869641cddc84f74dde1fe9735436a952be175284875a5995bfb1adfe372e7090eb9f50d
|
7
|
+
data.tar.gz: a7510386042a6a1367773cea6948a4a5a0cf455e5434d9e0446f1b106a783092c21a52c95a7606f0762bbf6086d6d09596565035b45d02b1bf528bbf80a000fc
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format documentation
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](https://travis-ci.org/wink/money-tree) [](https://coveralls.io/r/wink/money-tree?branch=master) [](http://badge.fury.io/rb/money-tree)
|
1
|
+
[](https://travis-ci.org/wink/money-tree) [](https://coveralls.io/r/wink/money-tree?branch=master) [](https://codeclimate.com/github/wink/money-tree) [](http://badge.fury.io/rb/money-tree)
|
2
2
|
# MoneyTree
|
3
3
|
|
4
4
|
MoneyTree is a Ruby implementation of Bitcoin Wallets. Specifically, it supports [Hierachical Deterministic wallets](https://en.bitcoin.it/wiki/Deterministic_Wallet) according to the protocol specified in [BIP0032](https://en.bitcoin.it/wiki/BIP_0032).
|
data/lib/money-tree/key.rb
CHANGED
@@ -14,14 +14,12 @@ module MoneyTree
|
|
14
14
|
class InvalidWIFFormat < Exception; end
|
15
15
|
class InvalidBase64Format < Exception; end
|
16
16
|
|
17
|
-
attr_reader :options, :key
|
17
|
+
attr_reader :options, :key, :raw_key
|
18
18
|
attr_accessor :ec_key
|
19
19
|
|
20
20
|
GROUP_NAME = 'secp256k1'
|
21
|
-
GROUP_UNCOMPRESSED = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".to_i(16)
|
22
|
-
GROUP_COMPRESSED = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".to_i(16)
|
23
21
|
ORDER = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".to_i(16)
|
24
|
-
|
22
|
+
|
25
23
|
def valid?(eckey = nil)
|
26
24
|
eckey ||= ec_key
|
27
25
|
eckey.nil? ? false : eckey.check_key
|
@@ -37,13 +35,11 @@ module MoneyTree
|
|
37
35
|
end
|
38
36
|
|
39
37
|
class PrivateKey < Key
|
40
|
-
|
41
|
-
attr_reader :raw_key
|
42
|
-
|
38
|
+
|
43
39
|
def initialize(opts = {})
|
44
40
|
@options = opts
|
45
41
|
# @ec_key = EC_KEY_new_by_curve_name(NID_secp256k1)
|
46
|
-
@ec_key = PKey::EC.new
|
42
|
+
@ec_key = PKey::EC.new GROUP_NAME
|
47
43
|
if @options[:key]
|
48
44
|
@raw_key = @options[:key]
|
49
45
|
@key = parse_raw_key
|
@@ -156,46 +152,103 @@ module MoneyTree
|
|
156
152
|
end
|
157
153
|
|
158
154
|
class PublicKey < Key
|
159
|
-
attr_reader :private_key
|
155
|
+
attr_reader :private_key, :point, :group, :key_int
|
160
156
|
|
161
157
|
def initialize(p_key, opts = {})
|
162
|
-
raise "Must initialize with a MoneyTree::PrivateKey" unless p_key.is_a?(PrivateKey)
|
163
|
-
@private_key = p_key
|
164
|
-
@ec_key = @private_key.ec_key
|
165
158
|
@options = opts
|
166
|
-
@
|
159
|
+
@options[:compressed] = true if @options[:compressed].nil?
|
160
|
+
|
161
|
+
if p_key.is_a?(PrivateKey)
|
162
|
+
@private_key = p_key
|
163
|
+
@point = @private_key.calculate_public_key(@options)
|
164
|
+
@group = @point.group
|
165
|
+
@key = @raw_key = to_hex
|
166
|
+
else
|
167
|
+
@raw_key = p_key
|
168
|
+
@group = PKey::EC::Group.new GROUP_NAME
|
169
|
+
@key = parse_raw_key
|
170
|
+
# set_point
|
171
|
+
end
|
172
|
+
raise ArgumentError, "Must initialize with a MoneyTree::PrivateKey or a public key value" if @key.nil?
|
173
|
+
end
|
174
|
+
|
175
|
+
def compression
|
176
|
+
@group.point_conversion_form
|
167
177
|
end
|
168
178
|
|
169
|
-
def
|
170
|
-
|
179
|
+
def compression=(compression_type = :compressed)
|
180
|
+
@group.point_conversion_form = compression_type
|
171
181
|
end
|
172
182
|
|
173
|
-
def
|
174
|
-
|
183
|
+
def compressed
|
184
|
+
compressed_key = self.dup
|
185
|
+
compressed_key.set_point to_i, compressed: true
|
186
|
+
compressed_key
|
175
187
|
end
|
176
188
|
|
177
|
-
def
|
178
|
-
|
189
|
+
def uncompressed
|
190
|
+
uncompressed_key = self.dup
|
191
|
+
uncompressed_key.set_point to_i, compressed: false
|
192
|
+
uncompressed_key
|
193
|
+
end
|
194
|
+
|
195
|
+
def set_point(int = to_i, opts = {})
|
196
|
+
opts = options.merge(opts)
|
197
|
+
opts[:compressed] = true if opts[:compressed].nil?
|
198
|
+
self.compression = opts[:compressed] ? :compressed : :uncompressed
|
199
|
+
bn = BN.new int_to_hex(int), 16
|
200
|
+
@point = PKey::EC::Point.new group, bn
|
201
|
+
raise KeyInvalid, 'point is not on the curve' unless @point.on_curve?
|
202
|
+
end
|
203
|
+
|
204
|
+
def parse_raw_key
|
205
|
+
result = if raw_key.is_a?(Bignum)
|
206
|
+
set_point raw_key
|
207
|
+
elsif hex_format?
|
208
|
+
set_point hex_to_int(raw_key), compressed: false
|
209
|
+
elsif compressed_hex_format?
|
210
|
+
set_point hex_to_int(raw_key), compressed: true
|
211
|
+
else
|
212
|
+
raise KeyFormatNotFound
|
213
|
+
end
|
214
|
+
to_hex
|
215
|
+
end
|
216
|
+
|
217
|
+
def hex_format?
|
218
|
+
raw_key.length == 130 && !raw_key[/\H/]
|
219
|
+
end
|
220
|
+
|
221
|
+
def compressed_hex_format?
|
222
|
+
raw_key.length == 66 && !raw_key[/\H/]
|
223
|
+
end
|
224
|
+
|
225
|
+
def to_hex
|
226
|
+
int_to_hex to_i
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_i
|
230
|
+
point.to_bn.to_i
|
231
|
+
end
|
232
|
+
|
233
|
+
def to_ripemd160
|
234
|
+
hash = sha256 to_hex
|
179
235
|
ripemd160 hash
|
180
236
|
end
|
181
237
|
|
182
|
-
def to_address
|
183
|
-
hash = to_ripemd160
|
238
|
+
def to_address
|
239
|
+
hash = to_ripemd160
|
184
240
|
address = MoneyTree::NETWORKS[:bitcoin][:address_version] + hash
|
185
241
|
to_serialized_base58 address
|
186
242
|
end
|
243
|
+
alias :to_s :to_address
|
187
244
|
|
188
|
-
def to_fingerprint
|
189
|
-
hash = to_ripemd160
|
245
|
+
def to_fingerprint
|
246
|
+
hash = to_ripemd160
|
190
247
|
hash.slice(0..7)
|
191
248
|
end
|
192
|
-
|
193
|
-
def to_s(opts = {})
|
194
|
-
to_address(opts)
|
195
|
-
end
|
196
249
|
|
197
|
-
def to_bytes
|
198
|
-
int_to_bytes to_i
|
250
|
+
def to_bytes
|
251
|
+
int_to_bytes to_i
|
199
252
|
end
|
200
253
|
end
|
201
254
|
end
|
data/lib/money-tree/node.rb
CHANGED
@@ -5,6 +5,8 @@ module MoneyTree
|
|
5
5
|
|
6
6
|
class PublicDerivationFailure < Exception; end
|
7
7
|
class InvalidKeyForIndex < Exception; end
|
8
|
+
class ImportError < Exception; end
|
9
|
+
class PrivatePublicMismatch < Exception; end
|
8
10
|
|
9
11
|
def initialize(opts = {})
|
10
12
|
@depth = opts[:depth]
|
@@ -16,6 +18,10 @@ module MoneyTree
|
|
16
18
|
@parent = opts[:parent]
|
17
19
|
end
|
18
20
|
|
21
|
+
def is_private?
|
22
|
+
is_private == true
|
23
|
+
end
|
24
|
+
|
19
25
|
def index_hex(i = index)
|
20
26
|
if i < 0
|
21
27
|
[i].pack('l>').unpack('H*').first
|
@@ -28,34 +34,42 @@ module MoneyTree
|
|
28
34
|
depth.to_s(16).rjust(2, "0")
|
29
35
|
end
|
30
36
|
|
31
|
-
def
|
37
|
+
def private_derivation_message(i)
|
32
38
|
"\x00" + private_key.to_bytes + i_as_bytes(i)
|
33
39
|
end
|
34
40
|
|
35
|
-
def
|
41
|
+
def public_derivation_message(i)
|
36
42
|
public_key.to_bytes << i_as_bytes(i)
|
37
43
|
end
|
38
|
-
|
39
|
-
# TODO: Complete public key derivation message
|
40
|
-
# def public_derivation_public_key_message(i)
|
41
|
-
# public_key.to_bytes + i_as_bytes(i)
|
42
|
-
# end
|
43
|
-
|
44
|
+
|
44
45
|
def i_as_bytes(i)
|
45
46
|
[i].pack('N')
|
46
47
|
end
|
47
48
|
|
48
49
|
def derive_private_key(i = 0)
|
49
|
-
message = i >= 0x80000000 ?
|
50
|
+
message = i >= 0x80000000 ? private_derivation_message(i) : public_derivation_message(i)
|
50
51
|
hash = hmac_sha512 int_to_bytes(chain_code), message
|
51
52
|
left_int = left_from_hash(hash)
|
52
|
-
raise InvalidKeyForIndex if left_int >= MoneyTree::Key::ORDER # very low probability
|
53
|
+
raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
|
53
54
|
child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
|
54
|
-
raise InvalidKeyForIndex if child_private_key == 0 # very low probability
|
55
|
+
raise InvalidKeyForIndex, 'equal to zero' if child_private_key == 0 # very low probability
|
55
56
|
child_chain_code = right_from_hash(hash)
|
56
57
|
return child_private_key, child_chain_code
|
57
58
|
end
|
58
59
|
|
60
|
+
def derive_public_key(i = 0)
|
61
|
+
raise PrivatePublicMismatch if i >= 0x80000000
|
62
|
+
message = public_derivation_message(i)
|
63
|
+
hash = hmac_sha512 int_to_bytes(chain_code), message
|
64
|
+
left_int = left_from_hash(hash)
|
65
|
+
raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
|
66
|
+
factor = BN.new left_int.to_s
|
67
|
+
child_public_key = public_key.uncompressed.group.generator.mul(factor).add(public_key.uncompressed.point).to_bn.to_i
|
68
|
+
raise InvalidKeyForIndex, 'at infinity' if child_public_key == 1/0.0 # very low probability
|
69
|
+
child_chain_code = right_from_hash(hash)
|
70
|
+
return child_public_key, child_chain_code
|
71
|
+
end
|
72
|
+
|
59
73
|
def left_from_hash(hash)
|
60
74
|
bytes_to_int hash.bytes.to_a[0..31]
|
61
75
|
end
|
@@ -76,25 +90,31 @@ module MoneyTree
|
|
76
90
|
# end
|
77
91
|
|
78
92
|
def to_serialized_hex(type = :public)
|
93
|
+
raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
|
79
94
|
version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
|
80
95
|
hex = MoneyTree::NETWORKS[:bitcoin][version_key] # version (4 bytes)
|
81
96
|
hex += depth_hex(depth) # depth (1 byte)
|
82
97
|
hex += depth.zero? ? '00000000' : parent.to_fingerprint# fingerprint of key (4 bytes)
|
83
98
|
hex += index_hex(index) # child number i (4 bytes)
|
84
99
|
hex += chain_code_hex
|
85
|
-
hex += type.to_sym == :private
|
100
|
+
hex += if type.to_sym == :private
|
101
|
+
"00#{private_key.to_hex}"
|
102
|
+
else
|
103
|
+
public_key.compressed.to_hex
|
104
|
+
end
|
86
105
|
end
|
87
106
|
|
88
107
|
def to_serialized_address(type = :public)
|
108
|
+
raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
|
89
109
|
to_serialized_base58 to_serialized_hex(type)
|
90
110
|
end
|
91
111
|
|
92
112
|
def to_identifier
|
93
|
-
public_key.to_ripemd160
|
113
|
+
public_key.compressed.to_ripemd160
|
94
114
|
end
|
95
115
|
|
96
116
|
def to_fingerprint
|
97
|
-
public_key.to_fingerprint
|
117
|
+
public_key.compressed.to_fingerprint
|
98
118
|
end
|
99
119
|
|
100
120
|
def to_address
|
@@ -104,18 +124,24 @@ module MoneyTree
|
|
104
124
|
|
105
125
|
def subnode(i = 0, opts = {})
|
106
126
|
# opts[:as_private] = is_private? unless opts[:as_private] == false
|
107
|
-
|
108
|
-
|
109
|
-
|
127
|
+
if private_key.nil?
|
128
|
+
child_public_key, child_chain_code = derive_public_key(i)
|
129
|
+
child_public_key = MoneyTree::PublicKey.new child_public_key
|
130
|
+
else
|
131
|
+
child_private_key, child_chain_code = derive_private_key(i)
|
132
|
+
child_private_key = MoneyTree::PrivateKey.new key: child_private_key
|
133
|
+
child_public_key = MoneyTree::PublicKey.new child_private_key
|
134
|
+
end
|
135
|
+
|
110
136
|
index = i
|
111
137
|
|
112
138
|
MoneyTree::Node.new depth: depth+1,
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
139
|
+
index: i,
|
140
|
+
is_private: i >= 0x80000000 || i < 0,
|
141
|
+
private_key: private_key.nil? ? nil : child_private_key,
|
142
|
+
public_key: child_public_key,
|
143
|
+
chain_code: child_chain_code,
|
144
|
+
parent: self
|
119
145
|
end
|
120
146
|
|
121
147
|
# path: a path of subkeys denoted by numbers and slashes. Use
|
@@ -182,28 +208,34 @@ module MoneyTree
|
|
182
208
|
@depth = 0
|
183
209
|
@index = 0
|
184
210
|
@is_private = true
|
185
|
-
@seed_generation_attempt = 0
|
186
211
|
opts[:seed] = [opts[:seed_hex]].pack("H*") if opts[:seed_hex]
|
187
212
|
if opts[:seed]
|
188
213
|
@seed = opts[:seed]
|
189
214
|
@seed_hash = generate_seed_hash(@seed)
|
190
215
|
raise SeedGeneration::ImportError unless seed_valid?(@seed_hash)
|
216
|
+
set_seeded_keys
|
217
|
+
elsif opts[:private_key] || opts[:public_key]
|
218
|
+
raise ImportError, 'chain code required' unless opts[:chain_code]
|
219
|
+
@chain_code = opts[:chain_code]
|
220
|
+
if opts[:private_key]
|
221
|
+
@private_key = opts[:private_key]
|
222
|
+
@public_key = MoneyTree::PublicKey.new @private_key
|
223
|
+
else opts[:public_key]
|
224
|
+
@public_key = opts[:public_key].is_a?(MoneyTree::PublicKey) ? opts[:public_key] : MoneyTree::PublicKey.new(opts[:public_key])
|
225
|
+
@is_private = false
|
226
|
+
end
|
191
227
|
else
|
192
|
-
|
228
|
+
generate_seed
|
229
|
+
set_seeded_keys
|
193
230
|
end
|
194
|
-
set_master_keys
|
195
231
|
end
|
196
232
|
|
197
|
-
def
|
198
|
-
@seed =
|
233
|
+
def generate_seed
|
234
|
+
@seed = OpenSSL::Random.random_bytes(32)
|
199
235
|
@seed_hash = generate_seed_hash(@seed)
|
200
236
|
raise SeedGeneration::ValidityError unless seed_valid?(@seed_hash)
|
201
237
|
end
|
202
238
|
|
203
|
-
def generate_seed
|
204
|
-
OpenSSL::Random.random_bytes(32)
|
205
|
-
end
|
206
|
-
|
207
239
|
def generate_seed_hash(seed)
|
208
240
|
hmac_sha512 HD_WALLET_BASE_KEY, seed
|
209
241
|
end
|
@@ -214,7 +246,7 @@ module MoneyTree
|
|
214
246
|
!master_key.zero? && master_key < MoneyTree::Key::ORDER
|
215
247
|
end
|
216
248
|
|
217
|
-
def
|
249
|
+
def set_seeded_keys
|
218
250
|
@private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
|
219
251
|
@chain_code = right_from_hash(seed_hash)
|
220
252
|
@public_key = MoneyTree::PublicKey.new @private_key
|
data/lib/money-tree/support.rb
CHANGED
@@ -3,24 +3,25 @@ require 'base64'
|
|
3
3
|
|
4
4
|
module MoneyTree
|
5
5
|
module Support
|
6
|
+
include OpenSSL
|
7
|
+
|
6
8
|
INT32_MAX = 256 ** [1].pack("L*").size
|
7
9
|
INT64_MAX = 256 ** [1].pack("Q*").size
|
10
|
+
BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
8
11
|
|
9
12
|
def int_to_base58(int_val, leading_zero_bytes=0)
|
10
|
-
|
11
|
-
base58_val, base = '', alpha.size
|
13
|
+
base58_val, base = '', BASE58_CHARS.size
|
12
14
|
while int_val > 0
|
13
15
|
int_val, remainder = int_val.divmod(base)
|
14
|
-
base58_val =
|
16
|
+
base58_val = BASE58_CHARS[remainder] + base58_val
|
15
17
|
end
|
16
18
|
base58_val
|
17
19
|
end
|
18
20
|
|
19
21
|
def base58_to_int(base58_val)
|
20
|
-
|
21
|
-
int_val, base = 0, alpha.size
|
22
|
+
int_val, base = 0, BASE58_CHARS.size
|
22
23
|
base58_val.reverse.each_char.with_index do |char,index|
|
23
|
-
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index =
|
24
|
+
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = BASE58_CHARS.index(char)
|
24
25
|
int_val += char_index*(base**index)
|
25
26
|
end
|
26
27
|
int_val
|
@@ -48,14 +49,17 @@ module MoneyTree
|
|
48
49
|
encode_base58 address
|
49
50
|
end
|
50
51
|
|
51
|
-
def
|
52
|
+
def digestify(digest_type, source, opts = {})
|
52
53
|
source = [source].pack("H*") unless opts[:ascii]
|
53
|
-
bytes_to_hex
|
54
|
+
bytes_to_hex Digest.digest(digest_type, source)
|
55
|
+
end
|
56
|
+
|
57
|
+
def sha256(source, opts = {})
|
58
|
+
digestify('SHA256', source, opts)
|
54
59
|
end
|
55
60
|
|
56
61
|
def ripemd160(source, opts = {})
|
57
|
-
|
58
|
-
bytes_to_hex OpenSSL::Digest::RIPEMD160.digest(source)
|
62
|
+
digestify('RIPEMD160', source, opts)
|
59
63
|
end
|
60
64
|
|
61
65
|
def encode_base64(hex)
|
@@ -67,8 +71,8 @@ module MoneyTree
|
|
67
71
|
end
|
68
72
|
|
69
73
|
def hmac_sha512(key, message)
|
70
|
-
digest =
|
71
|
-
|
74
|
+
digest = Digest::SHA512.new
|
75
|
+
HMAC.digest digest, key, message
|
72
76
|
end
|
73
77
|
|
74
78
|
def hmac_sha512_hex(key, message)
|
data/lib/money-tree/version.rb
CHANGED
data/lib/money-tree.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'ffi'
|
5
|
+
|
6
|
+
module MoneyTree
|
7
|
+
module OpenSSLExtensions
|
8
|
+
extend FFI::Library
|
9
|
+
ffi_lib 'ssl'
|
10
|
+
|
11
|
+
NID_secp256k1 = 714
|
12
|
+
POINT_CONVERSION_COMPRESSED = 2
|
13
|
+
POINT_CONVERSION_UNCOMPRESSED = 4
|
14
|
+
|
15
|
+
attach_function :EC_KEY_free, [:pointer], :int
|
16
|
+
attach_function :EC_KEY_get0_group, [:pointer], :pointer
|
17
|
+
attach_function :EC_KEY_new_by_curve_name, [:int], :pointer
|
18
|
+
attach_function :EC_POINT_free, [:pointer], :int
|
19
|
+
attach_function :EC_POINT_add, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
|
20
|
+
attach_function :EC_POINT_point2hex, [:pointer, :pointer, :int, :pointer], :string
|
21
|
+
attach_function :EC_POINT_hex2point, [:pointer, :string, :pointer, :pointer], :pointer
|
22
|
+
attach_function :EC_POINT_new, [:pointer], :pointer
|
23
|
+
|
24
|
+
def self.add(point_0, point_1)
|
25
|
+
eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
|
26
|
+
group = EC_KEY_get0_group(eckey)
|
27
|
+
|
28
|
+
point_0_hex = point_0.to_bn.to_s(16)
|
29
|
+
point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil)
|
30
|
+
point_1_hex = point_1.to_bn.to_s(16)
|
31
|
+
point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil)
|
32
|
+
|
33
|
+
sum_point = EC_POINT_new(group)
|
34
|
+
success = EC_POINT_add(group, sum_point, point_0_pt, point_1_pt, nil)
|
35
|
+
hex = EC_POINT_point2hex(group, sum_point, POINT_CONVERSION_UNCOMPRESSED, nil)
|
36
|
+
EC_KEY_free(eckey)
|
37
|
+
EC_POINT_free(sum_point)
|
38
|
+
hex
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
class OpenSSL::PKey::EC::Point
|
45
|
+
include MoneyTree::OpenSSLExtensions
|
46
|
+
|
47
|
+
def add(point)
|
48
|
+
sum_point_hex = MoneyTree::OpenSSLExtensions.add(self, point)
|
49
|
+
self.class.new group, OpenSSL::BN.new(sum_point_hex, 16)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/money-tree.gemspec
CHANGED
@@ -17,6 +17,8 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "ffi"
|
20
22
|
|
21
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
24
|
spec.add_development_dependency "rake"
|
@@ -302,8 +302,9 @@ describe MoneyTree::Master do
|
|
302
302
|
end
|
303
303
|
end
|
304
304
|
|
305
|
-
describe "m/0" do
|
305
|
+
describe "m/0 (testing imported private key)" do
|
306
306
|
before do
|
307
|
+
@master = MoneyTree::Master.new private_key: @master.private_key, chain_code: @master.chain_code
|
307
308
|
@node = @master.node_for_path "m/0"
|
308
309
|
end
|
309
310
|
|
@@ -348,6 +349,53 @@ describe MoneyTree::Master do
|
|
348
349
|
@node.to_serialized_address.should == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
|
349
350
|
end
|
350
351
|
end
|
352
|
+
|
353
|
+
describe "M/0 (testing import of public key)" do
|
354
|
+
before do
|
355
|
+
@master = MoneyTree::Master.new public_key: "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", chain_code: @master.chain_code
|
356
|
+
@node = @master.node_for_path "M/0"
|
357
|
+
end
|
358
|
+
|
359
|
+
it "has an index of 0" do
|
360
|
+
@node.index.should == 0
|
361
|
+
end
|
362
|
+
|
363
|
+
it "has a depth of 1" do
|
364
|
+
@node.depth.should == 1
|
365
|
+
end
|
366
|
+
|
367
|
+
it "is public" do
|
368
|
+
@node.is_private.should == false
|
369
|
+
end
|
370
|
+
|
371
|
+
it "generates subnode" do
|
372
|
+
@node.to_identifier.should == "5a61ff8eb7aaca3010db97ebda76121610b78096"
|
373
|
+
@node.to_fingerprint.should == "5a61ff8e"
|
374
|
+
@node.to_address.should == "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ"
|
375
|
+
end
|
376
|
+
|
377
|
+
it "does not generate a private key" do
|
378
|
+
@node.private_key.should be_nil
|
379
|
+
end
|
380
|
+
|
381
|
+
it "generates a public key" do
|
382
|
+
@node.public_key.to_hex.should == "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"
|
383
|
+
end
|
384
|
+
|
385
|
+
it "generates a chain code" do
|
386
|
+
@node.chain_code_hex.should == "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"
|
387
|
+
end
|
388
|
+
|
389
|
+
it "does not generate a serialized private key" do
|
390
|
+
lambda { @node.to_serialized_hex(:private) }.should raise_error(MoneyTree::Node::PrivatePublicMismatch)
|
391
|
+
lambda { @node.to_serialized_address(:private) }.should raise_error(MoneyTree::Node::PrivatePublicMismatch)
|
392
|
+
end
|
393
|
+
|
394
|
+
it "generates a serialized public_key" do
|
395
|
+
@node.to_serialized_hex.should == "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"
|
396
|
+
@node.to_serialized_address.should == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
|
397
|
+
end
|
398
|
+
end
|
351
399
|
|
352
400
|
describe "m/0/2147483647'" do
|
353
401
|
before do
|
@@ -2,54 +2,109 @@ require 'spec_helper'
|
|
2
2
|
require 'money-tree'
|
3
3
|
|
4
4
|
describe MoneyTree::PublicKey do
|
5
|
-
before do
|
6
|
-
@private_key = MoneyTree::PrivateKey.new key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
|
7
|
-
@key = MoneyTree::PublicKey.new @private_key
|
8
|
-
end
|
9
5
|
|
10
|
-
describe "
|
11
|
-
|
12
|
-
@
|
13
|
-
|
14
|
-
|
15
|
-
it "is a valid hex" do
|
16
|
-
@key.to_hex(compressed: false).should == '042dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b11203096f1a1c5276a73f91b9465357004c2103cc42c63d6d330df589080d2e4'
|
6
|
+
describe "with a private key" do
|
7
|
+
before do
|
8
|
+
@private_key = MoneyTree::PrivateKey.new key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
|
9
|
+
@key = MoneyTree::PublicKey.new @private_key
|
17
10
|
end
|
18
|
-
end
|
19
11
|
|
20
|
-
|
21
|
-
it "has
|
22
|
-
|
12
|
+
# describe "to_hex(compressed: false)" do
|
13
|
+
# it "has 65 bytes" do
|
14
|
+
# @key.uncompressed.to_hex.length.should == 130
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# it "is a valid hex" do
|
18
|
+
# @key.uncompressed.to_hex.should == '042dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b11203096f1a1c5276a73f91b9465357004c2103cc42c63d6d330df589080d2e4'
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
|
22
|
+
describe "to_hex" do
|
23
|
+
it "has 33 bytes" do
|
24
|
+
@key.to_hex.length.should == 66
|
25
|
+
end
|
26
|
+
|
27
|
+
it "is a valid compressed hex" do
|
28
|
+
@key.to_hex.should == '022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b'
|
29
|
+
end
|
23
30
|
end
|
24
31
|
|
25
|
-
|
26
|
-
|
32
|
+
describe "to_fingerprint" do
|
33
|
+
it "returns a valid fingerprint" do
|
34
|
+
@key.to_fingerprint.should == "1fddf42e"
|
35
|
+
end
|
27
36
|
end
|
28
|
-
end
|
29
37
|
|
30
|
-
|
31
|
-
it "
|
32
|
-
|
38
|
+
# describe "to_address(compressed: false)" do
|
39
|
+
# it "has 34 characters" do
|
40
|
+
# @key.uncompressed.to_address.length.should == 34
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# it "is a valid bitcoin address" do
|
44
|
+
# @key.uncompressed.to_address.should == '133bJA2xoVqBUsiR3uSkciMo5r15fLAaZg'
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
|
48
|
+
describe "to_compressed_address" do
|
49
|
+
it "has 34 characters" do
|
50
|
+
@key.to_address.length.should == 34
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is a valid compressed bitcoin address" do
|
54
|
+
@key.to_address.should == '13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe'
|
55
|
+
end
|
33
56
|
end
|
34
57
|
end
|
35
58
|
|
36
|
-
describe "
|
37
|
-
|
38
|
-
@key
|
59
|
+
describe "without a private key" do
|
60
|
+
before do
|
61
|
+
@key = MoneyTree::PublicKey.new '042dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b11203096f1a1c5276a73f91b9465357004c2103cc42c63d6d330df589080d2e4'
|
39
62
|
end
|
40
63
|
|
41
|
-
|
42
|
-
|
64
|
+
# describe "to_hex(compressed: false)" do
|
65
|
+
# it "has 65 bytes" do
|
66
|
+
# @key.uncompressed.to_hex.length.should == 130
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# it "is a valid hex" do
|
70
|
+
# @key.uncompressed.to_hex.should == '042dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b11203096f1a1c5276a73f91b9465357004c2103cc42c63d6d330df589080d2e4'
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
|
74
|
+
describe "to_hex" do
|
75
|
+
it "has 33 bytes" do
|
76
|
+
@key.compressed.to_hex.length.should == 66
|
77
|
+
end
|
78
|
+
|
79
|
+
it "is a valid compressed hex" do
|
80
|
+
@key.compressed.to_hex.should == '022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b'
|
81
|
+
end
|
43
82
|
end
|
44
|
-
end
|
45
83
|
|
46
|
-
|
47
|
-
|
48
|
-
|
84
|
+
describe "to_fingerprint" do
|
85
|
+
it "returns a valid fingerprint" do
|
86
|
+
@key.compressed.to_fingerprint.should == "1fddf42e"
|
87
|
+
end
|
49
88
|
end
|
89
|
+
|
90
|
+
# describe "to_address(compressed: false)" do
|
91
|
+
# it "has 34 characters" do
|
92
|
+
# @key.uncompressed.to_address.length.should == 34
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# it "is a valid bitcoin address" do
|
96
|
+
# @key.uncompressed.to_address.should == '133bJA2xoVqBUsiR3uSkciMo5r15fLAaZg'
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
|
100
|
+
describe "to_compressed_address" do
|
101
|
+
it "has 34 characters" do
|
102
|
+
@key.compressed.to_address.length.should == 34
|
103
|
+
end
|
50
104
|
|
51
|
-
|
52
|
-
|
105
|
+
it "is a valid compressed bitcoin address" do
|
106
|
+
@key.compressed.to_address.should == '13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe'
|
107
|
+
end
|
53
108
|
end
|
54
109
|
end
|
55
110
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money-tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micah Winkelspecht
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,6 +116,7 @@ extensions: []
|
|
102
116
|
extra_rdoc_files: []
|
103
117
|
files:
|
104
118
|
- .gitignore
|
119
|
+
- .rspec
|
105
120
|
- .simplecov
|
106
121
|
- .travis.yml
|
107
122
|
- Gemfile
|
@@ -115,6 +130,7 @@ files:
|
|
115
130
|
- lib/money-tree/node.rb
|
116
131
|
- lib/money-tree/support.rb
|
117
132
|
- lib/money-tree/version.rb
|
133
|
+
- lib/openssl_extensions.rb
|
118
134
|
- money-tree.gemspec
|
119
135
|
- spec/lib/money-tree/address_spec.rb
|
120
136
|
- spec/lib/money-tree/node_spec.rb
|