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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 040b86e807e6dea0ebf4dfc4b5e508e3f9ef673c94b95a210a18c125d8857974
4
- data.tar.gz: '01800e0fb32058488fd01f1a8f2be3539788c7d0bebc0021fd49776fdde947a4'
3
+ metadata.gz: 53b2d866f0baa17b011631e360bda7c118100ce772cb2ce7b681ac03381b5f3e
4
+ data.tar.gz: dc39341115ffc116c132e1f030084eafced123506af17943f19e4c1bd0847841
5
5
  SHA512:
6
- metadata.gz: 70a6465799b1f7140a2e45c2ded416b41e4b64a7801b57325ed20cea470c92cad96e7644596d523a192254e7284eb772406e90af0a797f5b367304cbe2e9b725
7
- data.tar.gz: 2d0c6bea3a33f161cf30a3400d3f75b6119098f035eca6cdac076c11c03596a2e0e205456b1e3ea2453f2ad1e58fd7edec91ff21ec1a8fd515c2d7836169762c
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/
@@ -15,8 +15,8 @@ jobs:
15
15
  strategy:
16
16
  fail-fast: false
17
17
  matrix:
18
- os: [ubuntu-latest, macos-latest]
19
- ruby: ['2.7', '3.0']
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 'https://rubygems.org'
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Micah Winkelspecht
1
+ Copyright (c) 2013-23 Micah Winkelspecht
2
2
 
3
3
  MIT License
4
4
 
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 Status](https://img.shields.io/coveralls/GemHQ/money-tree.svg)](https://coveralls.io/r/GemHQ/money-tree?branch=master)
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
@@ -1,5 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
- require 'rspec/core/rake_task'
2
+ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
@@ -0,0 +1 @@
1
+ fdc5329fa932c085d66ae40a7a9d730876b9a68a17932f0cd824c74a0562fafbfdc8fb2a3f92011138854ffbf7422bb8919e042dba46bcb55e11755bcf2bb5fb
@@ -0,0 +1 @@
1
+ eb2346654c00b8506127ac5a2e87a04ebdc2bcde9221fe1f9707c4d1522cc9280206aeca41a3a2a9430db4d8bcef0f7c27e95ef23755b54bcdda991e6886f067
@@ -1 +1 @@
1
- ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
1
+ ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
@@ -0,0 +1,14 @@
1
+ openssl_conf = openssl_init
2
+
3
+ [openssl_init]
4
+ providers = provider_sect
5
+
6
+ [provider_sect]
7
+ default = default_sect
8
+ legacy = legacy_sect
9
+
10
+ [default_sect]
11
+ activate = 1
12
+
13
+ [legacy_sect]
14
+ activate = 1
@@ -1,16 +1,26 @@
1
1
  module MoneyTree
2
2
  class Address
3
- attr_reader :private_key, :public_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 = MoneyTree::PrivateKey.new({ key: private_key }.merge(opts))
8
- @public_key = MoneyTree::PublicKey.new(@private_key, opts)
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
@@ -1,12 +1,11 @@
1
1
  # encoding ascii-8bit
2
2
 
3
- require 'openssl'
3
+ require "openssl"
4
4
 
5
5
  module MoneyTree
6
6
  class Key
7
- include OpenSSL
8
7
  include Support
9
- extend Support
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, :key, :raw_key
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 = 'secp256k1'
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
- @ec_key = PKey::EC.new GROUP_NAME
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.generate_key
53
+ @ec_key = PKey::EC.generate GROUP_NAME
54
54
  end
55
55
 
56
56
  def import
57
- ec_key.private_key = BN.new(key, 16)
58
- set_public_key
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 ec_key.private_key
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
- elsif hex_format? then from_hex
75
- elsif base64_format? then from_base64
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, 'point is not on the curve' unless @point.on_curve?
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
- set_point raw_key
218
- elsif hex_format?
219
- set_point hex_to_int(raw_key), compressed: false
220
- elsif compressed_hex_format?
221
- set_point hex_to_int(raw_key), compressed: true
222
- else
223
- raise KeyFormatNotFound
224
- end
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)
@@ -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: '00',
9
- p2sh_version: '05',
10
- p2sh_char: '3',
11
- privkey_version: '80',
12
- privkey_compression_flag: '01',
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: '6f',
21
- p2sh_version: 'c4',
22
- p2sh_char: '2',
23
- privkey_version: 'ef',
24
- privkey_compression_flag: '01',
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
@@ -2,8 +2,16 @@ module MoneyTree
2
2
  class Node
3
3
  include Support
4
4
  extend Support
5
- attr_reader :private_key, :public_key, :chain_code,
6
- :is_private, :depth, :index, :parent
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) == '00'
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, 'Public or private key data does not match version type'
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 >= 0x80000000 || index < 0
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('l>').unpack('H*').first
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('N')
75
+ [i].pack("N")
73
76
  end
74
77
 
75
78
  def derive_private_key(i = 0)
76
- message = i >= 0x80000000 || i < 0 ? private_derivation_message(i) : public_derivation_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, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
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, 'equal to zero' if child_private_key == 0 # very low probability
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 >= 0x80000000
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, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
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, 'at infinity' if child_public_key == 1/0.0 # very low probability
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 to_serialized_address(type = :public, network: :bitcoin)
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? ? '00000000' : parent.to_fingerprint
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( depth: depth+1,
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] == '.pub'
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 == 'M'
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 parse_index(path_part)
208
- is_prime = %w(p ').include? path_part[-1]
209
- i = path_part.to_i
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
- i = if i < 0
212
- i
213
- elsif is_prime
214
- i | 0x80000000
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, 'chain code required' unless opts[:chain_code]
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