bitcoinrb 0.3.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +17 -6
  6. data/bitcoinrb.gemspec +9 -8
  7. data/exe/bitcoinrbd +5 -0
  8. data/lib/bitcoin.rb +37 -19
  9. data/lib/bitcoin/bip85_entropy.rb +111 -0
  10. data/lib/bitcoin/block_filter.rb +14 -0
  11. data/lib/bitcoin/block_header.rb +2 -0
  12. data/lib/bitcoin/chain_params.rb +9 -8
  13. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  14. data/lib/bitcoin/chainparams/signet.yml +39 -0
  15. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  16. data/lib/bitcoin/constants.rb +44 -10
  17. data/lib/bitcoin/descriptor.rb +1 -1
  18. data/lib/bitcoin/errors.rb +19 -0
  19. data/lib/bitcoin/ext.rb +6 -0
  20. data/lib/bitcoin/ext/array_ext.rb +22 -0
  21. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  22. data/lib/bitcoin/ext/json_parser.rb +46 -0
  23. data/lib/bitcoin/ext_key.rb +51 -20
  24. data/lib/bitcoin/key.rb +89 -30
  25. data/lib/bitcoin/key_path.rb +12 -5
  26. data/lib/bitcoin/message.rb +79 -0
  27. data/lib/bitcoin/message/addr_v2.rb +34 -0
  28. data/lib/bitcoin/message/base.rb +17 -0
  29. data/lib/bitcoin/message/cf_parser.rb +16 -0
  30. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  31. data/lib/bitcoin/message/cfheaders.rb +40 -0
  32. data/lib/bitcoin/message/cfilter.rb +35 -0
  33. data/lib/bitcoin/message/fee_filter.rb +1 -1
  34. data/lib/bitcoin/message/filter_load.rb +3 -3
  35. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  36. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  37. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  38. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  39. data/lib/bitcoin/message/inventory.rb +1 -1
  40. data/lib/bitcoin/message/merkle_block.rb +1 -1
  41. data/lib/bitcoin/message/network_addr.rb +141 -18
  42. data/lib/bitcoin/message/ping.rb +1 -1
  43. data/lib/bitcoin/message/pong.rb +1 -1
  44. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  45. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  46. data/lib/bitcoin/message/tx.rb +1 -1
  47. data/lib/bitcoin/message/version.rb +7 -0
  48. data/lib/bitcoin/message_sign.rb +47 -0
  49. data/lib/bitcoin/mnemonic.rb +7 -7
  50. data/lib/bitcoin/network/peer.rb +9 -4
  51. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  52. data/lib/bitcoin/node/cli.rb +14 -10
  53. data/lib/bitcoin/node/configuration.rb +3 -1
  54. data/lib/bitcoin/node/spv.rb +9 -1
  55. data/lib/bitcoin/opcodes.rb +14 -1
  56. data/lib/bitcoin/out_point.rb +2 -0
  57. data/lib/bitcoin/payment_code.rb +92 -0
  58. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  59. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  60. data/lib/bitcoin/psbt/input.rb +9 -18
  61. data/lib/bitcoin/psbt/output.rb +1 -1
  62. data/lib/bitcoin/psbt/tx.rb +12 -17
  63. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  64. data/lib/bitcoin/rpc/request_handler.rb +5 -5
  65. data/lib/bitcoin/script/script.rb +96 -39
  66. data/lib/bitcoin/script/script_error.rb +27 -1
  67. data/lib/bitcoin/script/script_interpreter.rb +166 -66
  68. data/lib/bitcoin/script/tx_checker.rb +62 -14
  69. data/lib/bitcoin/secp256k1.rb +1 -0
  70. data/lib/bitcoin/secp256k1/native.rb +184 -17
  71. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  72. data/lib/bitcoin/secp256k1/ruby.rb +112 -56
  73. data/lib/bitcoin/sighash_generator.rb +156 -0
  74. data/lib/bitcoin/store.rb +1 -0
  75. data/lib/bitcoin/store/chain_entry.rb +1 -0
  76. data/lib/bitcoin/store/utxo_db.rb +226 -0
  77. data/lib/bitcoin/taproot.rb +9 -0
  78. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  79. data/lib/bitcoin/taproot/simple_builder.rb +139 -0
  80. data/lib/bitcoin/tx.rb +34 -104
  81. data/lib/bitcoin/tx_in.rb +4 -5
  82. data/lib/bitcoin/tx_out.rb +2 -3
  83. data/lib/bitcoin/util.rb +22 -6
  84. data/lib/bitcoin/version.rb +1 -1
  85. data/lib/bitcoin/wallet.rb +1 -0
  86. data/lib/bitcoin/wallet/account.rb +2 -1
  87. data/lib/bitcoin/wallet/base.rb +2 -2
  88. data/lib/bitcoin/wallet/master_key.rb +1 -0
  89. data/lib/bitcoin/wallet/utxo.rb +37 -0
  90. metadata +86 -32
  91. data/.travis.yml +0 -11
@@ -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