money-tree 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/money-tree.rb +11 -0
- data/lib/money-tree/address.rb +15 -0
- data/lib/money-tree/key.rb +200 -0
- data/lib/money-tree/networks.rb +15 -0
- data/lib/money-tree/node.rb +232 -0
- data/lib/money-tree/support.rb +109 -0
- data/lib/money-tree/version.rb +3 -0
- data/money-tree.gemspec +25 -0
- data/spec/lib/money-tree/address_spec.rb +52 -0
- data/spec/lib/money-tree/node_spec.rb +542 -0
- data/spec/lib/money-tree/private_key_spec.rb +65 -0
- data/spec/lib/money-tree/public_key_spec.rb +48 -0
- data/spec/lib/money-tree/support_spec.rb +26 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4fb106149c53cf468220d50f609604835f0771d0
|
4
|
+
data.tar.gz: ae65887642b07d8bcfdefae06452d4b0a91d0f2a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ea72d026539e19514e95d51a0e2404dfd594b6d524b4debfe491b762d0d1d24f1cc0d3ef0cbd1fb20db29de0403c7d7066d3190f4aca3736853bfd4d6354853a
|
7
|
+
data.tar.gz: c2e21de52d10e2f9d2917be4a6db7cd5e9c8b6588959963ead0648a6b219b479a1c5f67ac45299f0a31ff380562c6a138a449df656988a0ad1310a77f0762860
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Micah Winkelspecht
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# MoneyTree
|
2
|
+
|
3
|
+
MoneyTree is a Ruby implementation of Bitcoin Wallets. Specifically, it supports [Hierachical Deterministic wallets](http://) according to the protocol specified in [BIP0032](https://en.bitcoin.it/wiki/BIP_0032).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'money-tree'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install money-tree
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/money-tree.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module MoneyTree
|
2
|
+
class Address
|
3
|
+
attr_accessor :private_key, :public_key
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@private_key = MoneyTree::PrivateKey.new key: opts[:private_key]
|
7
|
+
@public_key = MoneyTree::PublicKey.new(@private_key)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
public_key.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# encoding ascii-8bit
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'pry'
|
5
|
+
|
6
|
+
module MoneyTree
|
7
|
+
class Key
|
8
|
+
include OpenSSL
|
9
|
+
include Support
|
10
|
+
class KeyInvalid < Exception; end
|
11
|
+
class KeyGenerationFailure < Exception; end
|
12
|
+
class KeyImportFailure < Exception; end
|
13
|
+
class KeyFormatNotFound < Exception; end
|
14
|
+
class InvalidWIFFormat < Exception; end
|
15
|
+
|
16
|
+
attr_reader :options, :key
|
17
|
+
attr_accessor :ec_key
|
18
|
+
|
19
|
+
GROUP_NAME = 'secp256k1'
|
20
|
+
GROUP_UNCOMPRESSED = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".to_i(16)
|
21
|
+
GROUP_COMPRESSED = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".to_i(16)
|
22
|
+
ORDER = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".to_i(16)
|
23
|
+
|
24
|
+
def valid?(eckey = nil)
|
25
|
+
eckey ||= ec_key
|
26
|
+
eckey.nil? ? false : eckey.check_key
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_bytes
|
30
|
+
hex_to_bytes to_hex
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_i
|
34
|
+
bytes_to_int to_bytes
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class PrivateKey < Key
|
39
|
+
|
40
|
+
attr_reader :raw_key
|
41
|
+
|
42
|
+
def initialize(opts = {})
|
43
|
+
@options = opts
|
44
|
+
# @ec_key = EC_KEY_new_by_curve_name(NID_secp256k1)
|
45
|
+
@ec_key = PKey::EC.new 'secp256k1'
|
46
|
+
if @options[:key]
|
47
|
+
@raw_key = @options[:key]
|
48
|
+
@key = parse_raw_key
|
49
|
+
import
|
50
|
+
else
|
51
|
+
generate
|
52
|
+
@key = to_hex
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate
|
57
|
+
ec_key.generate_key
|
58
|
+
end
|
59
|
+
|
60
|
+
def import
|
61
|
+
ec_key.private_key = BN.new(key, 16)
|
62
|
+
set_public_key
|
63
|
+
end
|
64
|
+
|
65
|
+
def calculate_public_key(opts = {})
|
66
|
+
opts[:compressed] = true unless opts[:compressed] == false
|
67
|
+
group = ec_key.group
|
68
|
+
group.point_conversion_form = opts[:compressed] ? :compressed : :uncompressed
|
69
|
+
point = group.generator.mul ec_key.private_key
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_public_key(opts = {})
|
73
|
+
ec_key.public_key = calculate_public_key(opts)
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_raw_key
|
77
|
+
result = if raw_key.is_a?(Bignum) then int_to_hex(raw_key)
|
78
|
+
elsif hex_format? then raw_key
|
79
|
+
elsif compressed_wif_format? then from_wif
|
80
|
+
elsif uncompressed_wif_format? then from_wif
|
81
|
+
elsif base64_format? then from_base64
|
82
|
+
else
|
83
|
+
raise raw_key.inspect
|
84
|
+
raise KeyFormatNotFound
|
85
|
+
end
|
86
|
+
result.downcase
|
87
|
+
end
|
88
|
+
|
89
|
+
def from_wif(wif = raw_key)
|
90
|
+
compressed = wif.length == 52
|
91
|
+
validate_wif(wif)
|
92
|
+
hex = decode_base58(wif)
|
93
|
+
last_char = compressed ? -11 : -9
|
94
|
+
hex.slice(2..last_char)
|
95
|
+
end
|
96
|
+
|
97
|
+
def from_base64
|
98
|
+
decode_base64(raw_key)
|
99
|
+
end
|
100
|
+
|
101
|
+
def compressed_wif_format?
|
102
|
+
raw_key.length == 52 && MoneyTree::NETWORKS[:bitcoin][:compressed_wif_chars].include?(raw_key.slice(0))
|
103
|
+
end
|
104
|
+
|
105
|
+
def uncompressed_wif_format?
|
106
|
+
raw_key.length == 51 && raw_key.slice(0) == MoneyTree::NETWORKS[:bitcoin][:uncompressed_wif_char]
|
107
|
+
end
|
108
|
+
|
109
|
+
def base64_format?
|
110
|
+
raw_key.length == 44
|
111
|
+
end
|
112
|
+
|
113
|
+
def hex_format?
|
114
|
+
raw_key.length == 64 && !raw_key[/\H/]
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_hex
|
118
|
+
int_to_hex @ec_key.private_key
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_wif(opts = {})
|
122
|
+
opts[:compressed] = true unless opts[:compressed] == false
|
123
|
+
source = MoneyTree::NETWORKS[:bitcoin][:privkey_version] + to_hex
|
124
|
+
source += MoneyTree::NETWORKS[:bitcoin][:privkey_compression_flag] if opts[:compressed]
|
125
|
+
hash = sha256(source)
|
126
|
+
hash = sha256(hash)
|
127
|
+
checksum = hash.slice(0..7)
|
128
|
+
source_with_checksum = source + checksum
|
129
|
+
encode_base58(source_with_checksum)
|
130
|
+
end
|
131
|
+
|
132
|
+
def wif_valid?(wif)
|
133
|
+
hex = decode_base58(wif)
|
134
|
+
return false unless hex.slice(0..1) == MoneyTree::NETWORKS[:bitcoin][:privkey_version]
|
135
|
+
checksum = hex.chars.to_a.pop(8).join
|
136
|
+
source = hex.slice(0..-9)
|
137
|
+
hash = sha256(source)
|
138
|
+
hash = sha256(hash)
|
139
|
+
hash_checksum = hash.slice(0..7)
|
140
|
+
checksum == hash_checksum
|
141
|
+
end
|
142
|
+
|
143
|
+
def validate_wif(wif)
|
144
|
+
raise InvalidWIFFormat unless wif_valid?(wif)
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_base64
|
148
|
+
encode_base64(to_hex)
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
to_wif
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
class PublicKey < Key
|
158
|
+
attr_reader :private_key
|
159
|
+
|
160
|
+
def initialize(p_key, opts = {})
|
161
|
+
raise "Must initialize with a MoneyTree::PrivateKey" unless p_key.is_a?(PrivateKey)
|
162
|
+
@private_key = p_key
|
163
|
+
@ec_key = @private_key.ec_key
|
164
|
+
@options = opts
|
165
|
+
@key = @options[:key] || to_hex
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_hex(opts = {})
|
169
|
+
int_to_hex to_i(opts)
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_i(opts = {})
|
173
|
+
private_key.calculate_public_key(opts).to_bn.to_i
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_ripemd160(opts = {})
|
177
|
+
hash = sha256 to_hex(opts)
|
178
|
+
ripemd160 hash
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_address(opts = {})
|
182
|
+
hash = to_ripemd160(opts)
|
183
|
+
address = MoneyTree::NETWORKS[:bitcoin][:address_version] + hash
|
184
|
+
to_serialized_base58 address
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_fingerprint(opts = {})
|
188
|
+
hash = to_ripemd160(opts)
|
189
|
+
hash.slice(0..7)
|
190
|
+
end
|
191
|
+
|
192
|
+
def to_s(opts = {})
|
193
|
+
to_address(opts)
|
194
|
+
end
|
195
|
+
|
196
|
+
def to_bytes(opts = {})
|
197
|
+
int_to_bytes to_i(opts)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MoneyTree
|
2
|
+
NETWORKS = {
|
3
|
+
bitcoin: {
|
4
|
+
address_version: '00',
|
5
|
+
p2sh_version: '05',
|
6
|
+
privkey_version: '80',
|
7
|
+
privkey_compression_flag: '01',
|
8
|
+
extended_privkey_version: "0488ade4",
|
9
|
+
extended_pubkey_version: "0488b21e",
|
10
|
+
compressed_wif_chars: %w(K L),
|
11
|
+
uncompressed_wif_char: '5',
|
12
|
+
protocol_version: 70001
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module MoneyTree
|
2
|
+
class Node
|
3
|
+
include Support
|
4
|
+
attr_reader :private_key, :public_key, :chain_code, :is_private, :depth, :index, :parent
|
5
|
+
|
6
|
+
class PublicDerivationFailure < Exception; end
|
7
|
+
class InvalidKeyForIndex < Exception; end
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
@depth = opts[:depth]
|
11
|
+
@index = opts[:index]
|
12
|
+
@is_private = opts[:is_private]
|
13
|
+
@private_key = opts[:private_key]
|
14
|
+
@public_key = opts[:public_key]
|
15
|
+
@chain_code = opts[:chain_code]
|
16
|
+
@parent = opts[:parent]
|
17
|
+
end
|
18
|
+
|
19
|
+
def is_private?
|
20
|
+
is_private == true
|
21
|
+
end
|
22
|
+
|
23
|
+
def index_hex(i = index)
|
24
|
+
i.to_s(16).rjust(8, "0")
|
25
|
+
end
|
26
|
+
|
27
|
+
def depth_hex(depth)
|
28
|
+
depth.to_s(16).rjust(2, "0")
|
29
|
+
end
|
30
|
+
|
31
|
+
def private_derivation_private_key_message(i)
|
32
|
+
"\x00" + private_key.to_bytes + i_as_bytes(i)
|
33
|
+
end
|
34
|
+
|
35
|
+
def public_derivation_private_key_message(i)
|
36
|
+
public_key.to_bytes << i_as_bytes(i)
|
37
|
+
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
|
+
def i_as_bytes(i)
|
45
|
+
[i].pack('N')
|
46
|
+
end
|
47
|
+
|
48
|
+
def derive_private_key(i = 0)
|
49
|
+
message = i >= 0x80000000 ? private_derivation_private_key_message(i) : public_derivation_private_key_message(i)
|
50
|
+
hash = hmac_sha512 int_to_bytes(chain_code), message
|
51
|
+
left_int = left_from_hash(hash)
|
52
|
+
raise InvalidKeyForIndex if left_int >= MoneyTree::Key::ORDER # very low probability
|
53
|
+
child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
|
54
|
+
raise InvalidKeyForIndex if child_private_key == 0 # very low probability
|
55
|
+
child_chain_code = right_from_hash(hash)
|
56
|
+
return child_private_key, child_chain_code
|
57
|
+
end
|
58
|
+
|
59
|
+
def left_from_hash(hash)
|
60
|
+
bytes_to_int hash.bytes.to_a[0..31]
|
61
|
+
end
|
62
|
+
|
63
|
+
def right_from_hash(hash)
|
64
|
+
bytes_to_int hash.bytes.to_a[32..-1]
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: Complete public key derivation
|
68
|
+
# def derive_public_key(i = 0, opts = {})
|
69
|
+
# raise PublicDerivationFailure unless i < 0x80000000
|
70
|
+
# hash = hmac_sha512([chain_code].pack("H*"), public_key.to_hex + index_hex(i))
|
71
|
+
# temp_key = MoneyTree::PrivateKey.new key: hash[0..63]
|
72
|
+
# temp_pub_key = MoneyTree::PublicKey.new temp_key
|
73
|
+
# child_public_key = (temp_key.to_hex.to_i(16) + temp_pub_key.to_hex.to_i(16))
|
74
|
+
# child_chain_code = hash[64..-1]
|
75
|
+
# return child_public_key, child_chain_code
|
76
|
+
# end
|
77
|
+
|
78
|
+
def to_serialized_hex(type = :public)
|
79
|
+
version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
|
80
|
+
hex = MoneyTree::NETWORKS[:bitcoin][version_key] # version (4 bytes)
|
81
|
+
hex += depth_hex(depth) # depth (1 byte)
|
82
|
+
hex += depth.zero? ? '00000000' : parent.to_fingerprint# fingerprint of key (4 bytes)
|
83
|
+
hex += index_hex(index) # child number i (4 bytes)
|
84
|
+
hex += chain_code_hex
|
85
|
+
hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.to_hex
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_serialized_address(type = :public)
|
89
|
+
to_serialized_base58 to_serialized_hex(type)
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_identifier
|
93
|
+
public_key.to_ripemd160
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_fingerprint
|
97
|
+
to_identifier[0..7]
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_address
|
101
|
+
address = MoneyTree::NETWORKS[:bitcoin][:address_version] + to_identifier
|
102
|
+
to_serialized_base58 address
|
103
|
+
end
|
104
|
+
|
105
|
+
def subnode(i = 0, opts = {})
|
106
|
+
# opts[:as_private] = is_private? unless opts[:as_private] == false
|
107
|
+
child_private_key, child_chain_code = derive_private_key(i)
|
108
|
+
child_private_key = MoneyTree::PrivateKey.new key: child_private_key
|
109
|
+
child_public_key = MoneyTree::PublicKey.new child_private_key
|
110
|
+
|
111
|
+
MoneyTree::Node.new depth: depth+1,
|
112
|
+
index: i,
|
113
|
+
is_private: i >= 0x80000000,
|
114
|
+
private_key: child_private_key,
|
115
|
+
public_key: child_public_key,
|
116
|
+
chain_code: child_chain_code,
|
117
|
+
parent: self
|
118
|
+
end
|
119
|
+
|
120
|
+
# path: a path of subkeys denoted by numbers and slashes. Use
|
121
|
+
# p or i<0 for private key derivation. End with .pub to force
|
122
|
+
# the key public.
|
123
|
+
#
|
124
|
+
# Examples:
|
125
|
+
# 1p/-5/2/1 would call subkey(i=1, is_prime=True).subkey(i=-5).
|
126
|
+
# subkey(i=2).subkey(i=1) and then yield the private key
|
127
|
+
# 0/0/458.pub would call subkey(i=0).subkey(i=0).subkey(i=458) and
|
128
|
+
# then yield the public key
|
129
|
+
#
|
130
|
+
# You should choose either the p or the negative number convention for private key derivation.
|
131
|
+
def node_for_path(path)
|
132
|
+
force_public = path[-4..-1] == '.pub'
|
133
|
+
path = path[0..-4] if force_public
|
134
|
+
parts = path.split('/')
|
135
|
+
nodes = []
|
136
|
+
parts.each_with_index do |part, depth|
|
137
|
+
if part =~ /m/i
|
138
|
+
nodes << self
|
139
|
+
else
|
140
|
+
i = parse_index(part)
|
141
|
+
nodes << nodes.last.subnode(i)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
nodes.last
|
145
|
+
end
|
146
|
+
|
147
|
+
def parse_index(path_part)
|
148
|
+
is_prime = %w(p ').include? path_part[-1]
|
149
|
+
i = path_part.to_i
|
150
|
+
|
151
|
+
i = if i < 0
|
152
|
+
i
|
153
|
+
elsif is_prime
|
154
|
+
i | 0x80000000
|
155
|
+
else
|
156
|
+
i & 0x7fffffff
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def chain_code_hex
|
161
|
+
int_to_hex chain_code
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Master < Node
|
166
|
+
module SeedGeneration
|
167
|
+
class Failure < Exception; end
|
168
|
+
class RNGFailure < Failure; end
|
169
|
+
class LengthFailure < Failure; end
|
170
|
+
class ValidityError < Failure; end
|
171
|
+
class ImportError < Failure; end
|
172
|
+
class TooManyAttempts < Failure; end
|
173
|
+
end
|
174
|
+
|
175
|
+
HD_WALLET_BASE_KEY = "Bitcoin seed"
|
176
|
+
RANDOM_SEED_SIZE = 32
|
177
|
+
|
178
|
+
attr_reader :seed, :seed_hash
|
179
|
+
|
180
|
+
def initialize(opts = {})
|
181
|
+
@depth = 0
|
182
|
+
@index = 0
|
183
|
+
@is_private = true
|
184
|
+
@seed_generation_attempt = 0
|
185
|
+
opts[:seed] = [opts[:seed_hex]].pack("H*") if opts[:seed_hex]
|
186
|
+
if opts[:seed]
|
187
|
+
@seed = opts[:seed]
|
188
|
+
@seed_hash = generate_seed_hash(@seed)
|
189
|
+
raise SeedGeneration::ImportError unless seed_valid?(@seed_hash)
|
190
|
+
else
|
191
|
+
generate_seed_until_valid
|
192
|
+
end
|
193
|
+
set_master_keys
|
194
|
+
end
|
195
|
+
|
196
|
+
def generate_seed_until_valid
|
197
|
+
begin
|
198
|
+
@seed = generate_seed
|
199
|
+
@seed_hash = generate_seed_hash(@seed)
|
200
|
+
raise SeedGeneration::ValidityError unless seed_valid?(@seed_hash)
|
201
|
+
@seed_generation_attempt = 0
|
202
|
+
rescue SeedGeneration::Failure
|
203
|
+
@seed_generation_attempt += 1
|
204
|
+
@seed_generation_attempt < 10 ? generate_seed_until_valid : raise(SeedGeneration::TooManyAttempts)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def generate_seed
|
209
|
+
OpenSSL::Random.random_bytes(32)
|
210
|
+
end
|
211
|
+
|
212
|
+
def generate_seed_hash(seed)
|
213
|
+
hmac_sha512 HD_WALLET_BASE_KEY, seed
|
214
|
+
end
|
215
|
+
|
216
|
+
def seed_valid?(seed_hash)
|
217
|
+
return false unless seed_hash.bytesize == 64
|
218
|
+
master_key = left_from_hash(seed_hash)
|
219
|
+
!master_key.zero? && master_key < MoneyTree::Key::ORDER
|
220
|
+
end
|
221
|
+
|
222
|
+
def is_hex?(str)
|
223
|
+
str =~ /^[0-9A-F]+$/i && str.length % 2 == 0
|
224
|
+
end
|
225
|
+
|
226
|
+
def set_master_keys
|
227
|
+
@private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
|
228
|
+
@chain_code = right_from_hash(seed_hash)
|
229
|
+
@public_key = MoneyTree::PublicKey.new @private_key
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|