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
@@ -26,7 +26,7 @@ module Bitcoin
26
26
  found_sep = true
27
27
  break
28
28
  end
29
- key_type = buf.read(1).unpack('C').first
29
+ key_type = buf.read(1).unpack1('C')
30
30
  key = buf.read(key_len - 1)
31
31
  value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
32
32
  case key_type
@@ -2,6 +2,7 @@ module Bitcoin
2
2
  module PSBT
3
3
 
4
4
  class GlobalXpub
5
+ include Bitcoin::HexConverter
5
6
 
6
7
  attr_reader :xpub # Bitcoin::ExtPubkey
7
8
  attr_reader :info # Bitcoin::PSBT::KeyOriginInfo
@@ -16,7 +17,7 @@ module Bitcoin
16
17
  end
17
18
 
18
19
  def to_h
19
- {xpub: xpub.to_payload.bth}.merge(info.to_h)
20
+ {xpub: xpub.to_hex}.merge(info.to_h)
20
21
  end
21
22
 
22
23
  def to_s
@@ -25,6 +26,8 @@ module Bitcoin
25
26
  end
26
27
 
27
28
  class Tx
29
+ include Bitcoin::HexConverter
30
+
28
31
  attr_accessor :tx
29
32
  attr_accessor :xpubs
30
33
  attr_reader :inputs
@@ -52,7 +55,7 @@ module Bitcoin
52
55
  # @return [Bitcoin::PartiallySignedTx]
53
56
  def self.parse_from_payload(payload)
54
57
  buf = StringIO.new(payload)
55
- raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack('N').first == PSBT_MAGIC_BYTES
58
+ raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack1('N') == PSBT_MAGIC_BYTES
56
59
  raise ArgumentError, 'Invalid PSBT separator.' unless buf.read(1).bth.to_i(16) == 0xff
57
60
  partial_tx = self.new
58
61
  found_sep = false
@@ -63,7 +66,7 @@ module Bitcoin
63
66
  found_sep = true
64
67
  break
65
68
  end
66
- key_type = buf.read(1).unpack('C').first
69
+ key_type = buf.read(1).unpack1('C')
67
70
  key = buf.read(key_len - 1)
68
71
  value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
69
72
 
@@ -71,20 +74,20 @@ module Bitcoin
71
74
  when PSBT_GLOBAL_TYPES[:unsigned_tx]
72
75
  raise ArgumentError, 'Invalid global transaction typed key.' unless key_len == 1
73
76
  raise ArgumentError, 'Duplicate Key, unsigned tx already provided.' if partial_tx.tx
74
- partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true)
77
+ partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true, strict: true)
75
78
  partial_tx.tx.in.each do |tx_in|
76
79
  raise ArgumentError, 'Unsigned tx does not have empty scriptSigs and scriptWitnesses.' if !tx_in.script_sig.empty? || !tx_in.script_witness.empty?
77
80
  end
78
81
  when PSBT_GLOBAL_TYPES[:xpub]
79
82
  raise ArgumentError, 'Size of key was not the expected size for the type global xpub.' unless key.size == Bitcoin::BIP32_EXTKEY_WITH_VERSION_SIZE
80
83
  xpub = Bitcoin::ExtPubkey.parse_from_payload(key)
81
- raise ArgumentError, 'Invalid pubkey.' unless xpub.key.fully_valid_pubkey?
84
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless xpub.key.fully_valid_pubkey?
82
85
  raise ArgumentError, 'Duplicate key, global xpub already provided' if partial_tx.xpubs.any?{|x|x.xpub == xpub}
83
86
  info = Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value)
84
87
  raise ArgumentError, "global xpub's depth and the number of indexes not matched." unless xpub.depth == info.key_paths.size
85
88
  partial_tx.xpubs << Bitcoin::PSBT::GlobalXpub.new(xpub, info)
86
89
  when PSBT_GLOBAL_TYPES[:ver]
87
- partial_tx.version_number = value.unpack('V').first
90
+ partial_tx.version_number = value.unpack1('V')
88
91
  raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
89
92
  else
90
93
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[key]
@@ -117,10 +120,6 @@ module Bitcoin
117
120
 
118
121
  raise ArgumentError, 'Outputs provided does not match the number of outputs in transaction.' unless partial_tx.outputs.size == partial_tx.tx.out.size
119
122
 
120
- partial_tx.inputs.each do |input|
121
- raise ArgumentError, 'PSBT is not sane.' unless input.sane?
122
- end
123
-
124
123
  partial_tx
125
124
  end
126
125
 
@@ -149,11 +148,9 @@ module Bitcoin
149
148
  payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:unsigned_tx], value: tx.to_payload)
150
149
  payload << xpubs.map(&:to_payload).join
151
150
  payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:ver], value: [version_number].pack('V')) if version_number
152
-
153
151
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
154
152
 
155
153
  payload << PSBT_SEPARATOR.itb
156
-
157
154
  payload << inputs.map(&:to_payload).join
158
155
  payload << outputs.map(&:to_payload).join
159
156
  payload
@@ -177,14 +174,12 @@ module Bitcoin
177
174
  utxo = prev_tx.out[tx_in.out_point.index]
178
175
  raise ArgumentError, 'redeem script does not match utxo.' if redeem_script && !utxo.script_pubkey.include?(redeem_script.to_hash160)
179
176
  raise ArgumentError, 'witness script does not match redeem script.' if redeem_script && witness_script && !redeem_script.include?(witness_script.to_sha256)
180
- if utxo.script_pubkey.witness_program? || (redeem_script && redeem_script.witness_program?)
181
- inputs[i].witness_utxo = utxo
182
- else
183
- inputs[i].non_witness_utxo = prev_tx
184
- end
177
+ inputs[i].witness_utxo = utxo if utxo.script_pubkey.witness_program? || redeem_script&.witness_program?
178
+ inputs[i].non_witness_utxo = prev_tx
185
179
  inputs[i].redeem_script = redeem_script if redeem_script
186
180
  inputs[i].witness_script = witness_script if witness_script
187
181
  inputs[i].hd_key_paths = hd_key_paths.map(&:pubkey).zip(hd_key_paths).to_h
182
+ break
188
183
  end
189
184
  end
190
185
  end
@@ -1,4 +1,4 @@
1
- require 'rest-client'
1
+ require 'net/http'
2
2
 
3
3
  module Bitcoin
4
4
  module RPC
@@ -53,20 +53,30 @@ module Bitcoin
53
53
  :params => params,
54
54
  :id => 'jsonrpc'
55
55
  }
56
- post(server_url, @config[:timeout], @config[:open_timeout], data.to_json, content_type: :json) do |respdata, request, result|
57
- raise result.message if !result.kind_of?(Net::HTTPSuccess) && respdata.empty?
58
- response = JSON.parse(respdata.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') })
59
- raise response['error'] if response['error']
60
- response['result']
61
- end
56
+ uri = URI.parse(server_url)
57
+ http = Net::HTTP.new(uri.hostname, uri.port)
58
+ http.use_ssl = uri.scheme === "https"
59
+ request = Net::HTTP::Post.new(uri.path.empty? ? '/' : uri.path)
60
+ request.basic_auth(uri.user, uri.password)
61
+ request.content_type = 'application/json'
62
+ request.body = data.to_json
63
+ response = http.request(request)
64
+ body = response.body
65
+ response = Bitcoin::Ext::JsonParser.new(body.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }).parse
66
+ raise response['error'].to_json if response['error']
67
+ response['result']
62
68
  end
63
-
64
- def post(url, timeout, open_timeout, payload, headers={}, &block)
65
- RestClient::Request.execute(method: :post, url: url, timeout: timeout,
66
- open_timeout: open_timeout, payload: payload, headers: headers, &block)
69
+
70
+ # Call CLI command on Ruby-like method names.
71
+ # e.g. generate_to_address, send_to_address, get_wallet_info
72
+ def method_missing(name, *args)
73
+ if name.to_s.include?('_')
74
+ send(name.to_s.gsub('_', '').to_sym, args)
75
+ else
76
+ super
77
+ end
67
78
  end
68
79
 
69
80
  end
70
-
71
81
  end
72
82
  end
@@ -42,14 +42,14 @@ module Bitcoin
42
42
  nextblockhash: node.chain.next_hash(block_hash).rhex
43
43
  }
44
44
  else
45
- entry.header.to_payload.bth
45
+ entry.header.to_hex
46
46
  end
47
47
  end
48
48
 
49
49
  # Returns connected peer information.
50
50
  def getpeerinfo
51
51
  node.pool.peers.map do |peer|
52
- local_addr = "#{peer.remote_version.remote_addr.ip}:18333"
52
+ local_addr = "#{peer.remote_version.remote_addr.addr_string}:18333"
53
53
  {
54
54
  id: peer.id,
55
55
  addr: "#{peer.host}:#{peer.port}",
@@ -75,7 +75,7 @@ module Bitcoin
75
75
 
76
76
  # broadcast transaction
77
77
  def sendrawtransaction(hex_tx)
78
- tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb)
78
+ tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true)
79
79
  # TODO check wether tx is valid
80
80
  node.broadcast(tx)
81
81
  tx.txid
@@ -84,7 +84,7 @@ module Bitcoin
84
84
  # decode tx data.
85
85
  def decoderawtransaction(hex_tx)
86
86
  begin
87
- Bitcoin::Tx.parse_from_payload(hex_tx.htb).to_h
87
+ Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true ).to_h
88
88
  rescue Exception
89
89
  raise ArgumentError.new('TX decode failed')
90
90
  end
@@ -96,7 +96,7 @@ module Bitcoin
96
96
  script = Bitcoin::Script.parse_from_payload(hex_script.htb)
97
97
  h = script.to_h
98
98
  h.delete(:hex)
99
- h[:p2sh] = script.to_p2sh.addresses.first unless script.p2sh?
99
+ h[:p2sh] = script.to_p2sh.to_addr unless script.p2sh?
100
100
  h
101
101
  rescue Exception
102
102
  raise ArgumentError.new('Script decode failed')
@@ -6,6 +6,7 @@ module Bitcoin
6
6
  # bitcoin script
7
7
  class Script
8
8
  include Bitcoin::Opcodes
9
+ include Bitcoin::HexConverter
9
10
 
10
11
  attr_accessor :chunks
11
12
 
@@ -20,7 +21,7 @@ module Bitcoin
20
21
 
21
22
  # generate P2WPKH script
22
23
  def self.to_p2wpkh(pubkey_hash)
23
- new << WITNESS_VERSION << pubkey_hash
24
+ new << WITNESS_VERSION_V0 << pubkey_hash
24
25
  end
25
26
 
26
27
  # generate m of n multisig p2sh script
@@ -51,7 +52,7 @@ module Bitcoin
51
52
  end
52
53
 
53
54
  # generate m of n multisig script
54
- # @param [String] m the number of signatures required for multisig
55
+ # @param [Integer] m the number of signatures required for multisig
55
56
  # @param [Array] pubkeys array of public keys that compose multisig
56
57
  # @return [Script] multisig script.
57
58
  def self.to_multisig_script(m, pubkeys, sort: false)
@@ -63,7 +64,7 @@ module Bitcoin
63
64
  # @param [Script] redeem_script target redeem script
64
65
  # @param [Script] p2wsh script
65
66
  def self.to_p2wsh(redeem_script)
66
- new << WITNESS_VERSION << redeem_script.to_sha256
67
+ new << WITNESS_VERSION_V0 << redeem_script.to_sha256
67
68
  end
68
69
 
69
70
  # generate script from string.
@@ -86,17 +87,21 @@ module Bitcoin
86
87
  def self.parse_from_addr(addr)
87
88
  begin
88
89
  segwit_addr = Bech32::SegwitAddr.new(addr)
89
- raise 'Invalid hrp.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
90
+ raise ArgumentError, 'Invalid address.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
90
91
  Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
91
92
  rescue Exception => e
93
+ begin
92
94
  hex, addr_version = Bitcoin.decode_base58_address(addr)
95
+ rescue
96
+ raise ArgumentError, 'Invalid address.'
97
+ end
93
98
  case addr_version
94
99
  when Bitcoin.chain_params.address_version
95
100
  Bitcoin::Script.to_p2pkh(hex)
96
101
  when Bitcoin.chain_params.p2sh_version
97
102
  Bitcoin::Script.to_p2sh(hex)
98
103
  else
99
- throw e
104
+ raise ArgumentError, 'Invalid address.'
100
105
  end
101
106
  end
102
107
  end
@@ -109,25 +114,28 @@ module Bitcoin
109
114
  if opcode.pushdata?
110
115
  pushcode = opcode.ord
111
116
  packed_size = nil
117
+ if buf.eof?
118
+ s.chunks << opcode
119
+ return s
120
+ end
112
121
  len = case pushcode
113
122
  when OP_PUSHDATA1
114
123
  packed_size = buf.read(1)
115
- packed_size.unpack('C').first
124
+ packed_size.unpack1('C')
116
125
  when OP_PUSHDATA2
117
126
  packed_size = buf.read(2)
118
- packed_size.unpack('v').first
127
+ packed_size.unpack1('v')
119
128
  when OP_PUSHDATA4
120
129
  packed_size = buf.read(4)
121
- packed_size.unpack('V').first
130
+ packed_size.unpack1('V')
122
131
  else
123
- pushcode if pushcode < OP_PUSHDATA1
132
+ pushcode < OP_PUSHDATA1 ? pushcode : 0
124
133
  end
125
- if len
126
- s.chunks << [len].pack('C') if buf.eof?
127
- unless buf.eof?
128
- chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
129
- s.chunks << chunk
130
- end
134
+ if buf.eof?
135
+ s.chunks << [len].pack('C')
136
+ else buf.eof?
137
+ chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
138
+ s.chunks << chunk
131
139
  end
132
140
  else
133
141
  if Opcodes.defined?(opcode.ord)
@@ -140,15 +148,21 @@ module Bitcoin
140
148
  s
141
149
  end
142
150
 
143
- def to_payload
144
- chunks.join
151
+ # Output script payload.
152
+ # @param [Boolean] length_prefixed Flag whether the length of the payload should be given at the beginning.(default: false)
153
+ # @return [String] payload
154
+ def to_payload(length_prefixed = false)
155
+ p = chunks.join
156
+ length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
145
157
  end
146
158
 
147
159
  def empty?
148
160
  chunks.size == 0
149
161
  end
150
162
 
163
+ # @deprecated
151
164
  def addresses
165
+ puts "WARNING: Bitcoin::Script#addresses is deprecated. Use Bitcoin::Script#to_addr instead."
152
166
  return [p2pkh_addr] if p2pkh?
153
167
  return [p2sh_addr] if p2sh?
154
168
  return [bech32_addr] if witness_program?
@@ -156,29 +170,51 @@ module Bitcoin
156
170
  []
157
171
  end
158
172
 
173
+ # convert to address
174
+ # @return [String] if script type is p2pkh or p2sh or witness program, return address, otherwise nil.
175
+ def to_addr
176
+ return p2pkh_addr if p2pkh?
177
+ return p2sh_addr if p2sh?
178
+ return bech32_addr if witness_program?
179
+ nil
180
+ end
181
+
159
182
  # check whether standard script.
160
183
  def standard?
161
- p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
184
+ p2pkh? | p2sh? | p2wpkh? | p2wsh? | p2tr? | multisig? | standard_op_return?
162
185
  end
163
186
 
164
- # whether this script is a P2PKH format script.
187
+ # Check whether this script is a P2PKH format script.
188
+ # @return [Boolean] if P2PKH return true, otherwise false
165
189
  def p2pkh?
166
190
  return false unless chunks.size == 5
167
191
  [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
168
192
  (chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
169
193
  end
170
194
 
171
- # whether this script is a P2WPKH format script.
195
+ # Check whether this script is a P2WPKH format script.
196
+ # @return [Boolean] if P2WPKH return true, otherwise false
172
197
  def p2wpkh?
173
198
  return false unless chunks.size == 2
174
- chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 21
199
+ chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 21
175
200
  end
176
201
 
202
+ # Check whether this script is a P2WPSH format script.
203
+ # @return [Boolean] if P2WPSH return true, otherwise false
177
204
  def p2wsh?
178
205
  return false unless chunks.size == 2
179
- chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 33
206
+ chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 33
207
+ end
208
+
209
+ # Check whether this script is a P2TR format script.
210
+ # @return [Boolean] if P2TR return true, otherwise false
211
+ def p2tr?
212
+ return false unless chunks.size == 2
213
+ chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].bytesize == 33
180
214
  end
181
215
 
216
+ # Check whether this script is a P2SH format script.
217
+ # @return [Boolean] if P2SH return true, otherwise false
182
218
  def p2sh?
183
219
  return false unless chunks.size == 3
184
220
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
@@ -230,7 +266,7 @@ module Bitcoin
230
266
  return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
231
267
  return false unless chunks[1].pushdata?
232
268
 
233
- if size == (chunks[1][0].unpack('C').first + 2)
269
+ if size == (chunks[1][0].unpack1('C') + 2)
234
270
  program_size = chunks[1].pushed_data.bytesize
235
271
  return program_size >= 2 && program_size <= 40
236
272
  end
@@ -324,16 +360,21 @@ module Bitcoin
324
360
  when Integer
325
361
  opcode_to_name(c)
326
362
  when String
363
+ return c if c.empty?
327
364
  if c.pushdata?
328
365
  v = Opcodes.opcode_to_small_int(c.ord)
329
366
  if v
330
367
  v
331
368
  else
332
369
  data = c.pushed_data
333
- if data.bytesize <= 4
334
- Script.decode_number(data.bth) # for scriptnum
370
+ if data
371
+ if data.bytesize <= 4
372
+ Script.decode_number(data.bth) # for scriptnum
373
+ else
374
+ data.bth
375
+ end
335
376
  else
336
- data.bth
377
+ c.bth
337
378
  end
338
379
  end
339
380
  else
@@ -351,7 +392,7 @@ module Bitcoin
351
392
 
352
393
  # generate hash160 hash for payload
353
394
  def to_hash160
354
- Bitcoin.hash160(to_payload.bth)
395
+ Bitcoin.hash160(to_hex)
355
396
  end
356
397
 
357
398
  # script size
@@ -380,8 +421,8 @@ module Bitcoin
380
421
  hex = '0' + hex unless (hex.length % 2).zero?
381
422
  v = hex.htb.reverse # change endian
382
423
 
383
- v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack('C').first & 0x80) == 0
384
- v[-1] = [v[-1].unpack('C').first | 0x80].pack('C') if negative
424
+ v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack1('C') & 0x80) == 0
425
+ v[-1] = [v[-1].unpack1('C') | 0x80].pack('C') if negative
385
426
  v.bth
386
427
  end
387
428
 
@@ -389,7 +430,7 @@ module Bitcoin
389
430
  def self.decode_number(s)
390
431
  v = s.htb.reverse
391
432
  return 0 if v.length.zero?
392
- mbs = v[0].unpack('C').first
433
+ mbs = v[0].unpack1('C')
393
434
  v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
394
435
  result = v.bth.to_i(16)
395
436
  result = -result unless (mbs & 0x80) == 0
@@ -474,7 +515,7 @@ module Bitcoin
474
515
  end
475
516
 
476
517
  def ==(other)
477
- return false unless other
518
+ return false unless other.is_a?(Script)
478
519
  chunks == other.chunks
479
520
  end
480
521
 
@@ -488,7 +529,7 @@ module Bitcoin
488
529
  end
489
530
 
490
531
  def to_h
491
- h = {asm: to_s, hex: to_payload.bth, type: type}
532
+ h = {asm: to_s, hex: to_hex, type: type}
492
533
  addrs = addresses
493
534
  unless addrs.empty?
494
535
  h[:req_sigs] = multisig? ? Bitcoin::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
@@ -504,12 +545,6 @@ module Bitcoin
504
545
  (size > 0 && op_return?) || size > Bitcoin::MAX_SCRIPT_SIZE
505
546
  end
506
547
 
507
- # convert payload to hex data.
508
- # @return [String] script with hex format.
509
- def to_hex
510
- to_payload.bth
511
- end
512
-
513
548
  private
514
549
 
515
550
  # generate p2pkh address. if script dose not p2pkh, return nil.
@@ -542,10 +577,32 @@ module Bitcoin
542
577
  def bech32_addr
543
578
  segwit_addr = Bech32::SegwitAddr.new
544
579
  segwit_addr.hrp = Bitcoin.chain_params.bech32_hrp
545
- segwit_addr.script_pubkey = to_payload.bth
580
+ segwit_addr.script_pubkey = to_hex
546
581
  segwit_addr.addr
547
582
  end
548
583
 
584
+ # Check whether push data length is valid.
585
+ # @return [Boolean] if valid return true, otherwise false.
586
+ def valid_pushdata_length?(chunk)
587
+ buf = StringIO.new(chunk)
588
+ opcode = buf.read(1).ord
589
+ offset = 1
590
+ len = case opcode
591
+ when OP_PUSHDATA1
592
+ offset += 1
593
+ buf.read(1).unpack1('C')
594
+ when OP_PUSHDATA2
595
+ offset += 2
596
+ buf.read(2).unpack1('v')
597
+ when OP_PUSHDATA4
598
+ offset += 4
599
+ buf.read(4).unpack1('V')
600
+ else
601
+ opcode
602
+ end
603
+ chunk.bytesize == len + offset
604
+ end
605
+
549
606
  end
550
607
 
551
608
  end