bitcoinrb 0.3.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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.