tapyrus 0.1.0 → 0.2.4

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -15
  3. data/exe/tapyrusrbd +2 -2
  4. data/lib/openassets/util.rb +2 -4
  5. data/lib/schnorr.rb +83 -0
  6. data/lib/schnorr/signature.rb +38 -0
  7. data/lib/tapyrus.rb +11 -18
  8. data/lib/tapyrus/block.rb +3 -38
  9. data/lib/tapyrus/block_header.rb +70 -41
  10. data/lib/tapyrus/chain_params.rb +13 -36
  11. data/lib/tapyrus/chainparams/{regtest.yml → dev.yml} +10 -19
  12. data/lib/tapyrus/chainparams/{mainnet.yml → prod.yml} +7 -19
  13. data/lib/tapyrus/constants.rb +12 -34
  14. data/lib/tapyrus/errors.rb +17 -0
  15. data/lib/tapyrus/ext.rb +5 -0
  16. data/lib/tapyrus/ext/ecdsa.rb +39 -0
  17. data/lib/tapyrus/ext/json_parser.rb +47 -0
  18. data/lib/tapyrus/ext_key.rb +42 -23
  19. data/lib/tapyrus/key.rb +47 -40
  20. data/lib/tapyrus/message.rb +2 -2
  21. data/lib/tapyrus/message/base.rb +1 -0
  22. data/lib/tapyrus/message/block.rb +4 -4
  23. data/lib/tapyrus/message/cmpct_block.rb +3 -5
  24. data/lib/tapyrus/message/header_and_short_ids.rb +1 -1
  25. data/lib/tapyrus/message/headers.rb +1 -1
  26. data/lib/tapyrus/message/merkle_block.rb +1 -1
  27. data/lib/tapyrus/message/tx.rb +2 -2
  28. data/lib/tapyrus/network/peer.rb +1 -15
  29. data/lib/tapyrus/node/cli.rb +15 -11
  30. data/lib/tapyrus/node/configuration.rb +1 -1
  31. data/lib/tapyrus/node/spv.rb +1 -1
  32. data/lib/tapyrus/opcodes.rb +5 -0
  33. data/lib/tapyrus/out_point.rb +1 -1
  34. data/lib/tapyrus/rpc/request_handler.rb +7 -6
  35. data/lib/tapyrus/rpc/tapyrus_core_client.rb +17 -15
  36. data/lib/tapyrus/script/color.rb +79 -0
  37. data/lib/tapyrus/script/multisig.rb +0 -27
  38. data/lib/tapyrus/script/script.rb +74 -89
  39. data/lib/tapyrus/script/script_error.rb +8 -14
  40. data/lib/tapyrus/script/script_interpreter.rb +65 -86
  41. data/lib/tapyrus/script/tx_checker.rb +21 -5
  42. data/lib/tapyrus/secp256k1.rb +1 -0
  43. data/lib/tapyrus/secp256k1/native.rb +93 -20
  44. data/lib/tapyrus/secp256k1/rfc6979.rb +43 -0
  45. data/lib/tapyrus/secp256k1/ruby.rb +63 -54
  46. data/lib/tapyrus/store/chain_entry.rb +3 -2
  47. data/lib/tapyrus/store/db/level_db.rb +58 -0
  48. data/lib/tapyrus/store/spv_chain.rb +29 -11
  49. data/lib/tapyrus/tx.rb +18 -160
  50. data/lib/tapyrus/tx_in.rb +4 -11
  51. data/lib/tapyrus/tx_out.rb +2 -1
  52. data/lib/tapyrus/util.rb +8 -0
  53. data/lib/tapyrus/validation.rb +1 -6
  54. data/lib/tapyrus/version.rb +1 -1
  55. data/lib/tapyrus/wallet/account.rb +1 -0
  56. data/lib/tapyrus/wallet/master_key.rb +1 -0
  57. data/tapyrusrb.gemspec +3 -3
  58. metadata +44 -39
  59. data/lib/tapyrus/chainparams/testnet.yml +0 -41
  60. data/lib/tapyrus/descriptor.rb +0 -147
  61. data/lib/tapyrus/script_witness.rb +0 -38
@@ -0,0 +1,79 @@
1
+ module Tapyrus
2
+ module Color
3
+ module TokenTypes
4
+ NONE = 0x00
5
+ REISSUABLE = 0xC1
6
+ NON_REISSUABLE = 0xC2
7
+ NFT = 0xC3
8
+ end
9
+
10
+ class ColorIdentifier
11
+ include Tapyrus::HexConverter
12
+
13
+ attr_reader :type, :payload
14
+
15
+ def self.reissuable(script_pubkey)
16
+ new(TokenTypes::REISSUABLE, Tapyrus.sha256(script_pubkey.to_payload))
17
+ end
18
+
19
+ def self.non_reissuable(out_point)
20
+ new(TokenTypes::NON_REISSUABLE, Tapyrus.sha256(out_point.to_payload))
21
+ end
22
+
23
+ def self.nft(out_point)
24
+ new(TokenTypes::NFT, Tapyrus.sha256(out_point.to_payload))
25
+ end
26
+
27
+ def to_payload
28
+ [type, payload].pack('Ca*')
29
+ end
30
+
31
+ def self.parse_from_payload(payload)
32
+ type, payload = payload.unpack('Ca*')
33
+ new(type, payload)
34
+ end
35
+
36
+ def ==(other)
37
+ other && other.to_payload == to_payload
38
+ end
39
+
40
+ def valid?
41
+ return false unless [TokenTypes::REISSUABLE, TokenTypes::NON_REISSUABLE, TokenTypes::NFT].include?(type)
42
+ return false unless payload.bytesize == 32
43
+ true
44
+ end
45
+
46
+ private
47
+
48
+ def initialize(type, payload)
49
+ @type = type
50
+ @payload = payload
51
+ end
52
+ end
53
+
54
+ module ColoredOutput
55
+ def colored?
56
+ script_pubkey.cp2pkh? || script_pubkey.cp2sh?
57
+ end
58
+
59
+ def color_id
60
+ @color_id ||= ColorIdentifier.parse_from_payload(script_pubkey.chunks[0].pushed_data)
61
+ end
62
+
63
+ def reissuable?
64
+ return false unless colored?
65
+ color_id.type == TokenTypes::REISSUABLE
66
+ end
67
+
68
+ def non_reissuable?
69
+ return false unless colored?
70
+ color_id.type == TokenTypes::NON_REISSUABLE
71
+ end
72
+
73
+ def nft?
74
+ return false unless colored?
75
+ color_id.type == TokenTypes::NFT
76
+ end
77
+ end
78
+ end
79
+ end
@@ -61,32 +61,5 @@ module Tapyrus
61
61
  prefix + pubkeys.map { |k| sigs[k] ? Tapyrus::Script.pack_pushdata(sigs[k]) : nil }.join +
62
62
  Tapyrus::Script.pack_pushdata(script.chunks[-1].pushed_data)
63
63
  end
64
-
65
- def self.add_sig_to_multisig_script_witness(sig_to_add, script_witness, hash_type = SIGHASH_TYPE[:all])
66
- signature = sig_to_add + [hash_type].pack("C*")
67
- script_witness.stack << signature
68
- end
69
-
70
- # Sort signatures in the given +script_witness+ according to the order of pubkeys in
71
- # the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
72
- # @param [ScriptWitness] script_witness for multisig.
73
- # @param [String] sig_hash to be signed.
74
- def self.sort_witness_multisig_signatures(script_witness, sig_hash)
75
- redeem_script = Tapyrus::Script.parse_from_payload(script_witness.stack[-1])
76
- pubkeys = redeem_script.get_multisig_pubkeys
77
- sigs = Hash[script_witness.stack[1...-1].map.with_index do |sig, idx|
78
- pubkey = pubkeys.map do |key|
79
- Tapyrus::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
80
- end.compact.first
81
- raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
82
- [pubkey, sig]
83
- end]
84
- script_witness.stack.clear
85
- script_witness.stack << ''
86
- pubkeys.each do |pubkey|
87
- script_witness.stack << sigs[pubkey] if sigs[pubkey]
88
- end
89
- script_witness.stack << redeem_script.to_payload
90
- end
91
64
  end
92
65
  end
@@ -5,6 +5,7 @@ module Tapyrus
5
5
 
6
6
  # tapyrus script
7
7
  class Script
8
+ include Tapyrus::HexConverter
8
9
  include Tapyrus::Opcodes
9
10
 
10
11
  attr_accessor :chunks
@@ -18,11 +19,6 @@ module Tapyrus
18
19
  new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
19
20
  end
20
21
 
21
- # generate P2WPKH script
22
- def self.to_p2wpkh(pubkey_hash)
23
- new << WITNESS_VERSION << pubkey_hash
24
- end
25
-
26
22
  # generate m of n multisig p2sh script
27
23
  # @param [String] m the number of signatures required for multisig
28
24
  # @param [Array] pubkeys array of public keys that compose multisig
@@ -45,6 +41,40 @@ module Tapyrus
45
41
  Script.to_p2sh(to_hash160)
46
42
  end
47
43
 
44
+ # generate cp2pkh script
45
+ # @param [ColorIdentifier] color identifier
46
+ # @param [String] hash160 of pubkey
47
+ # @return [Script] CP2PKH script
48
+ # @raise [ArgumentError] if color_id is nil or invalid
49
+ def self.to_cp2pkh(color_id, pubkey_hash)
50
+ raise ArgumentError, 'Specified color identifier is invalid' unless color_id&.valid?
51
+ new << color_id.to_payload << OP_COLOR << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
52
+ end
53
+
54
+ # generate cp2sh script
55
+ # @param [ColorIdentifier] color identifier
56
+ # @param [String] hash160 of script
57
+ # @return [Script] CP2SH script
58
+ # @raise [ArgumentError] if color_id is nil or invalid
59
+ def self.to_cp2sh(color_id, script_hash)
60
+ raise ArgumentError, 'Specified color identifier is invalid' unless color_id&.valid?
61
+ new << color_id.to_payload << OP_COLOR << OP_HASH160 << script_hash << OP_EQUAL
62
+ end
63
+
64
+ # Add color identifier to existing p2pkh or p2sh
65
+ # @param [ColorIdentifier] color identifier
66
+ # @return [Script] CP2PKH or CP2SH script
67
+ # @raise [ArgumentError] if color_id is nil or invalid
68
+ # @raise [RuntimeError] if script is neither p2pkh nor p2sh
69
+ def add_color(color_id)
70
+ raise ArgumentError, 'Specified color identifier is invalid' unless color_id&.valid?
71
+ raise RuntimeError, 'Only p2pkh and p2sh can add color' unless p2pkh? or p2sh?
72
+ Tapyrus::Script.new.tap do |s|
73
+ s << color_id.to_payload << OP_COLOR
74
+ s.chunks += self.chunks
75
+ end
76
+ end
77
+
48
78
  def get_multisig_pubkeys
49
79
  num = Tapyrus::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
50
80
  (1..num).map{ |i| chunks[i].pushed_data }
@@ -59,13 +89,6 @@ module Tapyrus
59
89
  new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
60
90
  end
61
91
 
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
92
  # generate script from string.
70
93
  def self.from_string(string)
71
94
  script = new
@@ -151,14 +174,15 @@ module Tapyrus
151
174
  def addresses
152
175
  return [p2pkh_addr] if p2pkh?
153
176
  return [p2sh_addr] if p2sh?
154
- return [bech32_addr] if witness_program?
177
+ return [cp2pkh_addr] if cp2pkh?
178
+ return [cp2sh_addr] if cp2sh?
155
179
  return get_multisig_pubkeys.map{|pubkey| Tapyrus::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
156
180
  []
157
181
  end
158
182
 
159
183
  # check whether standard script.
160
184
  def standard?
161
- p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
185
+ p2pkh? | p2sh? | multisig? | standard_op_return?
162
186
  end
163
187
 
164
188
  # whether this script is a P2PKH format script.
@@ -168,17 +192,6 @@ module Tapyrus
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.
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
195
  def p2sh?
183
196
  return false unless chunks.size == 3
184
197
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
@@ -201,6 +214,23 @@ module Tapyrus
201
214
  (chunks.size == 1 || chunks[1].opcode <= OP_16)
202
215
  end
203
216
 
217
+ def cp2pkh?
218
+ return false unless chunks.size == 7
219
+ return false unless chunks[0].bytesize == 34
220
+ return false unless Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)&.valid?
221
+ return false unless chunks[1].ord == OP_COLOR
222
+ [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
223
+ (chunks[2..3]+ chunks[5..6]).map(&:ord) && chunks[4].bytesize == 21
224
+ end
225
+
226
+ def cp2sh?
227
+ return false unless chunks.size == 5
228
+ return false unless chunks[0].bytesize == 34
229
+ return false unless Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)&.valid?
230
+ return false unless chunks[1].ord == OP_COLOR
231
+ OP_HASH160 == chunks[2].ord && OP_EQUAL == chunks[4].ord && chunks[3].bytesize == 21
232
+ end
233
+
204
234
  def op_return_data
205
235
  return nil unless op_return?
206
236
  return nil if chunks.size == 1
@@ -221,50 +251,15 @@ module Tapyrus
221
251
  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
252
  end
223
253
 
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.
254
+ # returns the self payload. ScriptInterpreter does not use this.
251
255
  def to_script_code(skip_separator_index = 0)
252
256
  payload = to_payload
253
- if p2wpkh?
254
- payload = Script.to_p2pkh(chunks[1].pushed_data.bth).to_payload
255
- elsif skip_separator_index > 0
257
+ if skip_separator_index > 0
256
258
  payload = subscript_codeseparator(skip_separator_index)
257
259
  end
258
260
  Tapyrus.pack_var_string(payload)
259
261
  end
260
262
 
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
263
  # append object to payload
269
264
  def <<(obj)
270
265
  if obj.is_a?(Integer)
@@ -324,6 +319,7 @@ module Tapyrus
324
319
  when Integer
325
320
  opcode_to_name(c)
326
321
  when String
322
+ return c if c.empty?
327
323
  if c.pushdata?
328
324
  v = Opcodes.opcode_to_small_int(c.ord)
329
325
  if v
@@ -351,7 +347,7 @@ module Tapyrus
351
347
 
352
348
  # generate hash160 hash for payload
353
349
  def to_hash160
354
- Tapyrus.hash160(to_payload.bth)
350
+ Tapyrus.hash160(to_hex)
355
351
  end
356
352
 
357
353
  # script size
@@ -482,13 +478,11 @@ module Tapyrus
482
478
  return 'pubkeyhash' if p2pkh?
483
479
  return 'scripthash' if p2sh?
484
480
  return 'multisig' if multisig?
485
- return 'witness_v0_keyhash' if p2wpkh?
486
- return 'witness_v0_scripthash' if p2wsh?
487
481
  'nonstandard'
488
482
  end
489
483
 
490
484
  def to_h
491
- h = {asm: to_s, hex: to_payload.bth, type: type}
485
+ h = {asm: to_s, hex: to_hex, type: type}
492
486
  addrs = addresses
493
487
  unless addrs.empty?
494
488
  h[:req_sigs] = multisig? ? Tapyrus::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
@@ -504,48 +498,39 @@ module Tapyrus
504
498
  (size > 0 && op_return?) || size > Tapyrus::MAX_SCRIPT_SIZE
505
499
  end
506
500
 
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
501
  private
514
502
 
515
503
  # generate p2pkh address. if script dose not p2pkh, return nil.
516
504
  def p2pkh_addr
517
505
  return nil unless p2pkh?
518
506
  hash160 = chunks[2].pushed_data.bth
519
- return nil unless hash160.htb.bytesize == 20
520
507
  Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.address_version)
521
508
  end
522
509
 
523
- # generate p2wpkh address. if script dose not p2wpkh, return nil.
524
- def p2wpkh_addr
525
- p2wpkh? ? bech32_addr : nil
526
- end
527
-
528
510
  # generate p2sh address. if script dose not p2sh, return nil.
529
511
  def p2sh_addr
530
512
  return nil unless p2sh?
531
513
  hash160 = chunks[1].pushed_data.bth
532
- return nil unless hash160.htb.bytesize == 20
533
514
  Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.p2sh_version)
534
515
  end
535
516
 
536
- # generate p2wsh address. if script dose not p2wsh, return nil.
537
- def p2wsh_addr
538
- p2wsh? ? bech32_addr : nil
539
- end
517
+ # generate cp2pkh address. if script dose not cp2pkh, return nil.
518
+ def cp2pkh_addr
519
+ return nil unless cp2pkh?
540
520
 
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
521
+ color_id = chunks[0].pushed_data.bth
522
+ hash160 = chunks[4].pushed_data.bth
523
+ Tapyrus.encode_base58_address(color_id + hash160, Tapyrus.chain_params.cp2pkh_version)
547
524
  end
548
525
 
526
+ # generate cp2sh address. if script dose not cp2sh, return nil.
527
+ def cp2sh_addr
528
+ return nil unless cp2sh?
529
+
530
+ color_id = chunks[0].pushed_data.bth
531
+ hash160 = chunks[3].pushed_data.bth
532
+ Tapyrus.encode_base58_address(color_id + hash160, Tapyrus.chain_params.cp2sh_version)
533
+ end
549
534
  end
550
535
 
551
536
  end
@@ -78,24 +78,18 @@ module Tapyrus
78
78
  'Witness version reserved for soft-fork upgrades'
79
79
  when SCRIPT_ERR_PUBKEYTYPE
80
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
81
  when SCRIPT_ERR_OP_CODESEPARATOR
96
82
  'Using OP_CODESEPARATOR in non-witness scrip'
97
83
  when SCRIPT_ERR_SIG_FINDANDDELETE
98
84
  'Signature is found in scriptCode'
85
+ when SCRIPT_ERR_OP_COLOR_UNEXPECTED
86
+ 'Unexpected OP_COLOR in script'
87
+ when SCRIPT_ERR_OP_COLOR_ID_INVALID
88
+ 'Invalid ColorId in script'
89
+ when SCRIPT_ERR_OP_COLOR_MULTIPLE
90
+ 'Multiple OP_COLOR found in script'
91
+ when SCRIPT_ERR_OP_COLOR_IN_BRANCH
92
+ 'OP_COLOR is not permitted in a script branch'
99
93
  when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
100
94
  'unknown error'
101
95
  else
@@ -32,32 +32,21 @@ module Tapyrus
32
32
  # eval script
33
33
  # @param [Tapyrus::Script] script_sig a signature script (unlock script which data push only)
34
34
  # @param [Tapyrus::Script] script_pubkey a script pubkey (locking script)
35
- # @param [Tapyrus::ScriptWitness] witness a witness script
36
35
  # @return [Boolean] result
37
- def verify_script(script_sig, script_pubkey, witness = ScriptWitness.new)
36
+ def verify_script(script_sig, script_pubkey)
38
37
 
39
38
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !script_sig.push_only?
40
39
 
41
40
  stack_copy = nil
42
- had_witness = false
43
41
 
44
- return false unless eval_script(script_sig, :base)
42
+ return false unless eval_script(script_sig, :base, false)
45
43
 
46
44
  stack_copy = stack.dup if flag?(SCRIPT_VERIFY_P2SH)
47
45
 
48
- return false unless eval_script(script_pubkey, :base)
46
+ return false unless eval_script(script_pubkey, :base, false)
49
47
 
50
48
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last.htb)
51
49
 
52
- # Bare witness programs
53
- if flag?(SCRIPT_VERIFY_WITNESS) && script_pubkey.witness_program?
54
- had_witness = true
55
- return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
56
- version, program = script_pubkey.witness_data
57
- stack_copy = stack.dup
58
- return false unless verify_witness_program(witness, version, program)
59
- end
60
-
61
50
  # Additional validation for spend-to-script-hash transactions
62
51
  if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
63
52
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
@@ -69,18 +58,8 @@ module Tapyrus
69
58
  rescue Exception => e
70
59
  return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
71
60
  end
72
- return false unless eval_script(redeem_script, :base)
61
+ return false unless eval_script(redeem_script, :base, true)
73
62
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
74
-
75
- # P2SH witness program
76
- if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
77
- had_witness = true
78
- # The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we reintroduce malleability.
79
- return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Tapyrus::Script.new << redeem_script.to_payload.bth)
80
-
81
- version, program = redeem_script.witness_data
82
- return false unless verify_witness_program(witness, version, program)
83
- end
84
63
  end
85
64
 
86
65
  # The CLEANSTACK check is only performed after potential P2SH evaluation,
@@ -93,11 +72,6 @@ module Tapyrus
93
72
  return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
94
73
  end
95
74
 
96
- if flag?(SCRIPT_VERIFY_WITNESS)
97
- raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH)
98
- return set_error(SCRIPT_ERR_WITNESS_UNEXPECTED) if !had_witness && !witness.empty?
99
- end
100
-
101
75
  true
102
76
  end
103
77
 
@@ -106,45 +80,14 @@ module Tapyrus
106
80
  false
107
81
  end
108
82
 
109
- def verify_witness_program(witness, version, program)
110
- if version == 0
111
- if program.bytesize == 32
112
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if witness.stack.size == 0
113
- script_pubkey = Tapyrus::Script.parse_from_payload(witness.stack.last)
114
- @stack = witness.stack[0..-2].map{|w|w.bth}
115
- script_hash = Tapyrus.sha256(script_pubkey.to_payload)
116
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program
117
- elsif program.bytesize == 20
118
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless witness.stack.size == 2
119
- script_pubkey = Tapyrus::Script.to_p2pkh(program.bth)
120
- @stack = witness.stack.map{|w|w.bth}
121
- else
122
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
123
- end
124
- elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
125
- return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
126
- else
127
- return true # Higher version witness scripts return true for future softfork compatibility
128
- end
129
-
130
- stack.each do |s| # Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack
131
- return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
132
- end
133
-
134
- return false unless eval_script(script_pubkey, :witness_v0)
135
-
136
- return set_error(SCRIPT_ERR_EVAL_FALSE) unless stack.size == 1
137
- return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
138
- true
139
- end
140
-
141
- def eval_script(script, sig_version)
83
+ def eval_script(script, sig_version, is_redeem_script)
142
84
  return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
143
85
  begin
144
86
  flow_stack = []
145
87
  alt_stack = []
146
88
  last_code_separator_index = 0
147
89
  op_count = 0
90
+ color_id = nil
148
91
 
149
92
  script.chunks.each_with_index do |c, index|
150
93
  need_exec = !flow_stack.include?(false)
@@ -209,7 +152,7 @@ module Tapyrus
209
152
  if need_exec
210
153
  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
211
154
  value = pop_string.htb
212
- if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
155
+ if flag?(SCRIPT_VERIFY_MINIMALIF)
213
156
  if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
214
157
  return set_error(SCRIPT_ERR_MINIMALIF)
215
158
  end
@@ -409,8 +352,8 @@ module Tapyrus
409
352
  return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
410
353
  subscript = tmp
411
354
  end
412
-
413
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
355
+ return false if (sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ?
356
+ !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey)
414
357
 
415
358
  success = checker.check_sig(sig, pubkey, subscript, sig_version)
416
359
 
@@ -428,6 +371,19 @@ module Tapyrus
428
371
  return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
429
372
  end
430
373
  end
374
+ when OP_CHECKDATASIG, OP_CHECKDATASIGVERIFY
375
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
376
+ sig, msg, pubkey = pop_string(3)
377
+ # check signature encoding without hashtype byte
378
+ return false if (sig.htb.bytesize != (Tapyrus::Key::COMPACT_SIGNATURE_SIZE - 1) && !check_ecdsa_signature_encoding(sig, true)) || !check_pubkey_encoding(pubkey)
379
+ digest = Tapyrus.sha256(msg)
380
+ success = checker.verify_sig(sig, pubkey, digest)
381
+ return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
382
+ push_int(success ? 1 : 0)
383
+ if opcode == OP_CHECKDATASIGVERIFY
384
+ stack.pop if success
385
+ return set_error(SCRIPT_ERR_CHECKDATASIGVERIFY) unless success
386
+ end
431
387
  when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
432
388
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
433
389
  pubkey_count = pop_int
@@ -463,10 +419,17 @@ module Tapyrus
463
419
  end
464
420
 
465
421
  success = true
422
+ current_sig_scheme = nil
466
423
  while success && sig_count > 0
467
424
  sig = sigs.pop
468
425
  pubkey = pubkeys.pop
469
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
426
+ sig_scheme = sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ? :schnorr : :ecdsa
427
+ current_sig_scheme = sig_scheme if current_sig_scheme.nil?
428
+
429
+ return false if (sig_scheme == :schnorr ? !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey) # error already set.
430
+
431
+ return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme
432
+
470
433
  ok = checker.check_sig(sig, pubkey, subscript, sig_version)
471
434
  if ok
472
435
  sig_count -= 1
@@ -488,9 +451,8 @@ module Tapyrus
488
451
  # Unfortunately this is a potential source of mutability,
489
452
  # so optionally verify it is exactly equal to zero prior to removing it from the stack.
490
453
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
491
- if flag?(SCRIPT_VERIFY_NULLDUMMY) && stack[-1].size > 0
492
- return set_error(SCRIPT_ERR_SIG_NULLDUMMY)
493
- end
454
+ return set_error(SCRIPT_ERR_SIG_NULLDUMMY) if stack[-1].size > 0
455
+
494
456
  stack.pop
495
457
 
496
458
  push_int(success ? 1 : 0)
@@ -503,6 +465,25 @@ module Tapyrus
503
465
  end
504
466
  when OP_RETURN
505
467
  return set_error(SCRIPT_ERR_OP_RETURN)
468
+ when OP_COLOR
469
+ # Color id is not permitted in p2sh redeem script
470
+ return set_error(SCRIPT_ERR_OP_COLOR_UNEXPECTED) if is_redeem_script
471
+
472
+ # if Color id is already initialized this must be an extra
473
+ return set_error(SCRIPT_ERR_OP_COLOR_MULTIPLE) if color_id && color_id.type != Tapyrus::Color::TokenTypes::NONE
474
+
475
+ # color id is not allowed inside OP_IF
476
+ return set_error(SCRIPT_ERR_OP_COLOR_IN_BRANCH) unless flow_stack.empty?
477
+
478
+ # pop one stack element and verify that it exists
479
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size() < 1
480
+
481
+ color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(stack.last.htb)
482
+
483
+ # check ColorIdentifier is valid
484
+ return set_error(SCRIPT_ERR_OP_COLOR_ID_INVALID) unless color_id.valid?
485
+
486
+ stack.pop
506
487
  else
507
488
  return set_error(SCRIPT_ERR_BAD_OPCODE)
508
489
  end
@@ -586,20 +567,26 @@ module Tapyrus
586
567
  end
587
568
  end
588
569
 
589
- def check_signature_encoding(sig)
570
+ def check_ecdsa_signature_encoding(sig, data_sig = false)
590
571
  return true if sig.size.zero?
591
- if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !Key.valid_signature_encoding?(sig.htb)
572
+ if !Key.valid_signature_encoding?(sig.htb, data_sig)
592
573
  return set_error(SCRIPT_ERR_SIG_DER)
593
- elsif flag?(SCRIPT_VERIFY_LOW_S) && !low_der_signature?(sig)
574
+ elsif !low_der_signature?(sig, data_sig)
594
575
  return false
595
- elsif flag?(SCRIPT_VERIFY_STRICTENC) && !defined_hashtype_signature?(sig)
576
+ elsif !data_sig && !defined_hashtype_signature?(sig)
596
577
  return set_error(SCRIPT_ERR_SIG_HASHTYPE)
597
578
  end
598
579
  true
599
580
  end
600
581
 
601
- def low_der_signature?(sig)
602
- return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb)
582
+ def check_schnorr_signature_encoding(sig, data_sig = false)
583
+ return false unless sig.htb.bytesize == (data_sig ? 64 : 65)
584
+ return set_error(SCRIPT_ERR_SIG_HASHTYPE) if !data_sig && !defined_hashtype_signature?(sig)
585
+ true
586
+ end
587
+
588
+ def low_der_signature?(sig, data_sig = false)
589
+ return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb, data_sig)
603
590
  return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb)
604
591
  true
605
592
  end
@@ -609,20 +596,12 @@ module Tapyrus
609
596
  return false if sig.empty?
610
597
  s = sig.unpack('C*')
611
598
  hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
612
- hash_type &= (~(Tapyrus::SIGHASH_FORK_ID)) if Tapyrus.chain_params.fork_chain? # for fork coin.
613
599
  return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
614
600
  true
615
601
  end
616
602
 
617
- def check_pubkey_encoding(pubkey, sig_version)
618
- if flag?(SCRIPT_VERIFY_STRICTENC) && !Key.compress_or_uncompress_pubkey?(pubkey)
619
- return set_error(SCRIPT_ERR_PUBKEYTYPE)
620
- end
621
- # Only compressed keys are accepted in segwit
622
- if flag?(SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) &&
623
- sig_version == :witness_v0 && !Key.compress_pubkey?(pubkey)
624
- return set_error(SCRIPT_ERR_WITNESS_PUBKEYTYPE)
625
- end
603
+ def check_pubkey_encoding(pubkey)
604
+ return set_error(SCRIPT_ERR_PUBKEYTYPE) unless Key.compress_or_uncompress_pubkey?(pubkey)
626
605
  true
627
606
  end
628
607