bitcoinrb 0.3.2 → 0.8.0

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +17 -6
  6. data/bitcoinrb.gemspec +9 -8
  7. data/exe/bitcoinrbd +5 -0
  8. data/lib/bitcoin.rb +37 -19
  9. data/lib/bitcoin/bip85_entropy.rb +111 -0
  10. data/lib/bitcoin/block_filter.rb +14 -0
  11. data/lib/bitcoin/block_header.rb +2 -0
  12. data/lib/bitcoin/chain_params.rb +9 -8
  13. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  14. data/lib/bitcoin/chainparams/signet.yml +39 -0
  15. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  16. data/lib/bitcoin/constants.rb +44 -10
  17. data/lib/bitcoin/descriptor.rb +1 -1
  18. data/lib/bitcoin/errors.rb +19 -0
  19. data/lib/bitcoin/ext.rb +6 -0
  20. data/lib/bitcoin/ext/array_ext.rb +22 -0
  21. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  22. data/lib/bitcoin/ext/json_parser.rb +46 -0
  23. data/lib/bitcoin/ext_key.rb +51 -20
  24. data/lib/bitcoin/key.rb +89 -30
  25. data/lib/bitcoin/key_path.rb +12 -5
  26. data/lib/bitcoin/message.rb +79 -0
  27. data/lib/bitcoin/message/addr_v2.rb +34 -0
  28. data/lib/bitcoin/message/base.rb +17 -0
  29. data/lib/bitcoin/message/cf_parser.rb +16 -0
  30. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  31. data/lib/bitcoin/message/cfheaders.rb +40 -0
  32. data/lib/bitcoin/message/cfilter.rb +35 -0
  33. data/lib/bitcoin/message/fee_filter.rb +1 -1
  34. data/lib/bitcoin/message/filter_load.rb +3 -3
  35. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  36. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  37. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  38. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  39. data/lib/bitcoin/message/inventory.rb +1 -1
  40. data/lib/bitcoin/message/merkle_block.rb +1 -1
  41. data/lib/bitcoin/message/network_addr.rb +141 -18
  42. data/lib/bitcoin/message/ping.rb +1 -1
  43. data/lib/bitcoin/message/pong.rb +1 -1
  44. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  45. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  46. data/lib/bitcoin/message/tx.rb +1 -1
  47. data/lib/bitcoin/message/version.rb +7 -0
  48. data/lib/bitcoin/message_sign.rb +47 -0
  49. data/lib/bitcoin/mnemonic.rb +7 -7
  50. data/lib/bitcoin/network/peer.rb +9 -4
  51. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  52. data/lib/bitcoin/node/cli.rb +14 -10
  53. data/lib/bitcoin/node/configuration.rb +3 -1
  54. data/lib/bitcoin/node/spv.rb +9 -1
  55. data/lib/bitcoin/opcodes.rb +14 -1
  56. data/lib/bitcoin/out_point.rb +2 -0
  57. data/lib/bitcoin/payment_code.rb +92 -0
  58. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  59. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  60. data/lib/bitcoin/psbt/input.rb +9 -18
  61. data/lib/bitcoin/psbt/output.rb +1 -1
  62. data/lib/bitcoin/psbt/tx.rb +12 -17
  63. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  64. data/lib/bitcoin/rpc/request_handler.rb +5 -5
  65. data/lib/bitcoin/script/script.rb +96 -39
  66. data/lib/bitcoin/script/script_error.rb +27 -1
  67. data/lib/bitcoin/script/script_interpreter.rb +166 -66
  68. data/lib/bitcoin/script/tx_checker.rb +62 -14
  69. data/lib/bitcoin/secp256k1.rb +1 -0
  70. data/lib/bitcoin/secp256k1/native.rb +184 -17
  71. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  72. data/lib/bitcoin/secp256k1/ruby.rb +112 -56
  73. data/lib/bitcoin/sighash_generator.rb +156 -0
  74. data/lib/bitcoin/store.rb +1 -0
  75. data/lib/bitcoin/store/chain_entry.rb +1 -0
  76. data/lib/bitcoin/store/utxo_db.rb +226 -0
  77. data/lib/bitcoin/taproot.rb +9 -0
  78. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  79. data/lib/bitcoin/taproot/simple_builder.rb +139 -0
  80. data/lib/bitcoin/tx.rb +34 -104
  81. data/lib/bitcoin/tx_in.rb +4 -5
  82. data/lib/bitcoin/tx_out.rb +2 -3
  83. data/lib/bitcoin/util.rb +22 -6
  84. data/lib/bitcoin/version.rb +1 -1
  85. data/lib/bitcoin/wallet.rb +1 -0
  86. data/lib/bitcoin/wallet/account.rb +2 -1
  87. data/lib/bitcoin/wallet/base.rb +2 -2
  88. data/lib/bitcoin/wallet/master_key.rb +1 -0
  89. data/lib/bitcoin/wallet/utxo.rb +37 -0
  90. metadata +86 -32
  91. data/.travis.yml +0 -11
@@ -9,7 +9,7 @@ privkey_version: "ef"
9
9
  extended_privkey_version: "04358394"
10
10
  extended_pubkey_version: "043587cf"
11
11
  bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
12
- bip49_pubkey_p2wsh_p2sh_version: "024285ef"
12
+ bip49_pubkey_p2wsh_p2sh_version: "024289ef"
13
13
  bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
14
14
  bip49_privkey_p2wsh_p2sh_version: "024285b5"
15
15
  bip84_pubkey_p2wpkh_version: "045f1cf6"
@@ -0,0 +1,39 @@
1
+ --- !ruby/object:Bitcoin::ChainParams
2
+ network: "signet"
3
+ magic_head: "0a03cf40"
4
+ message_magic: "Bitcoin Signed Message:\n"
5
+ address_version: "6f"
6
+ p2sh_version: "c4"
7
+ bech32_hrp: 'tb'
8
+ privkey_version: "ef"
9
+ extended_privkey_version: "04358394"
10
+ extended_pubkey_version: "043587cf"
11
+ bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
12
+ bip49_pubkey_p2wsh_p2sh_version: "024289ef"
13
+ bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
14
+ bip49_privkey_p2wsh_p2sh_version: "024285b5"
15
+ bip84_pubkey_p2wpkh_version: "045f1cf6"
16
+ bip84_pubkey_p2wsh_version: "02575483"
17
+ bip84_privkey_p2wpkh_version: "045f18bc"
18
+ bip84_privkey_p2wsh_version: "02575048"
19
+ default_port: 38333
20
+ protocol_version: 70013
21
+ retarget_interval: 2016
22
+ retarget_time: 1209600 # 2 weeks
23
+ target_spacing: 600 # block interval
24
+ max_money: 21000000
25
+ bip34_height: 227931
26
+ genesis_hash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
27
+ proof_of_work_limit: 0x1d00ffff
28
+ dns_seeds:
29
+ - "178.128.221.177"
30
+ - "2a01:7c8:d005:390::5"
31
+ genesis:
32
+ hash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
33
+ merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
34
+ time: 1598918400
35
+ nonce: 52613770
36
+ bits: 0x1e0377ae
37
+ version: 1
38
+ prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
39
+ bip44_coin_type: 1
@@ -9,7 +9,7 @@ privkey_version: "ef"
9
9
  extended_privkey_version: "04358394"
10
10
  extended_pubkey_version: "043587cf"
11
11
  bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
12
- bip49_pubkey_p2wsh_p2sh_version: "024285ef"
12
+ bip49_pubkey_p2wsh_p2sh_version: "024289ef"
13
13
  bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
14
14
  bip49_privkey_p2wsh_p2sh_version: "024285b5"
15
15
  bip84_pubkey_p2wpkh_version: "045f1cf6"
@@ -44,6 +44,11 @@ module Bitcoin
44
44
  SCRIPT_VERIFY_NULLFAIL = (1 << 14) # Signature(s) must be empty vector if an CHECK(MULTI)SIG operation failed
45
45
  SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1 << 15) # Public keys in segregated witness scripts must be compressed
46
46
  SCRIPT_VERIFY_CONST_SCRIPTCODE = (1 << 16) # Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts
47
+ SCRIPT_VERIFY_TAPROOT = (1 << 17) # Taproot/Tapscript validation (BIPs 341 & 342)
48
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1 << 18) # Making unknown Taproot leaf versions non-standard
49
+ SCRIPT_VERIFY_DISCOURAGE_UNKNOWN_ANNEX = (1 << 19) # Making the use of (unknown) annexes non-standard (currently no annexes are known)
50
+ SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1 << 20) # Making unknown OP_SUCCESS non-standard
51
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1 << 21) # Making unknown public key versions (in BIP 342 scripts) non-standard
47
52
 
48
53
  MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
49
54
 
@@ -68,7 +73,8 @@ module Bitcoin
68
73
  # for script
69
74
 
70
75
  # witness version
71
- WITNESS_VERSION = 0x00
76
+ WITNESS_VERSION_V0 = 0x00
77
+ WITNESS_VERSION_V1 = Bitcoin::Opcodes::OP_1
72
78
 
73
79
  # Maximum script length in bytes
74
80
  MAX_SCRIPT_SIZE = 10000
@@ -88,15 +94,19 @@ module Bitcoin
88
94
  # Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
89
95
  LOCKTIME_THRESHOLD = 500000000
90
96
 
91
- # Signature hash types/flags
92
- SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
97
+ # Tag for input annex. If there are at least two witness elements for a transaction input,
98
+ # and the first byte of the last element is 0x50, this last element is called annex, and
99
+ # has meanings independent of the script
100
+ ANNEX_TAG = 0x50
101
+
102
+ # Validation weight per passing signature (Tapscript only, see BIP 342).
103
+ VALIDATION_WEIGHT_PER_SIGOP_PASSED = 50
93
104
 
94
- # SIGHASH_FORK_ID for replay protection of the fork coin
95
- SIGHASH_FORK_ID = 0x40
105
+ # How much weight budget is added to the witness size (Tapscript only, see BIP 342).
106
+ VALIDATION_WEIGHT_OFFSET = 50
96
107
 
97
- # fork coin id.
98
- FORK_ID_CASH = 0
99
- FORK_ID_GOLD = 79
108
+ # Signature hash types/flags
109
+ SIGHASH_TYPE = { all: 0x01, none: 0x02, single: 0x3, anyonecanpay: 0x80 , default: 0}
100
110
 
101
111
  # Maximum number length in bytes
102
112
  DEFAULT_MAX_NUM_SIZE = 4
@@ -104,8 +114,6 @@ module Bitcoin
104
114
  # 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
105
115
  MAX_OP_RETURN_RELAY = 83
106
116
 
107
- SIG_VERSION = [:base, :witness_v0]
108
-
109
117
  # for script error
110
118
  SCRIPT_ERR_OK = 0
111
119
  SCRIPT_ERR_UNKNOWN_ERROR = 1
@@ -153,6 +161,10 @@ module Bitcoin
153
161
  # softfork safeness
154
162
  SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS = 60
155
163
  SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = 61
164
+ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = 62
165
+ SCRIPT_ERR_DISCOURAGE_UNKNOWN_ANNEX = 63
166
+ SCRIPT_ERR_DISCOURAGE_OP_SUCCESS = 64
167
+ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = 65
156
168
 
157
169
  # segregated witness
158
170
  SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH = 70
@@ -169,6 +181,15 @@ module Bitcoin
169
181
 
170
182
  SCRIPT_ERR_ERROR_COUNT = 80
171
183
 
184
+ # Taproot
185
+ SCRIPT_ERR_SCHNORR_SIG_SIZE = 90
186
+ SCRIPT_ERR_SCHNORR_SIG_HASHTYPE = 91
187
+ SCRIPT_ERR_SCHNORR_SIG = 92
188
+ SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE = 93
189
+ SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT = 94
190
+ SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG = 95
191
+ SCRIPT_ERR_TAPSCRIPT_MINIMALIF = 96
192
+
172
193
  ERRCODES_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [const_get(c), c.to_s] }.flatten]
173
194
  NAME_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [c.to_s, const_get(c)] }.flatten]
174
195
 
@@ -192,4 +213,17 @@ module Bitcoin
192
213
  BIP32_EXTKEY_WITH_VERSION_SIZE = 78
193
214
 
194
215
  HARDENED_THRESHOLD = 2147483648 # 2**31
216
+
217
+ # Signature hash sizes
218
+ WITNESS_V0_SCRIPTHASH_SIZE = 32
219
+ WITNESS_V0_KEYHASH_SIZE = 20
220
+ WITNESS_V1_TAPROOT_SIZE = 32
221
+
222
+ TAPROOT_LEAF_MASK = 0xfe
223
+ TAPROOT_LEAF_TAPSCRIPT = 0xc0
224
+ TAPROOT_CONTROL_BASE_SIZE = 33
225
+ TAPROOT_CONTROL_NODE_SIZE = 32
226
+ TAPROOT_CONTROL_MAX_NODE_COUNT = 128
227
+ TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT
228
+
195
229
  end
@@ -116,7 +116,7 @@ module Bitcoin
116
116
  end
117
117
  end
118
118
  key = key.is_a?(Bitcoin::Key) ? key : key.key
119
- raise ArgumentError, 'Invalid pubkey.' unless key.fully_valid_pubkey?
119
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless key.fully_valid_pubkey?
120
120
  key.pubkey
121
121
  end
122
122
 
@@ -0,0 +1,19 @@
1
+ module Bitcoin
2
+ module Errors
3
+
4
+ module Messages
5
+
6
+ INVALID_PUBLIC_KEY = 'Invalid public key.'
7
+ INVALID_BIP32_PRIV_PREFIX = 'Invalid BIP32 private key prefix. prefix must be 0x00.'
8
+ INVALID_BIP32_FINGERPRINT = 'Invalid parent fingerprint.'
9
+ INVALID_BIP32_ZERO_INDEX = 'Invalid index. Depth 0 must have 0 index.'
10
+ INVALID_BIP32_ZERO_DEPTH = 'Invalid depth. Master key must have 0 depth.'
11
+ INVALID_BIP32_VERSION = 'An unsupported version byte was specified.'
12
+
13
+ INVALID_PRIV_KEY = 'Private key is not in range [1..n-1].'
14
+ INVALID_CHECKSUM = 'Invalid checksum.'
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module Bitcoin
2
+ module Ext
3
+ autoload :JsonParser, 'bitcoin/ext/json_parser'
4
+ autoload :ArrayExt, 'bitcoin/ext/array_ext'
5
+ end
6
+ end
@@ -0,0 +1,22 @@
1
+ module Bitcoin
2
+ module Ext
3
+ module ArrayExt
4
+
5
+ refine Array do
6
+
7
+ # resize array content with +initial_value+.
8
+ # expect to behave like vec#resize in c++.
9
+ def resize!(new_size, initial_value = 0)
10
+ if size < new_size
11
+ (new_size - size).times{self.<< initial_value}
12
+ elsif size > new_size
13
+ (size - new_size).times{delete_at(-1)}
14
+ end
15
+ self
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ class ::ECDSA::Signature
2
+ # convert signature to der string.
3
+ def to_der
4
+ ECDSA::Format::SignatureDerString.encode(self)
5
+ end
6
+
7
+ def ==(other)
8
+ return false unless other.is_a?(ECDSA::Signature)
9
+ r == other.r && s == other.s
10
+ end
11
+ end
12
+
13
+ class ::ECDSA::Point
14
+ def to_hex(compression = true)
15
+ ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
16
+ end
17
+ end
18
+
19
+ module ::ECDSA::Format::PointOctetString
20
+
21
+ class << self
22
+ alias_method :base_decode, :decode
23
+ end
24
+
25
+ def self.decode(string, group, allow_hybrid: false)
26
+ string = string.dup.force_encoding('BINARY')
27
+ raise ECDSA::Format::DecodeError, 'Point octet string is empty.' if string.empty?
28
+ if [6, 7].include?(string[0].ord)
29
+ raise ECDSA::Format::DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord unless allow_hybrid
30
+ decode_uncompressed string, group if allow_hybrid
31
+ else
32
+ base_decode(string, group)
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,46 @@
1
+ require 'json/pure'
2
+
3
+ module Bitcoin
4
+ module Ext
5
+ # Extension of JSON::Pure::Parser.
6
+ # This class convert Float value to String value.
7
+ class JsonParser < JSON::Pure::Parser
8
+
9
+ def parse_value
10
+ case
11
+ when scan(FLOAT)
12
+ self[1].to_s
13
+ when scan(INTEGER)
14
+ Integer(self[1])
15
+ when scan(TRUE)
16
+ true
17
+ when scan(FALSE)
18
+ false
19
+ when scan(NULL)
20
+ nil
21
+ when !UNPARSED.equal?(string = parse_string)
22
+ string
23
+ when scan(ARRAY_OPEN)
24
+ @current_nesting += 1
25
+ ary = parse_array
26
+ @current_nesting -= 1
27
+ ary
28
+ when scan(OBJECT_OPEN)
29
+ @current_nesting += 1
30
+ obj = parse_object
31
+ @current_nesting -= 1
32
+ obj
33
+ when @allow_nan && scan(NAN)
34
+ NaN
35
+ when @allow_nan && scan(INFINITY)
36
+ Infinity
37
+ when @allow_nan && scan(MINUS_INFINITY)
38
+ MinusInfinity
39
+ else
40
+ UNPARSED
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -6,6 +6,11 @@ module Bitcoin
6
6
  # BIP32 Extended private key
7
7
  class ExtKey
8
8
 
9
+ include Bitcoin::HexConverter
10
+
11
+ MAX_DEPTH = 255
12
+ MASTER_FINGERPRINT = '00000000'
13
+
9
14
  attr_accessor :ver
10
15
  attr_accessor :depth
11
16
  attr_accessor :number
@@ -18,7 +23,7 @@ module Bitcoin
18
23
  def self.generate_master(seed)
19
24
  ext_key = ExtKey.new
20
25
  ext_key.depth = ext_key.number = 0
21
- ext_key.parent_fingerprint = '00000000'
26
+ ext_key.parent_fingerprint = MASTER_FINGERPRINT
22
27
  l = Bitcoin.hmac_sha512('Bitcoin seed', seed.htb)
23
28
  left = l[0..31].bth.to_i(16)
24
29
  raise 'invalid key' if left >= CURVE_ORDER || left == 0
@@ -47,9 +52,7 @@ module Bitcoin
47
52
 
48
53
  # Base58 encoded extended private key
49
54
  def to_base58
50
- h = to_payload.bth
51
- hex = h + Bitcoin.calc_checksum(h)
52
- Base58.encode(hex)
55
+ ExtPubkey.encode_base58(to_hex)
53
56
  end
54
57
 
55
58
  # get private key(hex)
@@ -94,6 +97,7 @@ module Bitcoin
94
97
  number += Bitcoin::HARDENED_THRESHOLD if harden
95
98
  new_key = ExtKey.new
96
99
  new_key.depth = depth + 1
100
+ raise IndexError, 'Depth over 255.' if new_key.depth > MAX_DEPTH
97
101
  new_key.number = number
98
102
  new_key.parent_fingerprint = fingerprint
99
103
  if number > (Bitcoin::HARDENED_THRESHOLD - 1)
@@ -140,19 +144,24 @@ module Bitcoin
140
144
  buf = StringIO.new(payload)
141
145
  ext_key = ExtKey.new
142
146
  ext_key.ver = buf.read(4).bth # version
143
- raise 'An unsupported version byte was specified.' unless ExtKey.support_version?(ext_key.ver)
144
- ext_key.depth = buf.read(1).unpack('C').first
147
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_VERSION unless ExtKey.support_version?(ext_key.ver)
148
+ ext_key.depth = buf.read(1).unpack1('C')
145
149
  ext_key.parent_fingerprint = buf.read(4).bth
146
- ext_key.number = buf.read(4).unpack('N').first
150
+ ext_key.number = buf.read(4).unpack1('N')
151
+ if ext_key.depth == 0
152
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_FINGERPRINT unless ext_key.parent_fingerprint == ExtKey::MASTER_FINGERPRINT
153
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_ZERO_INDEX if ext_key.number > 0
154
+ end
155
+ raise ArgumentError, Errors::Messages:: INVALID_BIP32_ZERO_DEPTH if ext_key.parent_fingerprint == ExtKey::MASTER_FINGERPRINT && ext_key.depth > 0
147
156
  ext_key.chain_code = buf.read(32)
148
- buf.read(1) # 0x00
157
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_PRIV_PREFIX unless buf.read(1).bth == '00' # 0x00
149
158
  ext_key.key = Bitcoin::Key.new(priv_key: buf.read(32).bth, key_type: Bitcoin::Key::TYPES[:compressed])
150
159
  ext_key
151
160
  end
152
161
 
153
162
  # import private key from Base58 private key address
154
- def self.from_base58(address)
155
- ExtKey.parse_from_payload(Base58.decode(address).htb)
163
+ def self.from_base58(base58)
164
+ ExtKey.parse_from_payload(ExtPubkey.validate_base58(base58))
156
165
  end
157
166
 
158
167
  # get version bytes from purpose' value.
@@ -191,6 +200,8 @@ module Bitcoin
191
200
  # BIP-32 Extended public key
192
201
  class ExtPubkey
193
202
 
203
+ include Bitcoin::HexConverter
204
+
194
205
  attr_accessor :ver
195
206
  attr_accessor :depth
196
207
  attr_accessor :number
@@ -242,9 +253,14 @@ module Bitcoin
242
253
 
243
254
  # Base58 encoded extended pubkey
244
255
  def to_base58
245
- h = to_payload.bth
246
- hex = h + Bitcoin.calc_checksum(h)
247
- Base58.encode(hex)
256
+ ExtPubkey.encode_base58(to_hex)
257
+ end
258
+
259
+ # Generate Base58 encoded key from BIP32 payload with hex format.
260
+ # @param [String] hex BIP32 payload with hex format.
261
+ # @return [String] Base58 encoded extended key.
262
+ def self.encode_base58(hex)
263
+ Base58.encode(hex + Bitcoin.calc_checksum(hex))
248
264
  end
249
265
 
250
266
  # whether hardened key.
@@ -256,6 +272,7 @@ module Bitcoin
256
272
  def derive(number)
257
273
  new_key = ExtPubkey.new
258
274
  new_key.depth = depth + 1
275
+ raise IndexError, 'Depth over 255.' if new_key.depth > Bitcoin::ExtKey::MAX_DEPTH
259
276
  new_key.number = number
260
277
  new_key.parent_fingerprint = fingerprint
261
278
  raise 'hardened key is not support' if number > (Bitcoin::HARDENED_THRESHOLD - 1)
@@ -263,9 +280,9 @@ module Bitcoin
263
280
  l = Bitcoin.hmac_sha512(chain_code, data)
264
281
  left = l[0..31].bth.to_i(16)
265
282
  raise 'invalid key' if left >= CURVE_ORDER
266
- p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
283
+ p1 = Bitcoin::Key.new(priv_key: left.to_s(16), key_type: Bitcoin::Key::TYPES[:uncompressed]).to_point
267
284
  p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
268
- new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
285
+ new_key.pubkey = (p1 + p2).to_hex
269
286
  new_key.chain_code = l[32..-1]
270
287
  new_key.ver = version
271
288
  new_key
@@ -298,19 +315,33 @@ module Bitcoin
298
315
  buf = StringIO.new(payload)
299
316
  ext_pubkey = ExtPubkey.new
300
317
  ext_pubkey.ver = buf.read(4).bth # version
301
- raise 'An unsupported version byte was specified.' unless ExtPubkey.support_version?(ext_pubkey.ver)
302
- ext_pubkey.depth = buf.read(1).unpack('C').first
318
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_VERSION unless ExtPubkey.support_version?(ext_pubkey.ver)
319
+ ext_pubkey.depth = buf.read(1).unpack1('C')
303
320
  ext_pubkey.parent_fingerprint = buf.read(4).bth
304
- ext_pubkey.number = buf.read(4).unpack('N').first
321
+ ext_pubkey.number = buf.read(4).unpack1('N')
322
+ if ext_pubkey.depth == 0
323
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_FINGERPRINT unless ext_pubkey.parent_fingerprint == ExtKey::MASTER_FINGERPRINT
324
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_ZERO_INDEX if ext_pubkey.number > 0
325
+ end
326
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_ZERO_DEPTH if ext_pubkey.parent_fingerprint == ExtKey::MASTER_FINGERPRINT && ext_pubkey.depth > 0
305
327
  ext_pubkey.chain_code = buf.read(32)
306
- ext_pubkey.pubkey = buf.read(33).bth
328
+ ext_pubkey.pubkey = Bitcoin::Key.new(pubkey: buf.read(33).bth).pubkey
307
329
  ext_pubkey
308
330
  end
309
331
 
310
332
 
311
333
  # import pub key from Base58 private key address
312
334
  def self.from_base58(address)
313
- ExtPubkey.parse_from_payload(Base58.decode(address).htb)
335
+ ExtPubkey.parse_from_payload(ExtPubkey.validate_base58(address))
336
+ end
337
+
338
+ # Validate address checksum and return payload.
339
+ # @param [String] BIP32 Base58 address
340
+ # @return [String] BIP32 payload with binary format
341
+ def self.validate_base58(address)
342
+ raw = Base58.decode(address)
343
+ raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(raw[0...-8]) == raw[-8..-1]
344
+ raw[0...-8].htb
314
345
  end
315
346
 
316
347
  # get version bytes from purpose' value.