money-tree 0.11.0 → 0.11.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
+
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
|