bitcoinrb 0.3.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +6 -3
  4. data/README.md +17 -6
  5. data/bitcoinrb.gemspec +9 -8
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +35 -19
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_filter.rb +14 -0
  10. data/lib/bitcoin/block_header.rb +2 -0
  11. data/lib/bitcoin/chain_params.rb +9 -8
  12. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  13. data/lib/bitcoin/chainparams/signet.yml +39 -0
  14. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  15. data/lib/bitcoin/constants.rb +45 -12
  16. data/lib/bitcoin/descriptor.rb +1 -1
  17. data/lib/bitcoin/errors.rb +19 -0
  18. data/lib/bitcoin/ext.rb +5 -0
  19. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  20. data/lib/bitcoin/ext/json_parser.rb +46 -0
  21. data/lib/bitcoin/ext_key.rb +50 -19
  22. data/lib/bitcoin/key.rb +46 -29
  23. data/lib/bitcoin/key_path.rb +12 -5
  24. data/lib/bitcoin/message.rb +79 -0
  25. data/lib/bitcoin/message/addr_v2.rb +34 -0
  26. data/lib/bitcoin/message/base.rb +17 -0
  27. data/lib/bitcoin/message/cf_parser.rb +16 -0
  28. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  29. data/lib/bitcoin/message/cfheaders.rb +40 -0
  30. data/lib/bitcoin/message/cfilter.rb +35 -0
  31. data/lib/bitcoin/message/fee_filter.rb +1 -1
  32. data/lib/bitcoin/message/filter_load.rb +3 -3
  33. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  34. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  35. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  36. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  37. data/lib/bitcoin/message/inventory.rb +1 -1
  38. data/lib/bitcoin/message/merkle_block.rb +1 -1
  39. data/lib/bitcoin/message/network_addr.rb +141 -18
  40. data/lib/bitcoin/message/ping.rb +1 -1
  41. data/lib/bitcoin/message/pong.rb +1 -1
  42. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  43. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  44. data/lib/bitcoin/message/version.rb +7 -0
  45. data/lib/bitcoin/mnemonic.rb +7 -7
  46. data/lib/bitcoin/network/peer.rb +9 -4
  47. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  48. data/lib/bitcoin/node/cli.rb +14 -10
  49. data/lib/bitcoin/node/configuration.rb +3 -1
  50. data/lib/bitcoin/node/spv.rb +9 -1
  51. data/lib/bitcoin/opcodes.rb +14 -1
  52. data/lib/bitcoin/out_point.rb +7 -0
  53. data/lib/bitcoin/payment_code.rb +92 -0
  54. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  55. data/lib/bitcoin/psbt/input.rb +8 -17
  56. data/lib/bitcoin/psbt/output.rb +1 -1
  57. data/lib/bitcoin/psbt/tx.rb +11 -16
  58. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  59. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  60. data/lib/bitcoin/script/script.rb +68 -28
  61. data/lib/bitcoin/script/script_error.rb +27 -1
  62. data/lib/bitcoin/script/script_interpreter.rb +164 -67
  63. data/lib/bitcoin/script/tx_checker.rb +64 -14
  64. data/lib/bitcoin/secp256k1.rb +1 -0
  65. data/lib/bitcoin/secp256k1/native.rb +138 -25
  66. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  67. data/lib/bitcoin/secp256k1/ruby.rb +82 -54
  68. data/lib/bitcoin/sighash_generator.rb +156 -0
  69. data/lib/bitcoin/store.rb +2 -1
  70. data/lib/bitcoin/store/chain_entry.rb +1 -0
  71. data/lib/bitcoin/store/db/level_db.rb +2 -2
  72. data/lib/bitcoin/store/utxo_db.rb +226 -0
  73. data/lib/bitcoin/tx.rb +17 -88
  74. data/lib/bitcoin/tx_in.rb +4 -5
  75. data/lib/bitcoin/tx_out.rb +2 -3
  76. data/lib/bitcoin/util.rb +34 -6
  77. data/lib/bitcoin/version.rb +1 -1
  78. data/lib/bitcoin/wallet.rb +1 -0
  79. data/lib/bitcoin/wallet/account.rb +2 -1
  80. data/lib/bitcoin/wallet/base.rb +3 -3
  81. data/lib/bitcoin/wallet/db.rb +1 -1
  82. data/lib/bitcoin/wallet/master_key.rb +1 -0
  83. data/lib/bitcoin/wallet/utxo.rb +37 -0
  84. metadata +66 -32
@@ -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
 
@@ -88,15 +93,19 @@ module Bitcoin
88
93
  # Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
89
94
  LOCKTIME_THRESHOLD = 500000000
90
95
 
91
- # Signature hash types/flags
92
- SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
96
+ # Tag for input annex. If there are at least two witness elements for a transaction input,
97
+ # and the first byte of the last element is 0x50, this last element is called annex, and
98
+ # has meanings independent of the script
99
+ ANNEX_TAG = 0x50
100
+
101
+ # Validation weight per passing signature (Tapscript only, see BIP 342).
102
+ VALIDATION_WEIGHT_PER_SIGOP_PASSED = 50
93
103
 
94
- # SIGHASH_FORK_ID for replay protection of the fork coin
95
- SIGHASH_FORK_ID = 0x40
104
+ # How much weight budget is added to the witness size (Tapscript only, see BIP 342).
105
+ VALIDATION_WEIGHT_OFFSET = 50
96
106
 
97
- # fork coin id.
98
- FORK_ID_CASH = 0
99
- FORK_ID_GOLD = 79
107
+ # Signature hash types/flags
108
+ SIGHASH_TYPE = { all: 0x01, none: 0x02, single: 0x3, anyonecanpay: 0x80 , default: 0}
100
109
 
101
110
  # Maximum number length in bytes
102
111
  DEFAULT_MAX_NUM_SIZE = 4
@@ -104,8 +113,6 @@ module Bitcoin
104
113
  # 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
105
114
  MAX_OP_RETURN_RELAY = 83
106
115
 
107
- SIG_VERSION = [:base, :witness_v0]
108
-
109
116
  # for script error
110
117
  SCRIPT_ERR_OK = 0
111
118
  SCRIPT_ERR_UNKNOWN_ERROR = 1
@@ -146,13 +153,17 @@ module Bitcoin
146
153
  SCRIPT_ERR_SIG_HIGH_S = 54
147
154
  SCRIPT_ERR_SIG_NULLDUMMY = 55
148
155
  SCRIPT_ERR_PUBKEYTYPE = 56
149
- SCRIPT_ERR_CLEANSTACK = 56
150
- SCRIPT_ERR_MINIMALIF = 57
151
- SCRIPT_ERR_SIG_NULLFAIL = 58
156
+ SCRIPT_ERR_CLEANSTACK = 57
157
+ SCRIPT_ERR_MINIMALIF = 58
158
+ SCRIPT_ERR_SIG_NULLFAIL = 59
152
159
 
153
160
  # softfork safeness
154
161
  SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS = 60
155
162
  SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = 61
163
+ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = 62
164
+ SCRIPT_ERR_DISCOURAGE_UNKNOWN_ANNEX = 63
165
+ SCRIPT_ERR_DISCOURAGE_OP_SUCCESS = 64
166
+ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = 65
156
167
 
157
168
  # segregated witness
158
169
  SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH = 70
@@ -169,6 +180,15 @@ module Bitcoin
169
180
 
170
181
  SCRIPT_ERR_ERROR_COUNT = 80
171
182
 
183
+ # Taproot
184
+ SCRIPT_ERR_SCHNORR_SIG_SIZE = 90
185
+ SCRIPT_ERR_SCHNORR_SIG_HASHTYPE = 91
186
+ SCRIPT_ERR_SCHNORR_SIG = 92
187
+ SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE = 93
188
+ SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT = 94
189
+ SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG = 95
190
+ SCRIPT_ERR_TAPSCRIPT_MINIMALIF = 96
191
+
172
192
  ERRCODES_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [const_get(c), c.to_s] }.flatten]
173
193
  NAME_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [c.to_s, const_get(c)] }.flatten]
174
194
 
@@ -192,4 +212,17 @@ module Bitcoin
192
212
  BIP32_EXTKEY_WITH_VERSION_SIZE = 78
193
213
 
194
214
  HARDENED_THRESHOLD = 2147483648 # 2**31
215
+
216
+ # Signature hash sizes
217
+ WITNESS_V0_SCRIPTHASH_SIZE = 32
218
+ WITNESS_V0_KEYHASH_SIZE = 20
219
+ WITNESS_V1_TAPROOT_SIZE = 32
220
+
221
+ TAPROOT_LEAF_MASK = 0xfe
222
+ TAPROOT_LEAF_TAPSCRIPT = 0xc0
223
+ TAPROOT_CONTROL_BASE_SIZE = 33
224
+ TAPROOT_CONTROL_NODE_SIZE = 32
225
+ TAPROOT_CONTROL_MAX_NODE_COUNT = 128
226
+ TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT
227
+
195
228
  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,5 @@
1
+ module Bitcoin
2
+ module Ext
3
+ autoload :JsonParser, 'bitcoin/ext/json_parser'
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ class ::ECDSA::Signature
2
+ # convert signature to der string.
3
+ def to_der
4
+ ECDSA::Format::SignatureDerString.encode(self)
5
+ end
6
+ end
7
+
8
+ class ::ECDSA::Point
9
+ def to_hex(compression = true)
10
+ ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
11
+ end
12
+ end
13
+
14
+ module ::ECDSA::Format::PointOctetString
15
+
16
+ class << self
17
+ alias_method :base_decode, :decode
18
+ end
19
+
20
+ def self.decode(string, group, allow_hybrid: false)
21
+ string = string.dup.force_encoding('BINARY')
22
+ raise ECDSA::Format::DecodeError, 'Point octet string is empty.' if string.empty?
23
+ if [6, 7].include?(string[0].ord)
24
+ raise ECDSA::Format::DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord unless allow_hybrid
25
+ decode_uncompressed string, group if allow_hybrid
26
+ else
27
+ base_decode(string, group)
28
+ end
29
+ end
30
+
31
+ 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)
@@ -265,7 +282,7 @@ module Bitcoin
265
282
  raise 'invalid key' if left >= CURVE_ORDER
266
283
  p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
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.
data/lib/bitcoin/key.rb CHANGED
@@ -28,7 +28,7 @@ module Bitcoin
28
28
  # @param [Integer] key_type a key type which determine address type.
29
29
  # @param [Boolean] compressed [Deprecated] whether public key is compressed.
30
30
  # @return [Bitcoin::Key] a key object.
31
- def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
31
+ def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
32
32
  puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
33
33
  if key_type
34
34
  @key_type = key_type
@@ -39,13 +39,14 @@ module Bitcoin
39
39
  @secp256k1_module = Bitcoin.secp_impl
40
40
  @priv_key = priv_key
41
41
  if @priv_key
42
- raise ArgumentError, 'private key is not on curve' unless validate_private_key_range(@priv_key)
42
+ raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
43
43
  end
44
44
  if pubkey
45
45
  @pubkey = pubkey
46
46
  else
47
47
  @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
48
48
  end
49
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
49
50
  end
50
51
 
51
52
  # generate key pair
@@ -63,9 +64,9 @@ module Bitcoin
63
64
  data = hex[2...-8].htb
64
65
  checksum = hex[-8..-1]
65
66
  raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
66
- raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(version + data.bth) == checksum
67
+ raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(version + data.bth) == checksum
67
68
  key_len = data.bytesize
68
- if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack('C').first == 1
69
+ if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack1('C') == 1
69
70
  key_type = TYPES[:compressed]
70
71
  data = data[0..-2]
71
72
  elsif key_len == 32
@@ -88,30 +89,46 @@ module Bitcoin
88
89
  # sign +data+ with private key
89
90
  # @param [String] data a data to be signed with binary format
90
91
  # @param [Boolean] low_r flag to apply low-R.
91
- # @param [String] extra_entropy the extra entropy for rfc6979.
92
+ # @param [String] extra_entropy the extra entropy with binary format for rfc6979.
93
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
92
94
  # @return [String] signature data with binary format
93
- def sign(data, low_r = true, extra_entropy = nil)
94
- sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
95
- if low_r && !sig_has_low_r?(sig)
96
- counter = 1
97
- until sig_has_low_r?(sig)
98
- extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
99
- sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
100
- counter += 1
95
+ def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
96
+ case algo
97
+ when :ecdsa
98
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
99
+ if low_r && !sig_has_low_r?(sig)
100
+ counter = 1
101
+ until sig_has_low_r?(sig)
102
+ extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
103
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
104
+ counter += 1
105
+ end
101
106
  end
107
+ sig
108
+ when :schnorr
109
+ secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :schnorr)
110
+ else
111
+ raise ArgumentError "Unsupported algo specified: #{algo}"
102
112
  end
103
- sig
104
113
  end
105
114
 
106
115
  # verify signature using public key
107
116
  # @param [String] sig signature data with binary format
108
- # @param [String] origin original message
117
+ # @param [String] data original message
118
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
109
119
  # @return [Boolean] verify result
110
- def verify(sig, origin)
120
+ def verify(sig, data, algo: :ecdsa)
111
121
  return false unless valid_pubkey?
112
122
  begin
113
- sig = ecdsa_signature_parse_der_lax(sig)
114
- secp256k1_module.verify_sig(origin, sig, pubkey)
123
+ case algo
124
+ when :ecdsa
125
+ sig = ecdsa_signature_parse_der_lax(sig)
126
+ secp256k1_module.verify_sig(data, sig, pubkey)
127
+ when :schnorr
128
+ secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr)
129
+ else
130
+ false
131
+ end
115
132
  rescue Exception
116
133
  false
117
134
  end
@@ -125,19 +142,19 @@ module Bitcoin
125
142
  # get pay to pubkey hash address
126
143
  # @deprecated
127
144
  def to_p2pkh
128
- Bitcoin::Script.to_p2pkh(hash160).addresses.first
145
+ Bitcoin::Script.to_p2pkh(hash160).to_addr
129
146
  end
130
147
 
131
148
  # get pay to witness pubkey hash address
132
149
  # @deprecated
133
150
  def to_p2wpkh
134
- Bitcoin::Script.to_p2wpkh(hash160).addresses.first
151
+ Bitcoin::Script.to_p2wpkh(hash160).to_addr
135
152
  end
136
153
 
137
154
  # get p2wpkh address nested in p2sh.
138
155
  # @deprecated
139
156
  def to_nested_p2wpkh
140
- Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.addresses.first
157
+ Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
141
158
  end
142
159
 
143
160
  def compressed?
@@ -152,6 +169,12 @@ module Bitcoin
152
169
  ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
153
170
  end
154
171
 
172
+ # get xonly public key (32 bytes).
173
+ # @return [String] xonly public key with hex format
174
+ def xonly_pubkey
175
+ pubkey[2..65]
176
+ end
177
+
155
178
  # check +pubkey+ (hex) is compress or uncompress pubkey.
156
179
  def self.compress_or_uncompress_pubkey?(pubkey)
157
180
  p = pubkey.htb
@@ -225,14 +248,8 @@ module Bitcoin
225
248
  end
226
249
 
227
250
  # fully validate whether this is a valid public key (more expensive than IsValid())
228
- def fully_valid_pubkey?
229
- return false unless valid_pubkey?
230
- begin
231
- point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
232
- ECDSA::Group::Secp256k1.valid_public_key?(point)
233
- rescue ECDSA::Format::DecodeError
234
- false
235
- end
251
+ def fully_valid_pubkey?(allow_hybrid = false)
252
+ valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
236
253
  end
237
254
 
238
255
  private