bitcoinrb 0.5.0 → 0.6.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +5 -4
  4. data/README.md +10 -0
  5. data/bitcoinrb.gemspec +4 -4
  6. data/lib/bitcoin.rb +29 -16
  7. data/lib/bitcoin/block_filter.rb +14 -0
  8. data/lib/bitcoin/chain_params.rb +9 -0
  9. data/lib/bitcoin/chainparams/signet.yml +39 -0
  10. data/lib/bitcoin/constants.rb +43 -3
  11. data/lib/bitcoin/descriptor.rb +1 -1
  12. data/lib/bitcoin/errors.rb +19 -0
  13. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  14. data/lib/bitcoin/ext_key.rb +35 -19
  15. data/lib/bitcoin/key.rb +43 -26
  16. data/lib/bitcoin/message/cfcheckpt.rb +2 -2
  17. data/lib/bitcoin/message/cfheaders.rb +1 -1
  18. data/lib/bitcoin/message/cfilter.rb +1 -1
  19. data/lib/bitcoin/message/fee_filter.rb +1 -1
  20. data/lib/bitcoin/message/filter_load.rb +3 -3
  21. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  22. data/lib/bitcoin/message/inventory.rb +1 -1
  23. data/lib/bitcoin/message/merkle_block.rb +1 -1
  24. data/lib/bitcoin/message/network_addr.rb +3 -3
  25. data/lib/bitcoin/message/ping.rb +1 -1
  26. data/lib/bitcoin/message/pong.rb +1 -1
  27. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  28. data/lib/bitcoin/mnemonic.rb +2 -2
  29. data/lib/bitcoin/network/peer_discovery.rb +1 -3
  30. data/lib/bitcoin/node/configuration.rb +3 -1
  31. data/lib/bitcoin/node/spv.rb +8 -0
  32. data/lib/bitcoin/opcodes.rb +14 -1
  33. data/lib/bitcoin/payment_code.rb +2 -2
  34. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  35. data/lib/bitcoin/psbt/input.rb +3 -3
  36. data/lib/bitcoin/psbt/output.rb +1 -1
  37. data/lib/bitcoin/psbt/tx.rb +4 -4
  38. data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
  39. data/lib/bitcoin/script/script.rb +52 -19
  40. data/lib/bitcoin/script/script_error.rb +27 -1
  41. data/lib/bitcoin/script/script_interpreter.rb +161 -62
  42. data/lib/bitcoin/script/tx_checker.rb +64 -14
  43. data/lib/bitcoin/secp256k1/native.rb +138 -25
  44. data/lib/bitcoin/secp256k1/ruby.rb +78 -19
  45. data/lib/bitcoin/sighash_generator.rb +156 -0
  46. data/lib/bitcoin/tx.rb +13 -80
  47. data/lib/bitcoin/tx_in.rb +1 -1
  48. data/lib/bitcoin/tx_out.rb +2 -3
  49. data/lib/bitcoin/util.rb +15 -6
  50. data/lib/bitcoin/version.rb +1 -1
  51. data/lib/bitcoin/wallet/account.rb +1 -1
  52. metadata +19 -15
@@ -63,7 +63,7 @@ module Bitcoin
63
63
  response = http.request(request)
64
64
  body = response.body
65
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_s if response['error']
66
+ raise response['error'].to_json if response['error']
67
67
  response['result']
68
68
  end
69
69
 
@@ -110,25 +110,28 @@ module Bitcoin
110
110
  if opcode.pushdata?
111
111
  pushcode = opcode.ord
112
112
  packed_size = nil
113
+ if buf.eof?
114
+ s.chunks << opcode
115
+ return s
116
+ end
113
117
  len = case pushcode
114
118
  when OP_PUSHDATA1
115
119
  packed_size = buf.read(1)
116
- packed_size.unpack('C').first
120
+ packed_size.unpack1('C')
117
121
  when OP_PUSHDATA2
118
122
  packed_size = buf.read(2)
119
- packed_size.unpack('v').first
123
+ packed_size.unpack1('v')
120
124
  when OP_PUSHDATA4
121
125
  packed_size = buf.read(4)
122
- packed_size.unpack('V').first
126
+ packed_size.unpack1('V')
123
127
  else
124
- pushcode if pushcode < OP_PUSHDATA1
128
+ pushcode < OP_PUSHDATA1 ? pushcode : 0
125
129
  end
126
- if len
127
- s.chunks << [len].pack('C') if buf.eof?
128
- unless buf.eof?
129
- chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
130
- s.chunks << chunk
131
- end
130
+ if buf.eof?
131
+ s.chunks << [len].pack('C')
132
+ else buf.eof?
133
+ chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
134
+ s.chunks << chunk
132
135
  end
133
136
  else
134
137
  if Opcodes.defined?(opcode.ord)
@@ -141,8 +144,12 @@ module Bitcoin
141
144
  s
142
145
  end
143
146
 
144
- def to_payload
145
- chunks.join
147
+ # Output script payload.
148
+ # @param [Boolean] length_prefixed Flag whether the length of the pyrode should be given at the beginning.(default: false)
149
+ # @return [String] payload
150
+ def to_payload(length_prefixed = false)
151
+ p = chunks.join
152
+ length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
146
153
  end
147
154
 
148
155
  def empty?
@@ -242,7 +249,7 @@ module Bitcoin
242
249
  return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
243
250
  return false unless chunks[1].pushdata?
244
251
 
245
- if size == (chunks[1][0].unpack('C').first + 2)
252
+ if size == (chunks[1][0].unpack1('C') + 2)
246
253
  program_size = chunks[1].pushed_data.bytesize
247
254
  return program_size >= 2 && program_size <= 40
248
255
  end
@@ -343,10 +350,14 @@ module Bitcoin
343
350
  v
344
351
  else
345
352
  data = c.pushed_data
346
- if data.bytesize <= 4
347
- Script.decode_number(data.bth) # for scriptnum
353
+ if data
354
+ if data.bytesize <= 4
355
+ Script.decode_number(data.bth) # for scriptnum
356
+ else
357
+ data.bth
358
+ end
348
359
  else
349
- data.bth
360
+ c.bth
350
361
  end
351
362
  end
352
363
  else
@@ -393,8 +404,8 @@ module Bitcoin
393
404
  hex = '0' + hex unless (hex.length % 2).zero?
394
405
  v = hex.htb.reverse # change endian
395
406
 
396
- v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack('C').first & 0x80) == 0
397
- v[-1] = [v[-1].unpack('C').first | 0x80].pack('C') if negative
407
+ v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack1('C') & 0x80) == 0
408
+ v[-1] = [v[-1].unpack1('C') | 0x80].pack('C') if negative
398
409
  v.bth
399
410
  end
400
411
 
@@ -402,7 +413,7 @@ module Bitcoin
402
413
  def self.decode_number(s)
403
414
  v = s.htb.reverse
404
415
  return 0 if v.length.zero?
405
- mbs = v[0].unpack('C').first
416
+ mbs = v[0].unpack1('C')
406
417
  v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
407
418
  result = v.bth.to_i(16)
408
419
  result = -result unless (mbs & 0x80) == 0
@@ -553,6 +564,28 @@ module Bitcoin
553
564
  segwit_addr.addr
554
565
  end
555
566
 
567
+ # Check whether push data length is valid.
568
+ # @return [Boolean] if valid return true, otherwise false.
569
+ def valid_pushdata_length?(chunk)
570
+ buf = StringIO.new(chunk)
571
+ opcode = buf.read(1).ord
572
+ offset = 1
573
+ len = case opcode
574
+ when OP_PUSHDATA1
575
+ offset += 1
576
+ buf.read(1).unpack1('C')
577
+ when OP_PUSHDATA2
578
+ offset += 2
579
+ buf.read(2).unpack1('v')
580
+ when OP_PUSHDATA4
581
+ offset += 4
582
+ buf.read(4).unpack1('V')
583
+ else
584
+ opcode
585
+ end
586
+ chunk.bytesize == len + offset
587
+ end
588
+
556
589
  end
557
590
 
558
591
  end
@@ -76,6 +76,14 @@ module Bitcoin
76
76
  'NOPx reserved for soft-fork upgrades'
77
77
  when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
78
78
  'Witness version reserved for soft-fork upgrades'
79
+ when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
80
+ 'Taproot version reserved for soft-fork upgrades'
81
+ when SCRIPT_ERR_DISCOURAGE_UNKNOWN_ANNEX
82
+ 'Unknown input annex reserved for soft-fork upgrades'
83
+ when SCRIPT_ERR_DISCOURAGE_OP_SUCCESS
84
+ 'SUCCESSx reserved for soft-fork upgrades'
85
+ when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
86
+ 'Public key version reserved for soft-fork upgrades'
79
87
  when SCRIPT_ERR_PUBKEYTYPE
80
88
  'Public key is neither compressed or uncompressed'
81
89
  when SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH
@@ -92,8 +100,22 @@ module Bitcoin
92
100
  'Witness provided for non-witness script'
93
101
  when SCRIPT_ERR_WITNESS_PUBKEYTYPE
94
102
  'Using non-compressed keys in segwit'
103
+ when SCRIPT_ERR_SCHNORR_SIG_SIZE
104
+ 'Invalid Schnorr signature size'
105
+ when SCRIPT_ERR_SCHNORR_SIG_HASHTYPE
106
+ 'Invalid Schnorr signature hash type'
107
+ when SCRIPT_ERR_SCHNORR_SIG
108
+ 'Invalid Schnorr signature'
109
+ when SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE
110
+ 'Invalid Taproot control block size'
111
+ when SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT
112
+ 'Too much signature validation relative to witness weight'
113
+ when SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG
114
+ 'OP_CHECKMULTISIG(VERIFY) is not available in tapscript'
115
+ when SCRIPT_ERR_TAPSCRIPT_MINIMALIF
116
+ 'OP_IF/NOTIF argument must be minimal in tapscript'
95
117
  when SCRIPT_ERR_OP_CODESEPARATOR
96
- 'Using OP_CODESEPARATOR in non-witness scrip'
118
+ 'Using OP_CODESEPARATOR in non-witness script'
97
119
  when SCRIPT_ERR_SIG_FINDANDDELETE
98
120
  'Signature is found in scriptCode'
99
121
  when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
@@ -103,6 +125,10 @@ module Bitcoin
103
125
  end
104
126
  end
105
127
 
128
+ def ok?
129
+ code == Bitcoin::SCRIPT_ERR_OK
130
+ end
131
+
106
132
  def self.name_to_code(name)
107
133
  NAME_MAP[name]
108
134
  end
@@ -13,7 +13,6 @@ module Bitcoin
13
13
 
14
14
  DISABLE_OPCODES = [OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_DIV, OP_MUL, OP_MOD, OP_LSHIFT, OP_RSHIFT]
15
15
 
16
-
17
16
  # syntax sugar for simple evaluation for script.
18
17
  # @param [Bitcoin::Script] script_sig a scriptSig.
19
18
  # @param [Bitcoin::Script] script_pubkey a scriptPubkey.
@@ -55,7 +54,7 @@ module Bitcoin
55
54
  return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
56
55
  version, program = script_pubkey.witness_data
57
56
  stack_copy = stack.dup
58
- return false unless verify_witness_program(witness, version, program)
57
+ return false unless verify_witness_program(witness, version, program, false)
59
58
  end
60
59
 
61
60
  # Additional validation for spend-to-script-hash transactions
@@ -63,11 +62,7 @@ module Bitcoin
63
62
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
64
63
  @stack = stack_copy
65
64
  raise 'stack cannot be empty.' if stack.empty?
66
- begin
67
- redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
68
- rescue Exception => e
69
- return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
70
- end
65
+ redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
71
66
  return false unless eval_script(redeem_script, :base)
72
67
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
73
68
 
@@ -78,7 +73,7 @@ module Bitcoin
78
73
  return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
79
74
 
80
75
  version, program = redeem_script.witness_data
81
- return false unless verify_witness_program(witness, version, program)
76
+ return false unless verify_witness_program(witness, version, program, true)
82
77
  end
83
78
  end
84
79
 
@@ -105,21 +100,71 @@ module Bitcoin
105
100
  false
106
101
  end
107
102
 
108
- def verify_witness_program(witness, version, program)
103
+ def verify_witness_program(witness, version, program, is_p2sh)
104
+ @stack = witness.stack.map(&:bth)
105
+ need_evaluate = false
106
+ sig_version = nil
107
+ opts = {}
109
108
  if version == 0
110
- if program.bytesize == 32
111
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if witness.stack.size == 0
112
- script_pubkey = Bitcoin::Script.parse_from_payload(witness.stack.last)
113
- @stack = witness.stack[0..-2].map{|w|w.bth}
109
+ need_evaluate = true
110
+ sig_version = :witness_v0
111
+ if program.bytesize == WITNESS_V0_SCRIPTHASH_SIZE # BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
112
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if stack.size == 0
113
+ script_pubkey = Bitcoin::Script.parse_from_payload(stack.pop.htb)
114
114
  script_hash = Bitcoin.sha256(script_pubkey.to_payload)
115
115
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program
116
- elsif program.bytesize == 20
117
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless witness.stack.size == 2
116
+ elsif program.bytesize == WITNESS_V0_KEYHASH_SIZE # BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
117
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless stack.size == 2
118
118
  script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
119
- @stack = witness.stack.map{|w|w.bth}
120
119
  else
121
120
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
122
121
  end
122
+ elsif version == 1 && program.bytesize == WITNESS_V1_TAPROOT_SIZE && !is_p2sh
123
+ # BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
124
+ return true unless flag?(SCRIPT_VERIFY_TAPROOT)
125
+ if stack.size == 0
126
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY)
127
+ elsif stack.size >= 2 && !stack.last.empty? && stack.last[0..1].to_i(16) == ANNEX_TAG
128
+ opts[:annex] = stack.pop.htb
129
+ end
130
+ if stack.size == 1 # Key path spending (stack size is 1 after removing optional annex)
131
+ result = checker.check_schnorr_sig(stack.last, program.bth, :taproot, opts)
132
+ return checker.has_error? ? set_error(checker.error_code) : result
133
+ else
134
+ sig_version = :tapscript
135
+ # Script path spending (stack size is >1 after removing optional annex)
136
+ control = stack.pop.htb
137
+ script_payload = stack.pop.htb
138
+ if control.bytesize < TAPROOT_CONTROL_BASE_SIZE || control.bytesize > TAPROOT_CONTROL_MAX_SIZE ||
139
+ (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
140
+ return set_error(SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE)
141
+ end
142
+ leaf_ver = control[0].bti & TAPROOT_LEAF_MASK
143
+ opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
144
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless valid_taproot_commitment?(control, program, opts[:leaf_hash])
145
+ if (control[0].bti & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT
146
+ opts[:weight_left] = witness.to_payload.bytesize + VALIDATION_WEIGHT_OFFSET
147
+ script_pubkey = Bitcoin::Script.parse_from_payload(script_payload)
148
+ script_pubkey.chunks.each do |c|
149
+ next if c.empty?
150
+ # Note how this condition would not be reached if an unknown OP_SUCCESSx was found
151
+ if c.pushdata?
152
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
153
+ elsif Opcodes.op_success?(c.ord)
154
+ # OP_SUCCESSx processing overrides everything, including stack element size limits
155
+ return set_error(SCRIPT_ERR_DISCOURAGE_OP_SUCCESS) if flag?(SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS)
156
+ return true
157
+ else
158
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless Opcodes.defined?(c.ord, true)
159
+ end
160
+ end
161
+ return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size > MAX_STACK_SIZE
162
+ need_evaluate = true
163
+ end
164
+
165
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
166
+ return true unless need_evaluate
167
+ end
123
168
  elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
124
169
  return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
125
170
  else
@@ -130,19 +175,22 @@ module Bitcoin
130
175
  return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
131
176
  end
132
177
 
133
- return false unless eval_script(script_pubkey, :witness_v0)
178
+ return false unless eval_script(script_pubkey, sig_version, opts: opts)
134
179
 
135
180
  return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
136
181
  return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
137
182
  true
138
183
  end
139
184
 
140
- def eval_script(script, sig_version)
141
- return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
185
+ def eval_script(script, sig_version, opts: {})
186
+ # sig_version cannot be TAPROOT here, as it admits no script execution.
187
+ raise ArgumentError, "Invalid sig version was specified: #{sig_version}" unless [:base, :witness_v0, :tapscript].include?(sig_version)
188
+ return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE && [:base, :witness_v0].include?(sig_version)
142
189
  begin
143
190
  flow_stack = []
144
191
  alt_stack = []
145
- last_code_separator_index = 0
192
+ opts[:last_code_separator_pos] = 0xffffffff
193
+ opts[:begincodehash] = 0
146
194
  op_count = 0
147
195
 
148
196
  script.chunks.each_with_index do |c, index|
@@ -156,10 +204,10 @@ module Bitcoin
156
204
  if require_minimal && !minimal_push?(c.pushed_data, opcode)
157
205
  return set_error(SCRIPT_ERR_MINIMALDATA)
158
206
  end
159
- return set_error(SCRIPT_ERR_BAD_OPCODE) unless verify_pushdata_length(c)
207
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
160
208
  stack << c.pushed_data.bth
161
209
  else
162
- if opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
210
+ if [:base, :witness_v0].include?(sig_version) && opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
163
211
  return set_error(SCRIPT_ERR_OP_COUNT)
164
212
  end
165
213
  return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
@@ -208,9 +256,11 @@ module Bitcoin
208
256
  if need_exec
209
257
  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
210
258
  value = pop_string.htb
211
- if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
212
- if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
213
- return set_error(SCRIPT_ERR_MINIMALIF)
259
+ if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF) || sig_version == :tapscript
260
+ # Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF.
261
+ # Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
262
+ if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack1('C') != 1)
263
+ return set_error(sig_version == :witness_v0 ? SCRIPT_ERR_MINIMALIF : SCRIPT_ERR_TAPSCRIPT_MINIMALIF)
214
264
  end
215
265
  end
216
266
  result = cast_to_bool(value)
@@ -397,26 +447,14 @@ module Bitcoin
397
447
  a, b = pop_int(2)
398
448
  push_int(a == b ? 0 : 1)
399
449
  when OP_CODESEPARATOR
400
- last_code_separator_index = index + 1
450
+ opts[:begincodehash] = index + 1
451
+ opts[:last_code_separator_pos] = index
401
452
  when OP_CHECKSIG, OP_CHECKSIGVERIFY
402
453
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
403
454
  sig, pubkey = pop_string(2)
404
455
 
405
- subscript = script.subscript(last_code_separator_index..-1)
406
- if sig_version == :base
407
- tmp = subscript.find_and_delete(Script.new << sig)
408
- return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
409
- subscript = tmp
410
- end
411
-
412
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
413
-
414
- success = checker.check_sig(sig, pubkey, subscript, sig_version)
415
-
416
- # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
417
- if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
418
- return set_error(SCRIPT_ERR_SIG_NULLFAIL)
419
- end
456
+ success = valid_sig?(sig, pubkey, sig_version, script, opts)
457
+ return false if error && !error.ok?
420
458
 
421
459
  push_int(success ? 1 : 0)
422
460
 
@@ -427,6 +465,19 @@ module Bitcoin
427
465
  return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
428
466
  end
429
467
  end
468
+ when OP_CHECKSIGADD
469
+ # OP_CHECKSIGADD is only available in Tapscript
470
+ return set_error(SCRIPT_ERR_BAD_OPCODE) if [:base, :witness_v0].include?(sig_version)
471
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
472
+
473
+ pubkey = pop_string
474
+ num = pop_int
475
+ sig = pop_string
476
+
477
+ success = valid_sig?(sig, pubkey, sig_version, script, opts) && !sig.empty?
478
+ return false if error && !error.ok?
479
+
480
+ push_int(success ? num + 1 : num)
430
481
  when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
431
482
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
432
483
  pubkey_count = pop_int
@@ -451,7 +502,7 @@ module Bitcoin
451
502
  sigs = pop_string(sig_count)
452
503
  sigs = [sigs] if sigs.is_a?(String)
453
504
 
454
- subscript = script.subscript(last_code_separator_index..-1)
505
+ subscript = script.subscript(opts[:begincodehash]..-1)
455
506
 
456
507
  if sig_version == :base
457
508
  sigs.each do |sig|
@@ -466,7 +517,7 @@ module Bitcoin
466
517
  sig = sigs.pop
467
518
  pubkey = pubkeys.pop
468
519
  return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
469
- ok = checker.check_sig(sig, pubkey, subscript, sig_version)
520
+ ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
470
521
  if ok
471
522
  sig_count -= 1
472
523
  else
@@ -513,7 +564,6 @@ module Bitcoin
513
564
  end
514
565
  rescue Exception => e
515
566
  puts e
516
- puts e.backtrace
517
567
  return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
518
568
  end
519
569
 
@@ -572,6 +622,7 @@ module Bitcoin
572
622
 
573
623
  # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
574
624
  def cast_to_bool(v)
625
+ return false if v.empty?
575
626
  case v
576
627
  when Numeric
577
628
  return v != 0
@@ -641,24 +692,72 @@ module Bitcoin
641
692
  true
642
693
  end
643
694
 
644
- def verify_pushdata_length(chunk)
645
- buf = StringIO.new(chunk)
646
- opcode = buf.read(1).ord
647
- offset = 1
648
- len = case opcode
649
- when OP_PUSHDATA1
650
- offset += 1
651
- buf.read(1).unpack('C').first
652
- when OP_PUSHDATA2
653
- offset += 2
654
- buf.read(2).unpack('v').first
655
- when OP_PUSHDATA4
656
- offset += 4
657
- buf.read(4).unpack('V').first
658
- else
659
- opcode
660
- end
661
- chunk.bytesize == len + offset
695
+ # check whether valid taproot commitment.
696
+ def valid_taproot_commitment?(control, program, leaf_hash)
697
+ begin
698
+ path_len = (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
699
+ xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
700
+ p = Bitcoin::Key.new(pubkey: "02#{xonly_pubkey.bth}", key_type: Key::TYPES[:compressed])
701
+ k = leaf_hash
702
+ path_len.times do |i|
703
+ pos = (TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i)
704
+ e = control[pos...(pos + TAPROOT_CONTROL_NODE_SIZE)]
705
+ k = Bitcoin.tagged_hash('TapBranch', k.bth < e.bth ? k + e : e + k)
706
+ end
707
+ t = Bitcoin.tagged_hash('TapTweak', xonly_pubkey + k)
708
+ key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
709
+ q = key.to_point + p.to_point
710
+ return q.x == program.bti && (control[0].bti & 1) == (q.y % 2)
711
+ rescue ArgumentError
712
+ return false
713
+ end
714
+ end
715
+
716
+ def valid_sig?(sig, pubkey, sig_version, script, opts)
717
+ case sig_version
718
+ when :base, :witness_v0
719
+ return eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
720
+ when :tapscript
721
+ return eval_checksig_tapscript(sig, pubkey, opts)
722
+ end
723
+ false
724
+ end
725
+
726
+ def eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
727
+ subscript = script.subscript(opts[:begincodehash]..-1)
728
+ if sig_version == :base
729
+ tmp = subscript.find_and_delete(Script.new << sig)
730
+ return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
731
+ subscript = tmp
732
+ end
733
+ return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
734
+ success = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
735
+ # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
736
+ return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
737
+ success
738
+ end
739
+
740
+ def eval_checksig_tapscript(sig, pubkey, opts)
741
+ success = !sig.empty?
742
+ if success
743
+ # Implement the sigops/witnesssize ratio test.
744
+ opts[:weight_left] -= VALIDATION_WEIGHT_PER_SIGOP_PASSED
745
+ return set_error(SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT) if opts[:weight_left] < 0
746
+ end
747
+
748
+ pubkey_size = pubkey.htb.bytesize
749
+ if pubkey_size == 0
750
+ return set_error(SCRIPT_ERR_PUBKEYTYPE)
751
+ elsif pubkey_size == 32
752
+ if success
753
+ result = checker.check_schnorr_sig(sig, pubkey, :tapscript, opts)
754
+ return checker.has_error? ? set_error(checker.error_code) : set_error(SCRIPT_ERR_SCHNORR_SIG) unless result
755
+ end
756
+ else
757
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE)
758
+ end
759
+ success
760
+ # return true
662
761
  end
663
762
 
664
763
  end