money-tree 0.11.0 → 0.11.2
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/code.yml +44 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +4 -2
- data/Gemfile +13 -2
- data/LICENSE.txt +1 -1
- 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.11.1.gem.sha512 +1 -0
- data/checksum/money-tree-0.9.0.gem.sha512 +1 -1
- data/config/openssl.conf +14 -0
- data/lib/money-tree/address.rb +14 -4
- data/lib/money-tree/key.rb +45 -31
- data/lib/money-tree/networks.rb +16 -15
- data/lib/money-tree/node.rb +65 -45
- 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 +24 -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 +552 -15
- data/spec/{lib/money-tree → money-tree}/openssl_extensions_spec.rb +4 -4
- 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 +39 -68
- data/certs/mattatgemco.pem +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53b2d866f0baa17b011631e360bda7c118100ce772cb2ce7b681ac03381b5f3e
|
4
|
+
data.tar.gz: dc39341115ffc116c132e1f030084eafced123506af17943f19e4c1bd0847841
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d4c68b7ef831c84b48c68e715007dbb16002c7a148c681d5db192216fd4be33f1767dd2a93cab0c09d87722fb7af475aa766e2bf4a561c76302ccf36747c7ca
|
7
|
+
data.tar.gz: 3019c78a855717e4d98b2f7f4d7a0458aac6c880fed23e6f81ae74587b9f0c4d2e122357ee95a481bb253b45e71b8f9e9c339cd367c2ccf1fd128131c326a2d7
|
@@ -0,0 +1,44 @@
|
|
1
|
+
---
|
2
|
+
name: CodeQL
|
3
|
+
|
4
|
+
on:
|
5
|
+
pull_request:
|
6
|
+
branches:
|
7
|
+
- master
|
8
|
+
push:
|
9
|
+
branches:
|
10
|
+
- master
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
analyze:
|
14
|
+
name: Analyze
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
permissions:
|
17
|
+
actions: read
|
18
|
+
contents: read
|
19
|
+
security-events: write
|
20
|
+
strategy:
|
21
|
+
fail-fast: false
|
22
|
+
matrix:
|
23
|
+
language:
|
24
|
+
- ruby
|
25
|
+
steps:
|
26
|
+
- name: "Checkout repository"
|
27
|
+
uses: actions/checkout@v3
|
28
|
+
- name: "Initialize CodeQL"
|
29
|
+
uses: github/codeql-action/init@v2
|
30
|
+
with:
|
31
|
+
languages: "${{ matrix.language }}"
|
32
|
+
- name: Autobuild
|
33
|
+
uses: github/codeql-action/autobuild@v2
|
34
|
+
- name: "Perform CodeQL Analysis"
|
35
|
+
uses: github/codeql-action/analyze@v2
|
36
|
+
- uses: ruby/setup-ruby@v1
|
37
|
+
with:
|
38
|
+
ruby-version: '3.0'
|
39
|
+
bundler-cache: true
|
40
|
+
- name: "Run rufo code formatting checks"
|
41
|
+
run: |
|
42
|
+
gem install rufo
|
43
|
+
rufo --check ./lib
|
44
|
+
rufo --check ./spec
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
name: Docs
|
3
|
+
|
4
|
+
on:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- master
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
docs:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v2
|
14
|
+
- uses: ruby/setup-ruby@v1
|
15
|
+
with:
|
16
|
+
ruby-version: '3.0'
|
17
|
+
bundler-cache: true
|
18
|
+
- name: Run Yard Doc
|
19
|
+
run: |
|
20
|
+
gem install yard
|
21
|
+
yard doc
|
22
|
+
- name: Deploy GH Pages
|
23
|
+
uses: JamesIves/github-pages-deploy-action@4.4.1
|
24
|
+
with:
|
25
|
+
branch: gh-pages
|
26
|
+
folder: doc/
|
data/.github/workflows/spec.yml
CHANGED
@@ -15,8 +15,8 @@ jobs:
|
|
15
15
|
strategy:
|
16
16
|
fail-fast: false
|
17
17
|
matrix:
|
18
|
-
os: [ubuntu-
|
19
|
-
ruby: ['
|
18
|
+
os: [ubuntu-22.04, macos-12]
|
19
|
+
ruby: ['3.0', '3.1', '3.2']
|
20
20
|
steps:
|
21
21
|
- uses: actions/checkout@v2
|
22
22
|
- uses: ruby/setup-ruby@v1
|
@@ -29,3 +29,5 @@ jobs:
|
|
29
29
|
- name: Run Tests
|
30
30
|
run: |
|
31
31
|
bundle exec rspec
|
32
|
+
env:
|
33
|
+
COVERAGE: true
|
data/Gemfile
CHANGED
@@ -1,4 +1,15 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
group :test, :development do
|
4
|
+
gem "bundler", "~> 2.2"
|
5
|
+
gem "codecov", "~> 0.6"
|
6
|
+
gem "pry", "~> 0.14"
|
7
|
+
gem "rake", "~> 13.0"
|
8
|
+
gem "rdoc", "~> 6.5"
|
9
|
+
gem "rspec", "~> 3.12"
|
10
|
+
gem "rufo", "~> 0.13"
|
11
|
+
gem "simplecov", "~> 0.21"
|
12
|
+
gem "yard", "~> 0.9"
|
13
|
+
end
|
2
14
|
|
3
|
-
# Specify your gem's dependencies in money-tree.gemspec
|
4
15
|
gemspec
|
data/LICENSE.txt
CHANGED
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
|
+
fdc5329fa932c085d66ae40a7a9d730876b9a68a17932f0cd824c74a0562fafbfdc8fb2a3f92011138854ffbf7422bb8919e042dba46bcb55e11755bcf2bb5fb
|
@@ -0,0 +1 @@
|
|
1
|
+
eb2346654c00b8506127ac5a2e87a04ebdc2bcde9221fe1f9707c4d1522cc9280206aeca41a3a2a9430db4d8bcef0f7c27e95ef23755b54bcdda991e6886f067
|
@@ -1 +1 @@
|
|
1
|
-
ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
|
1
|
+
ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
|
data/config/openssl.conf
ADDED
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,34 +37,40 @@ module MoneyTree
|
|
35
37
|
end
|
36
38
|
|
37
39
|
class PrivateKey < Key
|
38
|
-
|
39
40
|
def initialize(opts = {})
|
40
41
|
@options = opts
|
41
|
-
|
42
|
+
generate
|
42
43
|
if @options[:key]
|
43
44
|
@raw_key = @options[:key]
|
44
45
|
@key = parse_raw_key
|
45
46
|
import
|
46
47
|
else
|
47
|
-
generate
|
48
48
|
@key = to_hex
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
def generate
|
53
|
-
ec_key.
|
53
|
+
@ec_key = PKey::EC.generate GROUP_NAME
|
54
54
|
end
|
55
55
|
|
56
56
|
def import
|
57
|
-
ec_key
|
58
|
-
|
57
|
+
@ec_key = OpenSSL::PKey::EC.new(data_sequence.to_der)
|
58
|
+
end
|
59
|
+
|
60
|
+
def data_sequence
|
61
|
+
OpenSSL::ASN1::Sequence([
|
62
|
+
OpenSSL::ASN1::Integer(1),
|
63
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(key, 16).to_s(2)),
|
64
|
+
OpenSSL::ASN1::ObjectId(GROUP_NAME, 0, :EXPLICIT),
|
65
|
+
OpenSSL::ASN1::BitString(calculate_public_key.to_octet_string(:uncompressed), 1, :EXPLICIT),
|
66
|
+
])
|
59
67
|
end
|
60
68
|
|
61
69
|
def calculate_public_key(opts = {})
|
62
70
|
opts[:compressed] = true unless opts[:compressed] == false
|
63
71
|
group = ec_key.group
|
64
72
|
group.point_conversion_form = opts[:compressed] ? :compressed : :uncompressed
|
65
|
-
point = group.generator.mul
|
73
|
+
point = group.generator.mul OpenSSL::BN.new(key, 16)
|
66
74
|
end
|
67
75
|
|
68
76
|
def set_public_key(opts = {})
|
@@ -70,14 +78,9 @@ module MoneyTree
|
|
70
78
|
end
|
71
79
|
|
72
80
|
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
|
81
|
+
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
|
82
|
+
raise KeyFormatNotFound
|
83
|
+
end
|
81
84
|
result.downcase
|
82
85
|
end
|
83
86
|
|
@@ -113,7 +116,7 @@ module MoneyTree
|
|
113
116
|
|
114
117
|
def wif_format?(compression)
|
115
118
|
length = compression == :compressed ? 52 : 51
|
116
|
-
wif_prefixes = MoneyTree::NETWORKS.map {|k, v| v["#{compression}_wif_chars".to_sym]}.flatten
|
119
|
+
wif_prefixes = MoneyTree::NETWORKS.map { |k, v| v["#{compression}_wif_chars".to_sym] }.flatten
|
117
120
|
raw_key.length == length && wif_prefixes.include?(raw_key.slice(0))
|
118
121
|
end
|
119
122
|
|
@@ -160,7 +163,6 @@ module MoneyTree
|
|
160
163
|
def to_s(network: :bitcoin)
|
161
164
|
to_wif(network: network)
|
162
165
|
end
|
163
|
-
|
164
166
|
end
|
165
167
|
|
166
168
|
class PublicKey < Key
|
@@ -209,19 +211,19 @@ module MoneyTree
|
|
209
211
|
self.compression = opts[:compressed] ? :compressed : :uncompressed
|
210
212
|
bn = BN.new int_to_hex(int), 16
|
211
213
|
@point = PKey::EC::Point.new group, bn
|
212
|
-
raise KeyInvalid,
|
214
|
+
raise KeyInvalid, "point is not on the curve" unless @point.on_curve?
|
213
215
|
end
|
214
216
|
|
215
217
|
def parse_raw_key
|
216
218
|
result = if raw_key.is_a?(Integer)
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
219
|
+
set_point raw_key
|
220
|
+
elsif hex_format?
|
221
|
+
set_point hex_to_int(raw_key), compressed: false
|
222
|
+
elsif compressed_hex_format?
|
223
|
+
set_point hex_to_int(raw_key), compressed: true
|
224
|
+
else
|
225
|
+
raise KeyFormatNotFound
|
226
|
+
end
|
225
227
|
to_hex
|
226
228
|
end
|
227
229
|
|
@@ -251,8 +253,20 @@ module MoneyTree
|
|
251
253
|
address = NETWORKS[network][:address_version] + hash
|
252
254
|
to_serialized_base58 address
|
253
255
|
end
|
256
|
+
|
254
257
|
alias :to_s :to_address
|
255
258
|
|
259
|
+
def to_p2wpkh_p2sh(network: :bitcoin)
|
260
|
+
prefix = [NETWORKS[network][:p2sh_version]].pack("H*")
|
261
|
+
convert_p2wpkh_p2sh(to_hex, prefix)
|
262
|
+
end
|
263
|
+
|
264
|
+
def to_bech32_address(network: :bitcoin)
|
265
|
+
hrp = NETWORKS[network][:human_readable_part]
|
266
|
+
witprog = to_ripemd160
|
267
|
+
to_serialized_bech32(hrp, witprog)
|
268
|
+
end
|
269
|
+
|
256
270
|
def to_fingerprint
|
257
271
|
hash = to_ripemd160
|
258
272
|
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,16 @@ 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
|
13
|
+
|
14
|
+
PRIVATE_RANGE_LIMIT = 0x80000000
|
7
15
|
|
8
16
|
class PublicDerivationFailure < StandardError; end
|
9
17
|
class InvalidKeyForIndex < StandardError; end
|
@@ -21,36 +29,31 @@ module MoneyTree
|
|
21
29
|
depth: hex.slice!(0..1).to_i(16),
|
22
30
|
parent_fingerprint: hex.slice!(0..7),
|
23
31
|
index: hex.slice!(0..7).to_i(16),
|
24
|
-
chain_code: hex.slice!(0..63).to_i(16)
|
32
|
+
chain_code: hex.slice!(0..63).to_i(16),
|
25
33
|
}.merge(parse_out_key(hex)))
|
26
34
|
end
|
27
35
|
|
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
36
|
def self.parse_out_key(hex)
|
34
|
-
if hex.slice(0..1) ==
|
37
|
+
if hex.slice(0..1) == "00"
|
35
38
|
private_key = MoneyTree::PrivateKey.new(key: hex.slice(2..-1))
|
36
39
|
{
|
37
40
|
private_key: private_key,
|
38
|
-
public_key: MoneyTree::PublicKey.new(private_key)
|
41
|
+
public_key: MoneyTree::PublicKey.new(private_key),
|
39
42
|
}
|
40
43
|
elsif %w(02 03).include? hex.slice(0..1)
|
41
44
|
{ public_key: MoneyTree::PublicKey.new(hex) }
|
42
45
|
else
|
43
|
-
raise ImportError,
|
46
|
+
raise ImportError, "Public or private key data does not match version type"
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
50
|
def is_private?
|
48
|
-
index >=
|
51
|
+
index >= PRIVATE_RANGE_LIMIT || index < 0
|
49
52
|
end
|
50
53
|
|
51
54
|
def index_hex(i = index)
|
52
55
|
if i < 0
|
53
|
-
[i].pack(
|
56
|
+
[i].pack("l>").unpack("H*").first
|
54
57
|
else
|
55
58
|
i.to_s(16).rjust(8, "0")
|
56
59
|
end
|
@@ -69,26 +72,37 @@ module MoneyTree
|
|
69
72
|
end
|
70
73
|
|
71
74
|
def i_as_bytes(i)
|
72
|
-
[i].pack(
|
75
|
+
[i].pack("N")
|
73
76
|
end
|
74
77
|
|
75
78
|
def derive_private_key(i = 0)
|
76
|
-
message = i >=
|
79
|
+
message = i >= PRIVATE_RANGE_LIMIT || i < 0 ? private_derivation_message(i) : public_derivation_message(i)
|
77
80
|
hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
|
78
81
|
left_int = left_from_hash(hash)
|
79
|
-
raise InvalidKeyForIndex,
|
82
|
+
raise InvalidKeyForIndex, "greater than or equal to order" if left_int >= MoneyTree::Key::ORDER # very low probability
|
80
83
|
child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
|
81
|
-
raise InvalidKeyForIndex,
|
84
|
+
raise InvalidKeyForIndex, "equal to zero" if child_private_key == 0 # very low probability
|
82
85
|
child_chain_code = right_from_hash(hash)
|
83
86
|
return child_private_key, child_chain_code
|
84
87
|
end
|
85
88
|
|
89
|
+
def derive_parent_node(parent_key)
|
90
|
+
message = parent_key.public_key.to_bytes << i_as_bytes(index)
|
91
|
+
hash = hmac_sha512 hex_to_bytes(parent_key.chain_code_hex), message
|
92
|
+
priv = (private_key.to_i - left_from_hash(hash)) % MoneyTree::Key::ORDER
|
93
|
+
MoneyTree::Node.new(depth: parent_key.depth,
|
94
|
+
index: parent_key.index,
|
95
|
+
private_key: MoneyTree::PrivateKey.new(key: priv),
|
96
|
+
public_key: parent_key.public_key,
|
97
|
+
chain_code: parent_key.chain_code)
|
98
|
+
end
|
99
|
+
|
86
100
|
def derive_public_key(i = 0)
|
87
|
-
raise PrivatePublicMismatch if i >=
|
101
|
+
raise PrivatePublicMismatch if i >= PRIVATE_RANGE_LIMIT
|
88
102
|
message = public_derivation_message(i)
|
89
103
|
hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
|
90
104
|
left_int = left_from_hash(hash)
|
91
|
-
raise InvalidKeyForIndex,
|
105
|
+
raise InvalidKeyForIndex, "greater than or equal to order" if left_int >= MoneyTree::Key::ORDER # very low probability
|
92
106
|
factor = BN.new left_int.to_s
|
93
107
|
|
94
108
|
gen_point = public_key.uncompressed.group.generator.mul(factor)
|
@@ -96,7 +110,7 @@ module MoneyTree
|
|
96
110
|
sum_point_hex = MoneyTree::OpenSSLExtensions.add(gen_point, public_key.uncompressed.point)
|
97
111
|
child_public_key = OpenSSL::PKey::EC::Point.new(public_key.group, OpenSSL::BN.new(sum_point_hex, 16)).to_bn.to_i
|
98
112
|
|
99
|
-
raise InvalidKeyForIndex,
|
113
|
+
raise InvalidKeyForIndex, "at infinity" if child_public_key == 1 / 0.0 # very low probability
|
100
114
|
child_chain_code = right_from_hash(hash)
|
101
115
|
return child_public_key, child_chain_code
|
102
116
|
end
|
@@ -125,12 +139,7 @@ module MoneyTree
|
|
125
139
|
to_serialized_base58 to_serialized_hex(type, network: network)
|
126
140
|
end
|
127
141
|
|
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)
|
142
|
+
def to_identifier(compressed = true)
|
134
143
|
key = compressed ? public_key.compressed : public_key.uncompressed
|
135
144
|
key.to_ripemd160
|
136
145
|
end
|
@@ -143,15 +152,25 @@ module MoneyTree
|
|
143
152
|
if @parent_fingerprint
|
144
153
|
@parent_fingerprint
|
145
154
|
else
|
146
|
-
depth.zero? ?
|
155
|
+
depth.zero? ? "00000000" : parent.to_fingerprint
|
147
156
|
end
|
148
157
|
end
|
149
158
|
|
150
|
-
def to_address(compressed=true, network: :bitcoin)
|
159
|
+
def to_address(compressed = true, network: :bitcoin)
|
151
160
|
address = NETWORKS[network][:address_version] + to_identifier(compressed)
|
152
161
|
to_serialized_base58 address
|
153
162
|
end
|
154
163
|
|
164
|
+
def to_p2wpkh_p2sh(network: :bitcoin)
|
165
|
+
public_key.to_p2wpkh_p2sh(network: network)
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_bech32_address(network: :bitcoin)
|
169
|
+
hrp = NETWORKS[network][:human_readable_part]
|
170
|
+
witprog = to_identifier
|
171
|
+
to_serialized_bech32(hrp, witprog)
|
172
|
+
end
|
173
|
+
|
155
174
|
def subnode(i = 0, opts = {})
|
156
175
|
if private_key.nil?
|
157
176
|
child_public_key, child_chain_code = derive_public_key(i)
|
@@ -162,7 +181,7 @@ module MoneyTree
|
|
162
181
|
child_public_key = MoneyTree::PublicKey.new child_private_key
|
163
182
|
end
|
164
183
|
|
165
|
-
MoneyTree::Node.new(
|
184
|
+
MoneyTree::Node.new(depth: depth + 1,
|
166
185
|
index: i,
|
167
186
|
private_key: private_key.nil? ? nil : child_private_key,
|
168
187
|
public_key: child_public_key,
|
@@ -182,9 +201,9 @@ module MoneyTree
|
|
182
201
|
#
|
183
202
|
# You should choose either the p or the negative number convention for private key derivation.
|
184
203
|
def node_for_path(path)
|
185
|
-
force_public = path[-4..-1] ==
|
204
|
+
force_public = path[-4..-1] == ".pub"
|
186
205
|
path = path[0..-5] if force_public
|
187
|
-
parts = path.split(
|
206
|
+
parts = path.split("/")
|
188
207
|
nodes = []
|
189
208
|
parts.each_with_index do |part, depth|
|
190
209
|
if part =~ /m/i
|
@@ -195,7 +214,7 @@ module MoneyTree
|
|
195
214
|
nodes << node.subnode(i)
|
196
215
|
end
|
197
216
|
end
|
198
|
-
if force_public or parts.first ==
|
217
|
+
if force_public or parts.first == "M"
|
199
218
|
node = nodes.last
|
200
219
|
node.strip_private_info!
|
201
220
|
node
|
@@ -204,17 +223,19 @@ module MoneyTree
|
|
204
223
|
end
|
205
224
|
end
|
206
225
|
|
207
|
-
def
|
208
|
-
|
209
|
-
|
226
|
+
def negative?(path_part)
|
227
|
+
relative_index = path_part.to_i
|
228
|
+
prime_symbol_present = %w(p ').include?(path_part[-1])
|
229
|
+
minus_present = path_part[0] == "-"
|
230
|
+
private_range = relative_index >= PRIVATE_RANGE_LIMIT
|
231
|
+
negative_value = relative_index < 0
|
232
|
+
prime_symbol_present || minus_present || private_range || negative_value
|
233
|
+
end
|
210
234
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
else
|
216
|
-
i & 0x7fffffff
|
217
|
-
end
|
235
|
+
def parse_index(path_part)
|
236
|
+
index = path_part.to_i
|
237
|
+
return index | PRIVATE_RANGE_LIMIT if negative?(path_part)
|
238
|
+
index
|
218
239
|
end
|
219
240
|
|
220
241
|
def strip_private_info!
|
@@ -251,7 +272,7 @@ module MoneyTree
|
|
251
272
|
raise SeedGeneration::ImportError unless seed_valid?(@seed_hash)
|
252
273
|
set_seeded_keys
|
253
274
|
elsif opts[:private_key] || opts[:public_key]
|
254
|
-
raise ImportError,
|
275
|
+
raise ImportError, "chain code required" unless opts[:chain_code]
|
255
276
|
@chain_code = opts[:chain_code]
|
256
277
|
if opts[:private_key]
|
257
278
|
@private_key = opts[:private_key]
|
@@ -261,8 +282,7 @@ module MoneyTree
|
|
261
282
|
opts[:public_key]
|
262
283
|
else
|
263
284
|
MoneyTree::PublicKey.new(opts[:public_key])
|
264
|
-
end
|
265
|
-
end
|
285
|
+
end end
|
266
286
|
else
|
267
287
|
generate_seed
|
268
288
|
set_seeded_keys
|