money-tree 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/wink/money-tree.png)](https://travis-ci.org/wink/money-tree) [![Coverage Status](https://coveralls.io/repos/wink/money-tree/badge.png?branch=master)](https://coveralls.io/r/wink/money-tree?branch=master) [![Gem Version](https://badge.fury.io/rb/money-tree.png)](http://badge.fury.io/rb/money-tree)
|
1
|
+
[![Build Status](https://travis-ci.org/wink/money-tree.png)](https://travis-ci.org/wink/money-tree) [![Coverage Status](https://coveralls.io/repos/wink/money-tree/badge.png?branch=master)](https://coveralls.io/r/wink/money-tree?branch=master) [![Code Climate](https://codeclimate.com/github/wink/money-tree.png)](https://codeclimate.com/github/wink/money-tree) [![Gem Version](https://badge.fury.io/rb/money-tree.png)](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
|