money-tree 0.11.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/spec.yml +2 -0
- data/Gemfile +15 -3
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/checksum/money-tree-0.11.0.gem.sha512 +1 -0
- data/checksum/money-tree-0.9.0.gem.sha512 +1 -1
- data/lib/money-tree/address.rb +14 -4
- data/lib/money-tree/key.rb +32 -25
- data/lib/money-tree/networks.rb +16 -15
- data/lib/money-tree/node.rb +43 -38
- data/lib/money-tree/support.rb +42 -17
- data/lib/money-tree/version.rb +1 -1
- data/lib/money-tree.rb +6 -6
- data/lib/openssl_extensions.rb +6 -4
- data/money-tree.gemspec +21 -18
- data/spec/{lib/money-tree → money-tree}/address_spec.rb +23 -5
- data/spec/money-tree/money_tree_spec.rb +9 -0
- data/spec/{lib/money-tree → money-tree}/node_spec.rb +425 -15
- data/spec/{lib/money-tree → money-tree}/openssl_extensions_spec.rb +2 -2
- data/spec/{lib/money-tree → money-tree}/private_key_spec.rb +10 -10
- data/spec/{lib/money-tree → money-tree}/public_key_spec.rb +51 -22
- data/spec/{lib/money-tree → money-tree}/support_spec.rb +1 -1
- data/spec/spec_helper.rb +15 -2
- metadata +27 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a817c56d0f39bc09f84b1dd02d9e828a9b3fd4d93500d7f6e9cd4ead322c7355
|
4
|
+
data.tar.gz: 1e8de14764b5ddf911769e537b99eb4b9f3ce2e91e6dff9ffc2cef923214d156
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2d89e5ffa65f7781a7ff35c830da95614b22e743a24ce5662f4f952dd18685ba9b5a18078fb5bc0a45c1527bb66e32473a5d8453a0a87026e7d5625dbd940d0
|
7
|
+
data.tar.gz: 1355fe75f77593dec9c191e9c1d91f4a1f5205fe8e6f01600298311a17c070f25002c8ed0889166125b5879ce9b03675e476f2cf64b5a6f03e19b01886a4506f
|
data/.github/workflows/spec.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
gem "openssl", "~> 3.0"
|
4
|
+
gem "bech32", "~> 1.2"
|
5
|
+
|
6
|
+
group :test, :development do
|
7
|
+
gem "bundler", "~> 2.2"
|
8
|
+
gem "codecov", "~> 0.6"
|
9
|
+
gem "pry", "~> 0.14"
|
10
|
+
gem "rake", "~> 13.0"
|
11
|
+
gem "rdoc", "~> 6.3"
|
12
|
+
gem "rspec", "~> 3.10"
|
13
|
+
gem "rufo", "~> 0.13"
|
14
|
+
gem "simplecov", "~> 0.21"
|
15
|
+
gem "yard", "~> 0.9"
|
16
|
+
end
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
[![GitHub top language](https://img.shields.io/github/languages/top/GemHQ/money-tree?color=red)](https://github.com/GemHQ/money-tree/pulse)
|
7
7
|
|
8
8
|
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/GemHQ/money-tree/Spec)](https://github.com/GemHQ/money-tree/actions)
|
9
|
-
[![Coverage
|
9
|
+
[![Code Coverage](https://codecov.io/gh/GemHQ/money-tree/branch/master/graph/badge.svg?token=PF4BL36Z9q)](https://codecov.io/gh/GemHQ/money-tree)
|
10
10
|
[![Code Climate](https://codeclimate.com/github/GemHQ/money-tree.png)](https://codeclimate.com/github/GemHQ/money-tree)
|
11
11
|
[![GitHub](https://img.shields.io/github/license/GemHQ/money-tree)](LICENSE)
|
12
12
|
|
data/Rakefile
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
cc59abeb70c300eb32c0562dea582de4d26da71cb3fac9937fb3b88fd7092177c98ccbdf6eba7517c3dbc14b33f15e95bf799a02adfec5c12583cb9179c68bb7
|
@@ -1 +1 @@
|
|
1
|
-
ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
|
1
|
+
ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
|
data/lib/money-tree/address.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
1
|
module MoneyTree
|
2
2
|
class Address
|
3
|
-
attr_reader :private_key
|
3
|
+
attr_reader :private_key
|
4
|
+
attr_reader :public_key
|
4
5
|
|
5
6
|
def initialize(opts = {})
|
6
7
|
private_key = opts.delete(:private_key)
|
7
|
-
@private_key =
|
8
|
-
@public_key =
|
8
|
+
@private_key = PrivateKey.new({ key: private_key }.merge(opts))
|
9
|
+
@public_key = PublicKey.new(@private_key, opts)
|
9
10
|
end
|
10
11
|
|
11
12
|
def to_s(network: :bitcoin)
|
12
|
-
public_key.to_s(network: network)
|
13
|
+
@public_key.to_s(network: network)
|
13
14
|
end
|
14
15
|
|
16
|
+
def to_bech32(network: :bitcoin)
|
17
|
+
hrp = NETWORKS[network][:human_readable_part]
|
18
|
+
witprog = @public_key.to_ripemd160
|
19
|
+
Support.to_serialized_bech32(hrp, witprog)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_p2wpkh_p2sh(network: :bitcoin)
|
23
|
+
@public_key.to_p2wpkh_p2sh(network: network)
|
24
|
+
end
|
15
25
|
end
|
16
26
|
end
|
data/lib/money-tree/key.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
# encoding ascii-8bit
|
2
2
|
|
3
|
-
require
|
3
|
+
require "openssl"
|
4
4
|
|
5
5
|
module MoneyTree
|
6
6
|
class Key
|
7
|
-
include OpenSSL
|
8
7
|
include Support
|
9
|
-
|
8
|
+
|
10
9
|
class KeyInvalid < StandardError; end
|
11
10
|
class KeyGenerationFailure < StandardError; end
|
12
11
|
class KeyImportFailure < StandardError; end
|
@@ -14,10 +13,13 @@ module MoneyTree
|
|
14
13
|
class InvalidWIFFormat < StandardError; end
|
15
14
|
class InvalidBase64Format < StandardError; end
|
16
15
|
|
17
|
-
attr_reader :options
|
16
|
+
attr_reader :options
|
17
|
+
attr_reader :key
|
18
|
+
attr_reader :raw_key
|
19
|
+
|
18
20
|
attr_accessor :ec_key
|
19
21
|
|
20
|
-
GROUP_NAME =
|
22
|
+
GROUP_NAME = "secp256k1"
|
21
23
|
ORDER = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".to_i(16)
|
22
24
|
|
23
25
|
def valid?(eckey = nil)
|
@@ -35,7 +37,6 @@ module MoneyTree
|
|
35
37
|
end
|
36
38
|
|
37
39
|
class PrivateKey < Key
|
38
|
-
|
39
40
|
def initialize(opts = {})
|
40
41
|
@options = opts
|
41
42
|
@ec_key = PKey::EC.new GROUP_NAME
|
@@ -70,14 +71,9 @@ module MoneyTree
|
|
70
71
|
end
|
71
72
|
|
72
73
|
def parse_raw_key
|
73
|
-
result = if raw_key.is_a?(Integer) then from_integer
|
74
|
-
|
75
|
-
|
76
|
-
elsif compressed_wif_format? then from_wif
|
77
|
-
elsif uncompressed_wif_format? then from_wif
|
78
|
-
else
|
79
|
-
raise KeyFormatNotFound
|
80
|
-
end
|
74
|
+
result = if raw_key.is_a?(Integer) then from_integer elsif hex_format? then from_hex elsif base64_format? then from_base64 elsif compressed_wif_format? then from_wif elsif uncompressed_wif_format? then from_wif else
|
75
|
+
raise KeyFormatNotFound
|
76
|
+
end
|
81
77
|
result.downcase
|
82
78
|
end
|
83
79
|
|
@@ -113,7 +109,7 @@ module MoneyTree
|
|
113
109
|
|
114
110
|
def wif_format?(compression)
|
115
111
|
length = compression == :compressed ? 52 : 51
|
116
|
-
wif_prefixes = MoneyTree::NETWORKS.map {|k, v| v["#{compression}_wif_chars".to_sym]}.flatten
|
112
|
+
wif_prefixes = MoneyTree::NETWORKS.map { |k, v| v["#{compression}_wif_chars".to_sym] }.flatten
|
117
113
|
raw_key.length == length && wif_prefixes.include?(raw_key.slice(0))
|
118
114
|
end
|
119
115
|
|
@@ -160,7 +156,6 @@ module MoneyTree
|
|
160
156
|
def to_s(network: :bitcoin)
|
161
157
|
to_wif(network: network)
|
162
158
|
end
|
163
|
-
|
164
159
|
end
|
165
160
|
|
166
161
|
class PublicKey < Key
|
@@ -209,19 +204,19 @@ module MoneyTree
|
|
209
204
|
self.compression = opts[:compressed] ? :compressed : :uncompressed
|
210
205
|
bn = BN.new int_to_hex(int), 16
|
211
206
|
@point = PKey::EC::Point.new group, bn
|
212
|
-
raise KeyInvalid,
|
207
|
+
raise KeyInvalid, "point is not on the curve" unless @point.on_curve?
|
213
208
|
end
|
214
209
|
|
215
210
|
def parse_raw_key
|
216
211
|
result = if raw_key.is_a?(Integer)
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
212
|
+
set_point raw_key
|
213
|
+
elsif hex_format?
|
214
|
+
set_point hex_to_int(raw_key), compressed: false
|
215
|
+
elsif compressed_hex_format?
|
216
|
+
set_point hex_to_int(raw_key), compressed: true
|
217
|
+
else
|
218
|
+
raise KeyFormatNotFound
|
219
|
+
end
|
225
220
|
to_hex
|
226
221
|
end
|
227
222
|
|
@@ -251,8 +246,20 @@ module MoneyTree
|
|
251
246
|
address = NETWORKS[network][:address_version] + hash
|
252
247
|
to_serialized_base58 address
|
253
248
|
end
|
249
|
+
|
254
250
|
alias :to_s :to_address
|
255
251
|
|
252
|
+
def to_p2wpkh_p2sh(network: :bitcoin)
|
253
|
+
prefix = [NETWORKS[network][:p2sh_version]].pack("H*")
|
254
|
+
convert_p2wpkh_p2sh(to_hex, prefix)
|
255
|
+
end
|
256
|
+
|
257
|
+
def to_bech32_address(network: :bitcoin)
|
258
|
+
hrp = NETWORKS[network][:human_readable_part]
|
259
|
+
witprog = to_ripemd160
|
260
|
+
to_serialized_bech32(hrp, witprog)
|
261
|
+
end
|
262
|
+
|
256
263
|
def to_fingerprint
|
257
264
|
hash = to_ripemd160
|
258
265
|
hash.slice(0..7)
|
data/lib/money-tree/networks.rb
CHANGED
@@ -1,33 +1,34 @@
|
|
1
1
|
module MoneyTree
|
2
|
-
NETWORKS =
|
3
|
-
begin
|
2
|
+
NETWORKS = begin
|
4
3
|
hsh = Hash.new do |_, key|
|
5
4
|
raise "#{key} is not a valid network!"
|
6
5
|
end.merge(
|
7
6
|
bitcoin: {
|
8
|
-
address_version:
|
9
|
-
p2sh_version:
|
10
|
-
p2sh_char:
|
11
|
-
privkey_version:
|
12
|
-
privkey_compression_flag:
|
7
|
+
address_version: "00",
|
8
|
+
p2sh_version: "05",
|
9
|
+
p2sh_char: "3",
|
10
|
+
privkey_version: "80",
|
11
|
+
privkey_compression_flag: "01",
|
13
12
|
extended_privkey_version: "0488ade4",
|
14
13
|
extended_pubkey_version: "0488b21e",
|
15
14
|
compressed_wif_chars: %w(K L),
|
16
15
|
uncompressed_wif_chars: %w(5),
|
17
|
-
protocol_version: 70001
|
16
|
+
protocol_version: 70001,
|
17
|
+
human_readable_part: "bc",
|
18
18
|
},
|
19
19
|
bitcoin_testnet: {
|
20
|
-
address_version:
|
21
|
-
p2sh_version:
|
22
|
-
p2sh_char:
|
23
|
-
privkey_version:
|
24
|
-
privkey_compression_flag:
|
20
|
+
address_version: "6f",
|
21
|
+
p2sh_version: "c4",
|
22
|
+
p2sh_char: "2",
|
23
|
+
privkey_version: "ef",
|
24
|
+
privkey_compression_flag: "01",
|
25
25
|
extended_privkey_version: "04358394",
|
26
26
|
extended_pubkey_version: "043587cf",
|
27
27
|
compressed_wif_chars: %w(c),
|
28
28
|
uncompressed_wif_chars: %w(9),
|
29
|
-
protocol_version: 70001
|
30
|
-
|
29
|
+
protocol_version: 70001,
|
30
|
+
human_readable_part: "tb",
|
31
|
+
},
|
31
32
|
)
|
32
33
|
hsh[:testnet3] = hsh[:bitcoin_testnet]
|
33
34
|
hsh
|
data/lib/money-tree/node.rb
CHANGED
@@ -2,8 +2,14 @@ module MoneyTree
|
|
2
2
|
class Node
|
3
3
|
include Support
|
4
4
|
extend Support
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
attr_reader :private_key
|
7
|
+
attr_reader :public_key
|
8
|
+
attr_reader :chain_code
|
9
|
+
attr_reader :is_private
|
10
|
+
attr_reader :depth
|
11
|
+
attr_reader :index
|
12
|
+
attr_reader :parent
|
7
13
|
|
8
14
|
class PublicDerivationFailure < StandardError; end
|
9
15
|
class InvalidKeyForIndex < StandardError; end
|
@@ -21,26 +27,21 @@ module MoneyTree
|
|
21
27
|
depth: hex.slice!(0..1).to_i(16),
|
22
28
|
parent_fingerprint: hex.slice!(0..7),
|
23
29
|
index: hex.slice!(0..7).to_i(16),
|
24
|
-
chain_code: hex.slice!(0..63).to_i(16)
|
30
|
+
chain_code: hex.slice!(0..63).to_i(16),
|
25
31
|
}.merge(parse_out_key(hex)))
|
26
32
|
end
|
27
33
|
|
28
|
-
def self.from_serialized_address(address)
|
29
|
-
puts 'Node.from_serialized_address is DEPRECATED. Please use .from_bip32 instead.'
|
30
|
-
from_bip32(address)
|
31
|
-
end
|
32
|
-
|
33
34
|
def self.parse_out_key(hex)
|
34
|
-
if hex.slice(0..1) ==
|
35
|
+
if hex.slice(0..1) == "00"
|
35
36
|
private_key = MoneyTree::PrivateKey.new(key: hex.slice(2..-1))
|
36
37
|
{
|
37
38
|
private_key: private_key,
|
38
|
-
public_key: MoneyTree::PublicKey.new(private_key)
|
39
|
+
public_key: MoneyTree::PublicKey.new(private_key),
|
39
40
|
}
|
40
41
|
elsif %w(02 03).include? hex.slice(0..1)
|
41
42
|
{ public_key: MoneyTree::PublicKey.new(hex) }
|
42
43
|
else
|
43
|
-
raise ImportError,
|
44
|
+
raise ImportError, "Public or private key data does not match version type"
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
@@ -50,7 +51,7 @@ module MoneyTree
|
|
50
51
|
|
51
52
|
def index_hex(i = index)
|
52
53
|
if i < 0
|
53
|
-
[i].pack(
|
54
|
+
[i].pack("l>").unpack("H*").first
|
54
55
|
else
|
55
56
|
i.to_s(16).rjust(8, "0")
|
56
57
|
end
|
@@ -69,16 +70,16 @@ module MoneyTree
|
|
69
70
|
end
|
70
71
|
|
71
72
|
def i_as_bytes(i)
|
72
|
-
[i].pack(
|
73
|
+
[i].pack("N")
|
73
74
|
end
|
74
75
|
|
75
76
|
def derive_private_key(i = 0)
|
76
77
|
message = i >= 0x80000000 || i < 0 ? private_derivation_message(i) : public_derivation_message(i)
|
77
78
|
hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
|
78
79
|
left_int = left_from_hash(hash)
|
79
|
-
raise InvalidKeyForIndex,
|
80
|
+
raise InvalidKeyForIndex, "greater than or equal to order" if left_int >= MoneyTree::Key::ORDER # very low probability
|
80
81
|
child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
|
81
|
-
raise InvalidKeyForIndex,
|
82
|
+
raise InvalidKeyForIndex, "equal to zero" if child_private_key == 0 # very low probability
|
82
83
|
child_chain_code = right_from_hash(hash)
|
83
84
|
return child_private_key, child_chain_code
|
84
85
|
end
|
@@ -88,7 +89,7 @@ module MoneyTree
|
|
88
89
|
message = public_derivation_message(i)
|
89
90
|
hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
|
90
91
|
left_int = left_from_hash(hash)
|
91
|
-
raise InvalidKeyForIndex,
|
92
|
+
raise InvalidKeyForIndex, "greater than or equal to order" if left_int >= MoneyTree::Key::ORDER # very low probability
|
92
93
|
factor = BN.new left_int.to_s
|
93
94
|
|
94
95
|
gen_point = public_key.uncompressed.group.generator.mul(factor)
|
@@ -96,7 +97,7 @@ module MoneyTree
|
|
96
97
|
sum_point_hex = MoneyTree::OpenSSLExtensions.add(gen_point, public_key.uncompressed.point)
|
97
98
|
child_public_key = OpenSSL::PKey::EC::Point.new(public_key.group, OpenSSL::BN.new(sum_point_hex, 16)).to_bn.to_i
|
98
99
|
|
99
|
-
raise InvalidKeyForIndex,
|
100
|
+
raise InvalidKeyForIndex, "at infinity" if child_public_key == 1 / 0.0 # very low probability
|
100
101
|
child_chain_code = right_from_hash(hash)
|
101
102
|
return child_public_key, child_chain_code
|
102
103
|
end
|
@@ -125,12 +126,7 @@ module MoneyTree
|
|
125
126
|
to_serialized_base58 to_serialized_hex(type, network: network)
|
126
127
|
end
|
127
128
|
|
128
|
-
def
|
129
|
-
puts 'Node.to_serialized_address is DEPRECATED. Please use .to_bip32.'
|
130
|
-
to_bip32(type, network: network)
|
131
|
-
end
|
132
|
-
|
133
|
-
def to_identifier(compressed=true)
|
129
|
+
def to_identifier(compressed = true)
|
134
130
|
key = compressed ? public_key.compressed : public_key.uncompressed
|
135
131
|
key.to_ripemd160
|
136
132
|
end
|
@@ -143,15 +139,25 @@ module MoneyTree
|
|
143
139
|
if @parent_fingerprint
|
144
140
|
@parent_fingerprint
|
145
141
|
else
|
146
|
-
depth.zero? ?
|
142
|
+
depth.zero? ? "00000000" : parent.to_fingerprint
|
147
143
|
end
|
148
144
|
end
|
149
145
|
|
150
|
-
def to_address(compressed=true, network: :bitcoin)
|
146
|
+
def to_address(compressed = true, network: :bitcoin)
|
151
147
|
address = NETWORKS[network][:address_version] + to_identifier(compressed)
|
152
148
|
to_serialized_base58 address
|
153
149
|
end
|
154
150
|
|
151
|
+
def to_p2wpkh_p2sh(network: :bitcoin)
|
152
|
+
public_key.to_p2wpkh_p2sh(network: network)
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_bech32_address(network: :bitcoin)
|
156
|
+
hrp = NETWORKS[network][:human_readable_part]
|
157
|
+
witprog = to_identifier
|
158
|
+
to_serialized_bech32(hrp, witprog)
|
159
|
+
end
|
160
|
+
|
155
161
|
def subnode(i = 0, opts = {})
|
156
162
|
if private_key.nil?
|
157
163
|
child_public_key, child_chain_code = derive_public_key(i)
|
@@ -162,7 +168,7 @@ module MoneyTree
|
|
162
168
|
child_public_key = MoneyTree::PublicKey.new child_private_key
|
163
169
|
end
|
164
170
|
|
165
|
-
MoneyTree::Node.new(
|
171
|
+
MoneyTree::Node.new(depth: depth + 1,
|
166
172
|
index: i,
|
167
173
|
private_key: private_key.nil? ? nil : child_private_key,
|
168
174
|
public_key: child_public_key,
|
@@ -182,9 +188,9 @@ module MoneyTree
|
|
182
188
|
#
|
183
189
|
# You should choose either the p or the negative number convention for private key derivation.
|
184
190
|
def node_for_path(path)
|
185
|
-
force_public = path[-4..-1] ==
|
191
|
+
force_public = path[-4..-1] == ".pub"
|
186
192
|
path = path[0..-5] if force_public
|
187
|
-
parts = path.split(
|
193
|
+
parts = path.split("/")
|
188
194
|
nodes = []
|
189
195
|
parts.each_with_index do |part, depth|
|
190
196
|
if part =~ /m/i
|
@@ -195,7 +201,7 @@ module MoneyTree
|
|
195
201
|
nodes << node.subnode(i)
|
196
202
|
end
|
197
203
|
end
|
198
|
-
if force_public or parts.first ==
|
204
|
+
if force_public or parts.first == "M"
|
199
205
|
node = nodes.last
|
200
206
|
node.strip_private_info!
|
201
207
|
node
|
@@ -209,12 +215,12 @@ module MoneyTree
|
|
209
215
|
i = path_part.to_i
|
210
216
|
|
211
217
|
i = if i < 0
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
+
i
|
219
|
+
elsif is_prime
|
220
|
+
i | 0x80000000
|
221
|
+
else
|
222
|
+
i & 0x7fffffff
|
223
|
+
end
|
218
224
|
end
|
219
225
|
|
220
226
|
def strip_private_info!
|
@@ -251,7 +257,7 @@ module MoneyTree
|
|
251
257
|
raise SeedGeneration::ImportError unless seed_valid?(@seed_hash)
|
252
258
|
set_seeded_keys
|
253
259
|
elsif opts[:private_key] || opts[:public_key]
|
254
|
-
raise ImportError,
|
260
|
+
raise ImportError, "chain code required" unless opts[:chain_code]
|
255
261
|
@chain_code = opts[:chain_code]
|
256
262
|
if opts[:private_key]
|
257
263
|
@private_key = opts[:private_key]
|
@@ -261,8 +267,7 @@ module MoneyTree
|
|
261
267
|
opts[:public_key]
|
262
268
|
else
|
263
269
|
MoneyTree::PublicKey.new(opts[:public_key])
|
264
|
-
end
|
265
|
-
end
|
270
|
+
end end
|
266
271
|
else
|
267
272
|
generate_seed
|
268
273
|
set_seeded_keys
|
data/lib/money-tree/support.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "base64"
|
2
|
+
require "bech32"
|
3
|
+
require "openssl"
|
3
4
|
|
4
5
|
module MoneyTree
|
5
6
|
module Support
|
6
7
|
include OpenSSL
|
8
|
+
extend self
|
7
9
|
|
8
10
|
INT32_MAX = 256 ** [1].pack("L*").size
|
9
11
|
INT64_MAX = 256 ** [1].pack("Q*").size
|
10
12
|
BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
11
13
|
|
12
|
-
def int_to_base58(int_val, leading_zero_bytes=0)
|
13
|
-
base58_val, base =
|
14
|
+
def int_to_base58(int_val, leading_zero_bytes = 0)
|
15
|
+
base58_val, base = "", BASE58_CHARS.size
|
14
16
|
while int_val > 0
|
15
17
|
int_val, remainder = int_val.divmod(base)
|
16
18
|
base58_val = BASE58_CHARS[remainder] + base58_val
|
@@ -20,25 +22,26 @@ module MoneyTree
|
|
20
22
|
|
21
23
|
def base58_to_int(base58_val)
|
22
24
|
int_val, base = 0, BASE58_CHARS.size
|
23
|
-
base58_val.reverse.each_char.with_index do |char,index|
|
24
|
-
raise ArgumentError,
|
25
|
-
int_val += char_index*(base**index)
|
25
|
+
base58_val.reverse.each_char.with_index do |char, index|
|
26
|
+
raise ArgumentError, "Value not a valid Base58 String." unless char_index = BASE58_CHARS.index(char)
|
27
|
+
int_val += char_index * (base ** index)
|
26
28
|
end
|
27
29
|
int_val
|
28
30
|
end
|
29
31
|
|
30
32
|
def encode_base58(hex)
|
31
|
-
leading_zero_bytes
|
32
|
-
("1"*leading_zero_bytes) + int_to_base58(
|
33
|
+
leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : "").size / 2
|
34
|
+
("1" * leading_zero_bytes) + int_to_base58(hex.to_i(16))
|
33
35
|
end
|
34
36
|
|
35
37
|
def decode_base58(base58_val)
|
36
|
-
s = base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ?
|
37
|
-
s =
|
38
|
-
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 :
|
39
|
-
s = ("00"*leading_zero_bytes) + s
|
38
|
+
s = base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? "0" + s : s)
|
39
|
+
s = "" if s == "00"
|
40
|
+
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : "").size
|
41
|
+
s = ("00" * leading_zero_bytes) + s if leading_zero_bytes > 0
|
40
42
|
s
|
41
43
|
end
|
44
|
+
|
42
45
|
alias_method :base58_to_hex, :decode_base58
|
43
46
|
|
44
47
|
def to_serialized_base58(hex)
|
@@ -49,6 +52,13 @@ module MoneyTree
|
|
49
52
|
encode_base58 address
|
50
53
|
end
|
51
54
|
|
55
|
+
def to_serialized_bech32(human_readable_part, hex)
|
56
|
+
segwit_addr = Bech32::SegwitAddr.new
|
57
|
+
segwit_addr.hrp = human_readable_part
|
58
|
+
segwit_addr.script_pubkey = "0000" + hex
|
59
|
+
segwit_addr.addr
|
60
|
+
end
|
61
|
+
|
52
62
|
def from_serialized_base58(base58)
|
53
63
|
hex = decode_base58 base58
|
54
64
|
checksum = hex.slice!(-8..-1)
|
@@ -63,11 +73,11 @@ module MoneyTree
|
|
63
73
|
end
|
64
74
|
|
65
75
|
def sha256(source, opts = {})
|
66
|
-
digestify(
|
76
|
+
digestify("SHA256", source, opts)
|
67
77
|
end
|
68
78
|
|
69
79
|
def ripemd160(source, opts = {})
|
70
|
-
digestify(
|
80
|
+
digestify("RIPEMD160", source, opts)
|
71
81
|
end
|
72
82
|
|
73
83
|
def encode_base64(hex)
|
@@ -85,7 +95,7 @@ module MoneyTree
|
|
85
95
|
|
86
96
|
def hmac_sha512_hex(key, message)
|
87
97
|
md = hmac_sha512(key, message)
|
88
|
-
md.unpack("H*").first.rjust(64,
|
98
|
+
md.unpack("H*").first.rjust(64, "0")
|
89
99
|
end
|
90
100
|
|
91
101
|
def bytes_to_int(bytes, base = 16)
|
@@ -95,7 +105,7 @@ module MoneyTree
|
|
95
105
|
bytes.unpack("H*")[0].to_i(16)
|
96
106
|
end
|
97
107
|
|
98
|
-
def int_to_hex(i, size=nil)
|
108
|
+
def int_to_hex(i, size = nil)
|
99
109
|
hex = i.to_s(16).downcase
|
100
110
|
if (hex.size % 2) != 0
|
101
111
|
hex = "#{0}#{hex}"
|
@@ -123,5 +133,20 @@ module MoneyTree
|
|
123
133
|
def hex_to_int(hex)
|
124
134
|
hex.to_i(16)
|
125
135
|
end
|
136
|
+
|
137
|
+
def encode_p2wpkh_p2sh(value)
|
138
|
+
chk = [Digest::SHA256.hexdigest(Digest::SHA256.digest(value))].pack("H*")[0...4]
|
139
|
+
encode_base58 (value + chk).unpack("H*")[0]
|
140
|
+
end
|
141
|
+
|
142
|
+
def custom_hash_160(value)
|
143
|
+
[OpenSSL::Digest::RIPEMD160.hexdigest(Digest::SHA256.digest(value))].pack("H*")
|
144
|
+
end
|
145
|
+
|
146
|
+
def convert_p2wpkh_p2sh(key_hex, prefix)
|
147
|
+
push_20 = ["0014"].pack("H*")
|
148
|
+
script_sig = push_20 + custom_hash_160([key_hex].pack("H*"))
|
149
|
+
encode_p2wpkh_p2sh(prefix + custom_hash_160(script_sig))
|
150
|
+
end
|
126
151
|
end
|
127
152
|
end
|
data/lib/money-tree/version.rb
CHANGED
data/lib/money-tree.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require "openssl_extensions"
|
2
|
-
require "money-tree/version"
|
3
1
|
require "money-tree/support"
|
4
|
-
|
5
|
-
require "money-tree/key"
|
2
|
+
|
6
3
|
require "money-tree/address"
|
4
|
+
require "money-tree/key"
|
7
5
|
require "money-tree/networks"
|
8
6
|
require "money-tree/node"
|
7
|
+
require "money-tree/version"
|
8
|
+
|
9
|
+
require "openssl_extensions"
|
9
10
|
|
10
|
-
module MoneyTree
|
11
|
-
end
|
11
|
+
module MoneyTree; end
|
data/lib/openssl_extensions.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# encoding: ascii-8bit
|
2
2
|
|
3
|
-
require
|
3
|
+
require "openssl"
|
4
4
|
|
5
5
|
module MoneyTree
|
6
6
|
module OpenSSLExtensions
|
7
|
-
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def add(point_0, point_1)
|
8
10
|
validate_points(point_0, point_1)
|
9
|
-
group = OpenSSL::PKey::EC::Group.new(
|
11
|
+
group = OpenSSL::PKey::EC::Group.new("secp256k1")
|
10
12
|
point_0_hex = point_0.to_bn.to_s(16)
|
11
13
|
point_0_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_0_hex, 16))
|
12
14
|
point_1_hex = point_1.to_bn.to_s(16)
|
@@ -15,7 +17,7 @@ module MoneyTree
|
|
15
17
|
sum_point.to_bn.to_s(16)
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
20
|
+
def validate_points(*points)
|
19
21
|
points.each do |point|
|
20
22
|
if !point.is_a?(OpenSSL::PKey::EC::Point)
|
21
23
|
raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
|