money-tree 0.0.1
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 +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
|