money-tree 0.11.0 → 0.11.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 +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
|
[](https://github.com/GemHQ/money-tree/pulse)
|
7
7
|
|
8
8
|
[](https://github.com/GemHQ/money-tree/actions)
|
9
|
-
[](https://codecov.io/gh/GemHQ/money-tree)
|
10
10
|
[](https://codeclimate.com/github/GemHQ/money-tree)
|
11
11
|
[](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"
|