tapyrus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +12 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +100 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/tapyrusrb-cli +5 -0
  15. data/exe/tapyrusrbd +41 -0
  16. data/lib/openassets/marker_output.rb +20 -0
  17. data/lib/openassets/payload.rb +54 -0
  18. data/lib/openassets/util.rb +28 -0
  19. data/lib/openassets.rb +9 -0
  20. data/lib/tapyrus/base58.rb +38 -0
  21. data/lib/tapyrus/block.rb +77 -0
  22. data/lib/tapyrus/block_header.rb +88 -0
  23. data/lib/tapyrus/bloom_filter.rb +78 -0
  24. data/lib/tapyrus/chain_params.rb +90 -0
  25. data/lib/tapyrus/chainparams/mainnet.yml +41 -0
  26. data/lib/tapyrus/chainparams/regtest.yml +38 -0
  27. data/lib/tapyrus/chainparams/testnet.yml +41 -0
  28. data/lib/tapyrus/constants.rb +195 -0
  29. data/lib/tapyrus/descriptor.rb +147 -0
  30. data/lib/tapyrus/ext_key.rb +337 -0
  31. data/lib/tapyrus/key.rb +296 -0
  32. data/lib/tapyrus/key_path.rb +26 -0
  33. data/lib/tapyrus/logger.rb +42 -0
  34. data/lib/tapyrus/merkle_tree.rb +149 -0
  35. data/lib/tapyrus/message/addr.rb +35 -0
  36. data/lib/tapyrus/message/base.rb +28 -0
  37. data/lib/tapyrus/message/block.rb +46 -0
  38. data/lib/tapyrus/message/block_transaction_request.rb +45 -0
  39. data/lib/tapyrus/message/block_transactions.rb +31 -0
  40. data/lib/tapyrus/message/block_txn.rb +27 -0
  41. data/lib/tapyrus/message/cmpct_block.rb +42 -0
  42. data/lib/tapyrus/message/error.rb +10 -0
  43. data/lib/tapyrus/message/fee_filter.rb +27 -0
  44. data/lib/tapyrus/message/filter_add.rb +28 -0
  45. data/lib/tapyrus/message/filter_clear.rb +17 -0
  46. data/lib/tapyrus/message/filter_load.rb +39 -0
  47. data/lib/tapyrus/message/get_addr.rb +17 -0
  48. data/lib/tapyrus/message/get_block_txn.rb +27 -0
  49. data/lib/tapyrus/message/get_blocks.rb +29 -0
  50. data/lib/tapyrus/message/get_data.rb +21 -0
  51. data/lib/tapyrus/message/get_headers.rb +28 -0
  52. data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
  53. data/lib/tapyrus/message/headers.rb +35 -0
  54. data/lib/tapyrus/message/headers_parser.rb +24 -0
  55. data/lib/tapyrus/message/inv.rb +21 -0
  56. data/lib/tapyrus/message/inventories_parser.rb +23 -0
  57. data/lib/tapyrus/message/inventory.rb +51 -0
  58. data/lib/tapyrus/message/mem_pool.rb +17 -0
  59. data/lib/tapyrus/message/merkle_block.rb +42 -0
  60. data/lib/tapyrus/message/network_addr.rb +63 -0
  61. data/lib/tapyrus/message/not_found.rb +21 -0
  62. data/lib/tapyrus/message/ping.rb +30 -0
  63. data/lib/tapyrus/message/pong.rb +26 -0
  64. data/lib/tapyrus/message/prefilled_tx.rb +29 -0
  65. data/lib/tapyrus/message/reject.rb +46 -0
  66. data/lib/tapyrus/message/send_cmpct.rb +43 -0
  67. data/lib/tapyrus/message/send_headers.rb +16 -0
  68. data/lib/tapyrus/message/tx.rb +30 -0
  69. data/lib/tapyrus/message/ver_ack.rb +17 -0
  70. data/lib/tapyrus/message/version.rb +69 -0
  71. data/lib/tapyrus/message.rb +70 -0
  72. data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
  73. data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
  74. data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
  75. data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
  76. data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
  77. data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
  78. data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
  79. data/lib/tapyrus/mnemonic.rb +77 -0
  80. data/lib/tapyrus/network/connection.rb +73 -0
  81. data/lib/tapyrus/network/message_handler.rb +241 -0
  82. data/lib/tapyrus/network/peer.rb +223 -0
  83. data/lib/tapyrus/network/peer_discovery.rb +42 -0
  84. data/lib/tapyrus/network/pool.rb +135 -0
  85. data/lib/tapyrus/network.rb +13 -0
  86. data/lib/tapyrus/node/cli.rb +112 -0
  87. data/lib/tapyrus/node/configuration.rb +38 -0
  88. data/lib/tapyrus/node/spv.rb +79 -0
  89. data/lib/tapyrus/node.rb +7 -0
  90. data/lib/tapyrus/opcodes.rb +178 -0
  91. data/lib/tapyrus/out_point.rb +44 -0
  92. data/lib/tapyrus/rpc/http_server.rb +65 -0
  93. data/lib/tapyrus/rpc/request_handler.rb +150 -0
  94. data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
  95. data/lib/tapyrus/rpc.rb +7 -0
  96. data/lib/tapyrus/script/multisig.rb +92 -0
  97. data/lib/tapyrus/script/script.rb +551 -0
  98. data/lib/tapyrus/script/script_error.rb +111 -0
  99. data/lib/tapyrus/script/script_interpreter.rb +668 -0
  100. data/lib/tapyrus/script/tx_checker.rb +81 -0
  101. data/lib/tapyrus/script_witness.rb +38 -0
  102. data/lib/tapyrus/secp256k1/native.rb +174 -0
  103. data/lib/tapyrus/secp256k1/ruby.rb +123 -0
  104. data/lib/tapyrus/secp256k1.rb +12 -0
  105. data/lib/tapyrus/slip39/share.rb +122 -0
  106. data/lib/tapyrus/slip39/sss.rb +245 -0
  107. data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
  108. data/lib/tapyrus/slip39.rb +93 -0
  109. data/lib/tapyrus/store/chain_entry.rb +67 -0
  110. data/lib/tapyrus/store/db/level_db.rb +98 -0
  111. data/lib/tapyrus/store/db.rb +9 -0
  112. data/lib/tapyrus/store/spv_chain.rb +101 -0
  113. data/lib/tapyrus/store.rb +9 -0
  114. data/lib/tapyrus/tx.rb +347 -0
  115. data/lib/tapyrus/tx_in.rb +89 -0
  116. data/lib/tapyrus/tx_out.rb +74 -0
  117. data/lib/tapyrus/util.rb +133 -0
  118. data/lib/tapyrus/validation.rb +115 -0
  119. data/lib/tapyrus/version.rb +3 -0
  120. data/lib/tapyrus/wallet/account.rb +151 -0
  121. data/lib/tapyrus/wallet/base.rb +162 -0
  122. data/lib/tapyrus/wallet/db.rb +81 -0
  123. data/lib/tapyrus/wallet/master_key.rb +110 -0
  124. data/lib/tapyrus/wallet.rb +8 -0
  125. data/lib/tapyrus.rb +219 -0
  126. data/tapyrusrb.conf.sample +0 -0
  127. data/tapyrusrb.gemspec +47 -0
  128. metadata +451 -0
@@ -0,0 +1,551 @@
1
+ # Porting part of the code from bitcoin-ruby. see the license.
2
+ # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
+
4
+ module Tapyrus
5
+
6
+ # tapyrus script
7
+ class Script
8
+ include Tapyrus::Opcodes
9
+
10
+ attr_accessor :chunks
11
+
12
+ def initialize
13
+ @chunks = []
14
+ end
15
+
16
+ # generate P2PKH script
17
+ def self.to_p2pkh(pubkey_hash)
18
+ new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
19
+ end
20
+
21
+ # generate P2WPKH script
22
+ def self.to_p2wpkh(pubkey_hash)
23
+ new << WITNESS_VERSION << pubkey_hash
24
+ end
25
+
26
+ # generate m of n multisig p2sh script
27
+ # @param [String] m the number of signatures required for multisig
28
+ # @param [Array] pubkeys array of public keys that compose multisig
29
+ # @return [Script, Script] first element is p2sh script, second one is redeem script.
30
+ def self.to_p2sh_multisig_script(m, pubkeys)
31
+ redeem_script = to_multisig_script(m, pubkeys)
32
+ [redeem_script.to_p2sh, redeem_script]
33
+ end
34
+
35
+ # generate p2sh script.
36
+ # @param [String] script_hash script hash for P2SH
37
+ # @return [Script] P2SH script
38
+ def self.to_p2sh(script_hash)
39
+ Script.new << OP_HASH160 << script_hash << OP_EQUAL
40
+ end
41
+
42
+ # generate p2sh script with this as a redeem script
43
+ # @return [Script] P2SH script
44
+ def to_p2sh
45
+ Script.to_p2sh(to_hash160)
46
+ end
47
+
48
+ def get_multisig_pubkeys
49
+ num = Tapyrus::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
50
+ (1..num).map{ |i| chunks[i].pushed_data }
51
+ end
52
+
53
+ # generate m of n multisig script
54
+ # @param [String] m the number of signatures required for multisig
55
+ # @param [Array] pubkeys array of public keys that compose multisig
56
+ # @return [Script] multisig script.
57
+ def self.to_multisig_script(m, pubkeys, sort: false)
58
+ pubkeys = pubkeys.sort if sort
59
+ new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
60
+ end
61
+
62
+ # generate p2wsh script for +redeem_script+
63
+ # @param [Script] redeem_script target redeem script
64
+ # @param [Script] p2wsh script
65
+ def self.to_p2wsh(redeem_script)
66
+ new << WITNESS_VERSION << redeem_script.to_sha256
67
+ end
68
+
69
+ # generate script from string.
70
+ def self.from_string(string)
71
+ script = new
72
+ string.split(' ').each do |v|
73
+ opcode = Opcodes.name_to_opcode(v)
74
+ if opcode
75
+ script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
76
+ else
77
+ script << (v =~ /^[0-9]+$/ ? v.to_i : v)
78
+ end
79
+ end
80
+ script
81
+ end
82
+
83
+ # generate script from addr.
84
+ # @param [String] addr address.
85
+ # @return [Tapyrus::Script] parsed script.
86
+ def self.parse_from_addr(addr)
87
+ begin
88
+ segwit_addr = Bech32::SegwitAddr.new(addr)
89
+ raise 'Invalid hrp.' unless Tapyrus.chain_params.bech32_hrp == segwit_addr.hrp
90
+ Tapyrus::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
91
+ rescue Exception => e
92
+ hex, addr_version = Tapyrus.decode_base58_address(addr)
93
+ case addr_version
94
+ when Tapyrus.chain_params.address_version
95
+ Tapyrus::Script.to_p2pkh(hex)
96
+ when Tapyrus.chain_params.p2sh_version
97
+ Tapyrus::Script.to_p2sh(hex)
98
+ else
99
+ throw e
100
+ end
101
+ end
102
+ end
103
+
104
+ def self.parse_from_payload(payload)
105
+ s = new
106
+ buf = StringIO.new(payload)
107
+ until buf.eof?
108
+ opcode = buf.read(1)
109
+ if opcode.pushdata?
110
+ pushcode = opcode.ord
111
+ packed_size = nil
112
+ len = case pushcode
113
+ when OP_PUSHDATA1
114
+ packed_size = buf.read(1)
115
+ packed_size.unpack('C').first
116
+ when OP_PUSHDATA2
117
+ packed_size = buf.read(2)
118
+ packed_size.unpack('v').first
119
+ when OP_PUSHDATA4
120
+ packed_size = buf.read(4)
121
+ packed_size.unpack('V').first
122
+ else
123
+ pushcode if pushcode < OP_PUSHDATA1
124
+ 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
131
+ end
132
+ else
133
+ if Opcodes.defined?(opcode.ord)
134
+ s << opcode.ord
135
+ else
136
+ s.chunks << (opcode + buf.read) # If opcode is invalid, put all remaining data in last chunk.
137
+ end
138
+ end
139
+ end
140
+ s
141
+ end
142
+
143
+ def to_payload
144
+ chunks.join
145
+ end
146
+
147
+ def empty?
148
+ chunks.size == 0
149
+ end
150
+
151
+ def addresses
152
+ return [p2pkh_addr] if p2pkh?
153
+ return [p2sh_addr] if p2sh?
154
+ return [bech32_addr] if witness_program?
155
+ return get_multisig_pubkeys.map{|pubkey| Tapyrus::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
156
+ []
157
+ end
158
+
159
+ # check whether standard script.
160
+ def standard?
161
+ p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
162
+ end
163
+
164
+ # whether this script is a P2PKH format script.
165
+ def p2pkh?
166
+ return false unless chunks.size == 5
167
+ [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
168
+ (chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
169
+ end
170
+
171
+ # whether this script is a P2WPKH format script.
172
+ def p2wpkh?
173
+ return false unless chunks.size == 2
174
+ chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 21
175
+ end
176
+
177
+ def p2wsh?
178
+ return false unless chunks.size == 2
179
+ chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 33
180
+ end
181
+
182
+ def p2sh?
183
+ return false unless chunks.size == 3
184
+ OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
185
+ end
186
+
187
+ def multisig?
188
+ return false if chunks.size < 4 || chunks.last.ord != OP_CHECKMULTISIG
189
+ pubkey_count = Opcodes.opcode_to_small_int(chunks[-2].opcode)
190
+ sig_count = Opcodes.opcode_to_small_int(chunks[0].opcode)
191
+ return false unless pubkey_count || sig_count
192
+ sig_count <= pubkey_count
193
+ end
194
+
195
+ def op_return?
196
+ chunks.size >= 1 && chunks[0].ord == OP_RETURN
197
+ end
198
+
199
+ def standard_op_return?
200
+ op_return? && size <= MAX_OP_RETURN_RELAY &&
201
+ (chunks.size == 1 || chunks[1].opcode <= OP_16)
202
+ end
203
+
204
+ def op_return_data
205
+ return nil unless op_return?
206
+ return nil if chunks.size == 1
207
+ chunks[1].pushed_data
208
+ end
209
+
210
+ # whether data push only script which dose not include other opcode
211
+ def push_only?
212
+ chunks.each do |c|
213
+ return false if !c.opcode.nil? && c.opcode > OP_16
214
+ end
215
+ true
216
+ end
217
+
218
+ # get public keys in the stack.
219
+ # @return[Array[String]] an array of the pubkeys with hex format.
220
+ def get_pubkeys
221
+ chunks.select{|c|c.pushdata? && [33, 65].include?(c.pushed_data.bytesize) && [2, 3, 4, 6, 7].include?(c.pushed_data[0].bth.to_i(16))}.map{|c|c.pushed_data.bth}
222
+ end
223
+
224
+ # A witness program is any valid Script that consists of a 1-byte push opcode followed by a data push between 2 and 40 bytes.
225
+ def witness_program?
226
+ return false if size < 4 || size > 42 || chunks.size < 2
227
+
228
+ opcode = chunks[0].opcode
229
+
230
+ return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
231
+ return false unless chunks[1].pushdata?
232
+
233
+ if size == (chunks[1][0].unpack('C').first + 2)
234
+ program_size = chunks[1].pushed_data.bytesize
235
+ return program_size >= 2 && program_size <= 40
236
+ end
237
+
238
+ false
239
+ end
240
+
241
+ # get witness commitment
242
+ def witness_commitment
243
+ return nil if !op_return? || op_return_data.bytesize < 36
244
+ buf = StringIO.new(op_return_data)
245
+ return nil unless buf.read(4).bth == WITNESS_COMMITMENT_HEADER
246
+ buf.read(32).bth
247
+ end
248
+
249
+ # If this script is witness program, return its script code,
250
+ # otherwise returns the self payload. ScriptInterpreter does not use this.
251
+ def to_script_code(skip_separator_index = 0)
252
+ payload = to_payload
253
+ if p2wpkh?
254
+ payload = Script.to_p2pkh(chunks[1].pushed_data.bth).to_payload
255
+ elsif skip_separator_index > 0
256
+ payload = subscript_codeseparator(skip_separator_index)
257
+ end
258
+ Tapyrus.pack_var_string(payload)
259
+ end
260
+
261
+ # get witness version and witness program
262
+ def witness_data
263
+ version = opcode_to_small_int(chunks[0].opcode)
264
+ program = chunks[1].pushed_data
265
+ [version, program]
266
+ end
267
+
268
+ # append object to payload
269
+ def <<(obj)
270
+ if obj.is_a?(Integer)
271
+ push_int(obj)
272
+ elsif obj.is_a?(String)
273
+ append_data(obj)
274
+ elsif obj.is_a?(Array)
275
+ obj.each { |o| self.<< o}
276
+ self
277
+ end
278
+ end
279
+
280
+ # push integer to stack.
281
+ def push_int(n)
282
+ begin
283
+ append_opcode(n)
284
+ rescue ArgumentError
285
+ append_data(Script.encode_number(n))
286
+ end
287
+ self
288
+ end
289
+
290
+ # append opcode to payload
291
+ # @param [Integer] opcode append opcode which defined by Tapyrus::Opcodes
292
+ # @return [Script] return self
293
+ def append_opcode(opcode)
294
+ opcode = Opcodes.small_int_to_opcode(opcode) if -1 <= opcode && opcode <= 16
295
+ raise ArgumentError, "specified invalid opcode #{opcode}." unless Opcodes.defined?(opcode)
296
+ chunks << opcode.chr
297
+ self
298
+ end
299
+
300
+ # append data to payload with pushdata opcode
301
+ # @param [String] data append data. this data is not binary
302
+ # @return [Script] return self
303
+ def append_data(data)
304
+ data = Encoding::ASCII_8BIT == data.encoding ? data : data.htb
305
+ chunks << Tapyrus::Script.pack_pushdata(data)
306
+ self
307
+ end
308
+
309
+ # Check the item is in the chunk of the script.
310
+ def include?(item)
311
+ chunk_item = if item.is_a?(Integer)
312
+ item.chr
313
+ elsif item.is_a?(String)
314
+ data = Encoding::ASCII_8BIT == item.encoding ? item : item.htb
315
+ Tapyrus::Script.pack_pushdata(data)
316
+ end
317
+ return false unless chunk_item
318
+ chunks.include?(chunk_item)
319
+ end
320
+
321
+ def to_s
322
+ chunks.map { |c|
323
+ case c
324
+ when Integer
325
+ opcode_to_name(c)
326
+ when String
327
+ if c.pushdata?
328
+ v = Opcodes.opcode_to_small_int(c.ord)
329
+ if v
330
+ v
331
+ else
332
+ data = c.pushed_data
333
+ if data.bytesize <= 4
334
+ Script.decode_number(data.bth) # for scriptnum
335
+ else
336
+ data.bth
337
+ end
338
+ end
339
+ else
340
+ opcode = Opcodes.opcode_to_name(c.ord)
341
+ opcode ? opcode : 'OP_UNKNOWN [error]'
342
+ end
343
+ end
344
+ }.join(' ')
345
+ end
346
+
347
+ # generate sha-256 hash for payload
348
+ def to_sha256
349
+ Tapyrus.sha256(to_payload).bth
350
+ end
351
+
352
+ # generate hash160 hash for payload
353
+ def to_hash160
354
+ Tapyrus.hash160(to_payload.bth)
355
+ end
356
+
357
+ # script size
358
+ def size
359
+ to_payload.bytesize
360
+ end
361
+
362
+ # execute script interpreter using this script for development.
363
+ def run
364
+ Tapyrus::ScriptInterpreter.eval(Tapyrus::Script.new, self.dup)
365
+ end
366
+
367
+ # encode int value to script number hex.
368
+ # The stacks hold byte vectors.
369
+ # When used as numbers, byte vectors are interpreted as little-endian variable-length integers
370
+ # with the most significant bit determining the sign of the integer.
371
+ # Thus 0x81 represents -1. 0x80 is another representation of zero (so called negative 0).
372
+ # Positive 0 is represented by a null-length vector.
373
+ # Byte vectors are interpreted as Booleans where False is represented by any representation of zero,
374
+ # and True is represented by any representation of non-zero.
375
+ def self.encode_number(i)
376
+ return '' if i == 0
377
+ negative = i < 0
378
+
379
+ hex = i.abs.to_even_length_hex
380
+ hex = '0' + hex unless (hex.length % 2).zero?
381
+ v = hex.htb.reverse # change endian
382
+
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
385
+ v.bth
386
+ end
387
+
388
+ # decode script number hex to int value
389
+ def self.decode_number(s)
390
+ v = s.htb.reverse
391
+ return 0 if v.length.zero?
392
+ mbs = v[0].unpack('C').first
393
+ v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
394
+ result = v.bth.to_i(16)
395
+ result = -result unless (mbs & 0x80) == 0
396
+ result
397
+ end
398
+
399
+ # binary +data+ convert pushdata which contains data length and append PUSHDATA opcode if necessary.
400
+ def self.pack_pushdata(data)
401
+ size = data.bytesize
402
+ header = if size < OP_PUSHDATA1
403
+ [size].pack('C')
404
+ elsif size < 0xff
405
+ [OP_PUSHDATA1, size].pack('CC')
406
+ elsif size < 0xffff
407
+ [OP_PUSHDATA2, size].pack('Cv')
408
+ elsif size < 0xffffffff
409
+ [OP_PUSHDATA4, size].pack('CV')
410
+ else
411
+ raise ArgumentError, 'data size is too big.'
412
+ end
413
+ header + data
414
+ end
415
+
416
+ # subscript this script to the specified range.
417
+ def subscript(*args)
418
+ s = self.class.new
419
+ s.chunks = chunks[*args]
420
+ s
421
+ end
422
+
423
+ # removes chunks matching subscript byte-for-byte and returns as a new object.
424
+ def find_and_delete(subscript)
425
+ raise ArgumentError, 'subscript must be Tapyrus::Script' unless subscript.is_a?(Script)
426
+ return self if subscript.chunks.empty?
427
+ buf = []
428
+ i = 0
429
+ result = Script.new
430
+ chunks.each do |chunk|
431
+ sub_chunk = subscript.chunks[i]
432
+ if chunk.start_with?(sub_chunk)
433
+ if chunk == sub_chunk
434
+ buf << chunk
435
+ i += 1
436
+ (i = 0; buf.clear) if i == subscript.chunks.size # matched the whole subscript
437
+ else # matched the part of head
438
+ i = 0
439
+ tmp = chunk.dup
440
+ tmp.slice!(sub_chunk)
441
+ result.chunks << tmp
442
+ end
443
+ else
444
+ result.chunks << buf.join unless buf.empty?
445
+ if buf.first == chunk
446
+ i = 1
447
+ buf = [chunk]
448
+ else
449
+ i = 0
450
+ result.chunks << chunk
451
+ end
452
+ end
453
+ end
454
+ result
455
+ end
456
+
457
+ # remove all occurences of opcode. Typically it's OP_CODESEPARATOR.
458
+ def delete_opcode(opcode)
459
+ @chunks = chunks.select{|chunk| chunk.ord != opcode}
460
+ self
461
+ end
462
+
463
+ # Returns a script that deleted the script before the index specified by separator_index.
464
+ def subscript_codeseparator(separator_index)
465
+ buf = []
466
+ process_separator_index = 0
467
+ chunks.each{|chunk|
468
+ buf << chunk if process_separator_index == separator_index
469
+ if chunk.ord == OP_CODESEPARATOR && process_separator_index < separator_index
470
+ process_separator_index += 1
471
+ end
472
+ }
473
+ buf.join
474
+ end
475
+
476
+ def ==(other)
477
+ return false unless other
478
+ chunks == other.chunks
479
+ end
480
+
481
+ def type
482
+ return 'pubkeyhash' if p2pkh?
483
+ return 'scripthash' if p2sh?
484
+ return 'multisig' if multisig?
485
+ return 'witness_v0_keyhash' if p2wpkh?
486
+ return 'witness_v0_scripthash' if p2wsh?
487
+ 'nonstandard'
488
+ end
489
+
490
+ def to_h
491
+ h = {asm: to_s, hex: to_payload.bth, type: type}
492
+ addrs = addresses
493
+ unless addrs.empty?
494
+ h[:req_sigs] = multisig? ? Tapyrus::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
495
+ h[:addresses] = addrs
496
+ end
497
+ h
498
+ end
499
+
500
+ # Returns whether the script is guaranteed to fail at execution, regardless of the initial stack.
501
+ # This allows outputs to be pruned instantly when entering the UTXO set.
502
+ # @return [Boolean] whether the script is guaranteed to fail at execution
503
+ def unspendable?
504
+ (size > 0 && op_return?) || size > Tapyrus::MAX_SCRIPT_SIZE
505
+ end
506
+
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
+ private
514
+
515
+ # generate p2pkh address. if script dose not p2pkh, return nil.
516
+ def p2pkh_addr
517
+ return nil unless p2pkh?
518
+ hash160 = chunks[2].pushed_data.bth
519
+ return nil unless hash160.htb.bytesize == 20
520
+ Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.address_version)
521
+ end
522
+
523
+ # generate p2wpkh address. if script dose not p2wpkh, return nil.
524
+ def p2wpkh_addr
525
+ p2wpkh? ? bech32_addr : nil
526
+ end
527
+
528
+ # generate p2sh address. if script dose not p2sh, return nil.
529
+ def p2sh_addr
530
+ return nil unless p2sh?
531
+ hash160 = chunks[1].pushed_data.bth
532
+ return nil unless hash160.htb.bytesize == 20
533
+ Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.p2sh_version)
534
+ end
535
+
536
+ # generate p2wsh address. if script dose not p2wsh, return nil.
537
+ def p2wsh_addr
538
+ p2wsh? ? bech32_addr : nil
539
+ end
540
+
541
+ # return bech32 address for payload
542
+ def bech32_addr
543
+ segwit_addr = Bech32::SegwitAddr.new
544
+ segwit_addr.hrp = Tapyrus.chain_params.bech32_hrp
545
+ segwit_addr.script_pubkey = to_payload.bth
546
+ segwit_addr.addr
547
+ end
548
+
549
+ end
550
+
551
+ end
@@ -0,0 +1,111 @@
1
+ module Tapyrus
2
+
3
+ # tapyrus script error
4
+ class ScriptError < Exception
5
+
6
+ attr_accessor :code
7
+ attr_accessor :extra_msg
8
+
9
+ def initialize(code, extra_msg = '')
10
+ raise 'invalid error code.' unless ERRCODES_MAP[code]
11
+ @code = code
12
+ @extra_msg = extra_msg
13
+ end
14
+
15
+ def to_s
16
+ case code
17
+ when SCRIPT_ERR_OK
18
+ 'No error'
19
+ when SCRIPT_ERR_EVAL_FALSE
20
+ 'Script evaluated without error but finished with a false/empty top stack element'
21
+ when SCRIPT_ERR_VERIFY
22
+ 'Script failed an OP_VERIFY operation'
23
+ when SCRIPT_ERR_EQUALVERIFY
24
+ 'Script failed an OP_EQUALVERIFY operation'
25
+ when SCRIPT_ERR_CHECKMULTISIGVERIFY
26
+ 'Script failed an OP_CHECKMULTISIGVERIFY operation'
27
+ when SCRIPT_ERR_CHECKSIGVERIFY
28
+ 'Script failed an OP_CHECKSIGVERIFY operation'
29
+ when SCRIPT_ERR_NUMEQUALVERIFY
30
+ 'Script failed an OP_NUMEQUALVERIFY operation'
31
+ when SCRIPT_ERR_SCRIPT_SIZE
32
+ 'Script is too big'
33
+ when SCRIPT_ERR_PUSH_SIZE
34
+ 'Push value size limit exceeded'
35
+ when SCRIPT_ERR_OP_COUNT
36
+ 'Operation limit exceeded'
37
+ when SCRIPT_ERR_STACK_SIZE
38
+ 'Stack size limit exceeded'
39
+ when SCRIPT_ERR_SIG_COUNT
40
+ 'Signature count negative or greater than pubkey count'
41
+ when SCRIPT_ERR_PUBKEY_COUNT
42
+ 'Pubkey count negative or limit exceeded'
43
+ when SCRIPT_ERR_BAD_OPCODE
44
+ 'Opcode missing or not understood'
45
+ when SCRIPT_ERR_DISABLED_OPCODE
46
+ 'Attempted to use a disabled opcode'
47
+ when SCRIPT_ERR_INVALID_STACK_OPERATION
48
+ 'Operation not valid with the current stack size'
49
+ when SCRIPT_ERR_INVALID_ALTSTACK_OPERATION
50
+ 'Operation not valid with the current altstack size'
51
+ when SCRIPT_ERR_OP_RETURN
52
+ 'OP_was encountered'
53
+ when SCRIPT_ERR_UNBALANCED_CONDITIONAL
54
+ 'Invalid OP_IF construction'
55
+ when SCRIPT_ERR_NEGATIVE_LOCKTIME
56
+ 'Negative locktime'
57
+ when SCRIPT_ERR_UNSATISFIED_LOCKTIME
58
+ 'Locktime requirement not satisfied'
59
+ when SCRIPT_ERR_SIG_HASHTYPE
60
+ 'Signature hash type missing or not understood'
61
+ when SCRIPT_ERR_SIG_DER
62
+ 'Non-canonical DER signature'
63
+ when SCRIPT_ERR_MINIMALDATA
64
+ 'Data push larger than necessary'
65
+ when SCRIPT_ERR_SIG_PUSHONLY
66
+ 'Only non-push operators allowed in signatures'
67
+ when SCRIPT_ERR_SIG_HIGH_S
68
+ 'Non-canonical signature S value is unnecessarily high'
69
+ when SCRIPT_ERR_SIG_NULLDUMMY
70
+ 'Dummy CHECKMULTISIG argument must be zero'
71
+ when SCRIPT_ERR_MINIMALIF
72
+ 'OP_IF/NOTIF argument must be minimal'
73
+ when SCRIPT_ERR_SIG_NULLFAIL
74
+ 'Signature must be zero for failed CHECK(MULTI)SIG operation'
75
+ when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS
76
+ 'NOPx reserved for soft-fork upgrades'
77
+ when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
78
+ 'Witness version reserved for soft-fork upgrades'
79
+ when SCRIPT_ERR_PUBKEYTYPE
80
+ 'Public key is neither compressed or uncompressed'
81
+ when SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH
82
+ 'Witness program has incorrect length'
83
+ when SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY
84
+ 'Witness program was passed an empty witness'
85
+ when SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH
86
+ 'Witness program hash mismatch'
87
+ when SCRIPT_ERR_WITNESS_MALLEATED
88
+ 'Witness requires empty scriptSig'
89
+ when SCRIPT_ERR_WITNESS_MALLEATED_P2SH
90
+ 'Witness requires only-redeemscript scriptSig'
91
+ when SCRIPT_ERR_WITNESS_UNEXPECTED
92
+ 'Witness provided for non-witness script'
93
+ when SCRIPT_ERR_WITNESS_PUBKEYTYPE
94
+ 'Using non-compressed keys in segwit'
95
+ when SCRIPT_ERR_OP_CODESEPARATOR
96
+ 'Using OP_CODESEPARATOR in non-witness scrip'
97
+ when SCRIPT_ERR_SIG_FINDANDDELETE
98
+ 'Signature is found in scriptCode'
99
+ when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
100
+ 'unknown error'
101
+ else
102
+ extra_msg ? extra_msg : 'unknown error'
103
+ end
104
+ end
105
+
106
+ def self.name_to_code(name)
107
+ NAME_MAP[name]
108
+ end
109
+
110
+ end
111
+ end