bitcoinrb 0.3.1 → 0.7.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 (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