bitcoinrb 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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