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 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