bitcoinrb 0.3.1 → 0.7.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +6 -3
  4. data/README.md +17 -6
  5. data/bitcoinrb.gemspec +9 -8
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +35 -19
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_filter.rb +14 -0
  10. data/lib/bitcoin/block_header.rb +2 -0
  11. data/lib/bitcoin/chain_params.rb +9 -8
  12. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  13. data/lib/bitcoin/chainparams/signet.yml +39 -0
  14. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  15. data/lib/bitcoin/constants.rb +45 -12
  16. data/lib/bitcoin/descriptor.rb +1 -1
  17. data/lib/bitcoin/errors.rb +19 -0
  18. data/lib/bitcoin/ext.rb +5 -0
  19. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  20. data/lib/bitcoin/ext/json_parser.rb +46 -0
  21. data/lib/bitcoin/ext_key.rb +50 -19
  22. data/lib/bitcoin/key.rb +46 -29
  23. data/lib/bitcoin/key_path.rb +12 -5
  24. data/lib/bitcoin/message.rb +79 -0
  25. data/lib/bitcoin/message/addr_v2.rb +34 -0
  26. data/lib/bitcoin/message/base.rb +17 -0
  27. data/lib/bitcoin/message/cf_parser.rb +16 -0
  28. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  29. data/lib/bitcoin/message/cfheaders.rb +40 -0
  30. data/lib/bitcoin/message/cfilter.rb +35 -0
  31. data/lib/bitcoin/message/fee_filter.rb +1 -1
  32. data/lib/bitcoin/message/filter_load.rb +3 -3
  33. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  34. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  35. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  36. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  37. data/lib/bitcoin/message/inventory.rb +1 -1
  38. data/lib/bitcoin/message/merkle_block.rb +1 -1
  39. data/lib/bitcoin/message/network_addr.rb +141 -18
  40. data/lib/bitcoin/message/ping.rb +1 -1
  41. data/lib/bitcoin/message/pong.rb +1 -1
  42. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  43. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  44. data/lib/bitcoin/message/version.rb +7 -0
  45. data/lib/bitcoin/mnemonic.rb +7 -7
  46. data/lib/bitcoin/network/peer.rb +9 -4
  47. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  48. data/lib/bitcoin/node/cli.rb +14 -10
  49. data/lib/bitcoin/node/configuration.rb +3 -1
  50. data/lib/bitcoin/node/spv.rb +9 -1
  51. data/lib/bitcoin/opcodes.rb +14 -1
  52. data/lib/bitcoin/out_point.rb +7 -0
  53. data/lib/bitcoin/payment_code.rb +92 -0
  54. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  55. data/lib/bitcoin/psbt/input.rb +8 -17
  56. data/lib/bitcoin/psbt/output.rb +1 -1
  57. data/lib/bitcoin/psbt/tx.rb +11 -16
  58. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  59. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  60. data/lib/bitcoin/script/script.rb +68 -28
  61. data/lib/bitcoin/script/script_error.rb +27 -1
  62. data/lib/bitcoin/script/script_interpreter.rb +164 -67
  63. data/lib/bitcoin/script/tx_checker.rb +64 -14
  64. data/lib/bitcoin/secp256k1.rb +1 -0
  65. data/lib/bitcoin/secp256k1/native.rb +138 -25
  66. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  67. data/lib/bitcoin/secp256k1/ruby.rb +82 -54
  68. data/lib/bitcoin/sighash_generator.rb +156 -0
  69. data/lib/bitcoin/store.rb +2 -1
  70. data/lib/bitcoin/store/chain_entry.rb +1 -0
  71. data/lib/bitcoin/store/db/level_db.rb +2 -2
  72. data/lib/bitcoin/store/utxo_db.rb +226 -0
  73. data/lib/bitcoin/tx.rb +17 -88
  74. data/lib/bitcoin/tx_in.rb +4 -5
  75. data/lib/bitcoin/tx_out.rb +2 -3
  76. data/lib/bitcoin/util.rb +34 -6
  77. data/lib/bitcoin/version.rb +1 -1
  78. data/lib/bitcoin/wallet.rb +1 -0
  79. data/lib/bitcoin/wallet/account.rb +2 -1
  80. data/lib/bitcoin/wallet/base.rb +3 -3
  81. data/lib/bitcoin/wallet/db.rb +1 -1
  82. data/lib/bitcoin/wallet/master_key.rb +1 -0
  83. data/lib/bitcoin/wallet/utxo.rb +37 -0
  84. metadata +66 -32
@@ -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,20 +54,15 @@ 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
62
61
  if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
63
62
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
64
- tmp = stack
65
63
  @stack = stack_copy
66
64
  raise 'stack cannot be empty.' if stack.empty?
67
- begin
68
- redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
69
- rescue Exception => e
70
- return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
71
- end
65
+ redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
72
66
  return false unless eval_script(redeem_script, :base)
73
67
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
74
68
 
@@ -76,10 +70,10 @@ module Bitcoin
76
70
  if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
77
71
  had_witness = true
78
72
  # 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 == (Bitcoin::Script.new << redeem_script.to_payload.bth)
73
+ return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
80
74
 
81
75
  version, program = redeem_script.witness_data
82
- return false unless verify_witness_program(witness, version, program)
76
+ return false unless verify_witness_program(witness, version, program, true)
83
77
  end
84
78
  end
85
79
 
@@ -106,21 +100,71 @@ module Bitcoin
106
100
  false
107
101
  end
108
102
 
109
- 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 = {}
110
108
  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 = Bitcoin::Script.parse_from_payload(witness.stack.last)
114
- @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)
115
114
  script_hash = Bitcoin.sha256(script_pubkey.to_payload)
116
115
  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
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
119
118
  script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
120
- @stack = witness.stack.map{|w|w.bth}
121
119
  else
122
120
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
123
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
124
168
  elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
125
169
  return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
126
170
  else
@@ -131,23 +175,26 @@ module Bitcoin
131
175
  return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
132
176
  end
133
177
 
134
- return false unless eval_script(script_pubkey, :witness_v0)
178
+ return false unless eval_script(script_pubkey, sig_version, opts: opts)
135
179
 
136
- return set_error(SCRIPT_ERR_EVAL_FALSE) unless stack.size == 1
180
+ return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
137
181
  return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
138
182
  true
139
183
  end
140
184
 
141
- def eval_script(script, sig_version)
142
- 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)
143
189
  begin
144
190
  flow_stack = []
145
191
  alt_stack = []
146
- last_code_separator_index = 0
192
+ opts[:last_code_separator_pos] = 0xffffffff
193
+ opts[:begincodehash] = 0
147
194
  op_count = 0
148
195
 
149
196
  script.chunks.each_with_index do |c, index|
150
- need_exec = !flow_stack.include?(false)
197
+ need_exec = flow_stack.rindex(false).nil?
151
198
 
152
199
  return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE
153
200
 
@@ -157,10 +204,10 @@ module Bitcoin
157
204
  if require_minimal && !minimal_push?(c.pushed_data, opcode)
158
205
  return set_error(SCRIPT_ERR_MINIMALDATA)
159
206
  end
160
- 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?
161
208
  stack << c.pushed_data.bth
162
209
  else
163
- 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
164
211
  return set_error(SCRIPT_ERR_OP_COUNT)
165
212
  end
166
213
  return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
@@ -209,9 +256,11 @@ module Bitcoin
209
256
  if need_exec
210
257
  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
211
258
  value = pop_string.htb
212
- if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
213
- if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
214
- 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)
215
264
  end
216
265
  end
217
266
  result = cast_to_bool(value)
@@ -398,26 +447,14 @@ module Bitcoin
398
447
  a, b = pop_int(2)
399
448
  push_int(a == b ? 0 : 1)
400
449
  when OP_CODESEPARATOR
401
- last_code_separator_index = index + 1
450
+ opts[:begincodehash] = index + 1
451
+ opts[:last_code_separator_pos] = index
402
452
  when OP_CHECKSIG, OP_CHECKSIGVERIFY
403
453
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
404
454
  sig, pubkey = pop_string(2)
405
455
 
406
- subscript = script.subscript(last_code_separator_index..-1)
407
- if sig_version == :base
408
- tmp = subscript.find_and_delete(Script.new << sig)
409
- return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
410
- subscript = tmp
411
- end
412
-
413
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
414
-
415
- success = checker.check_sig(sig, pubkey, subscript, sig_version)
416
-
417
- # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
418
- if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
419
- return set_error(SCRIPT_ERR_SIG_NULLFAIL)
420
- end
456
+ success = valid_sig?(sig, pubkey, sig_version, script, opts)
457
+ return false if error && !error.ok?
421
458
 
422
459
  push_int(success ? 1 : 0)
423
460
 
@@ -428,6 +465,19 @@ module Bitcoin
428
465
  return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
429
466
  end
430
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)
431
481
  when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
432
482
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
433
483
  pubkey_count = pop_int
@@ -452,7 +502,7 @@ module Bitcoin
452
502
  sigs = pop_string(sig_count)
453
503
  sigs = [sigs] if sigs.is_a?(String)
454
504
 
455
- subscript = script.subscript(last_code_separator_index..-1)
505
+ subscript = script.subscript(opts[:begincodehash]..-1)
456
506
 
457
507
  if sig_version == :base
458
508
  sigs.each do |sig|
@@ -467,7 +517,7 @@ module Bitcoin
467
517
  sig = sigs.pop
468
518
  pubkey = pubkeys.pop
469
519
  return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
470
- 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))
471
521
  if ok
472
522
  sig_count -= 1
473
523
  else
@@ -514,7 +564,6 @@ module Bitcoin
514
564
  end
515
565
  rescue Exception => e
516
566
  puts e
517
- puts e.backtrace
518
567
  return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
519
568
  end
520
569
 
@@ -573,6 +622,7 @@ module Bitcoin
573
622
 
574
623
  # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
575
624
  def cast_to_bool(v)
625
+ return false if v.empty?
576
626
  case v
577
627
  when Numeric
578
628
  return v != 0
@@ -609,7 +659,6 @@ module Bitcoin
609
659
  return false if sig.empty?
610
660
  s = sig.unpack('C*')
611
661
  hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
612
- hash_type &= (~(Bitcoin::SIGHASH_FORK_ID)) if Bitcoin.chain_params.fork_chain? # for fork coin.
613
662
  return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
614
663
  true
615
664
  end
@@ -643,24 +692,72 @@ module Bitcoin
643
692
  true
644
693
  end
645
694
 
646
- def verify_pushdata_length(chunk)
647
- buf = StringIO.new(chunk)
648
- opcode = buf.read(1).ord
649
- offset = 1
650
- len = case opcode
651
- when OP_PUSHDATA1
652
- offset += 1
653
- buf.read(1).unpack('C').first
654
- when OP_PUSHDATA2
655
- offset += 2
656
- buf.read(2).unpack('v').first
657
- when OP_PUSHDATA4
658
- offset += 4
659
- buf.read(4).unpack('V').first
660
- else
661
- opcode
662
- end
663
- 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
664
761
  end
665
762
 
666
763
  end
@@ -4,28 +4,67 @@ module Bitcoin
4
4
  attr_reader :tx
5
5
  attr_reader :input_index
6
6
  attr_reader :amount
7
+ attr_reader :prevouts
8
+ attr_accessor :error_code
7
9
 
8
- def initialize(tx: nil, amount: 0, input_index: nil)
10
+ def initialize(tx: nil, amount: 0, input_index: nil, prevouts: [])
9
11
  @tx = tx
10
- @amount = amount
11
12
  @input_index = input_index
13
+ @prevouts = prevouts
14
+ @amount = input_index && prevouts[input_index] ? prevouts[input_index].value : amount
12
15
  end
13
16
 
14
- # check signature
15
- # @param [String] script_sig
16
- # @param [String] pubkey
17
+ # check ecdsa signature
18
+ # @param [String] sig signature with hex format
19
+ # @param [String] pubkey with hex format.
17
20
  # @param [Bitcoin::Script] script_code
18
21
  # @param [Integer] sig_version
19
- def check_sig(script_sig, pubkey, script_code, sig_version)
20
- return false if script_sig.empty?
21
- script_sig = script_sig.htb
22
- hash_type = script_sig[-1].unpack('C').first
23
- sig = script_sig[0..-2]
24
- sighash = tx.sighash_for_input(input_index, script_code, hash_type: hash_type,
25
- amount: amount, sig_version: sig_version)
22
+ # @return [Boolean] verification result
23
+ def check_sig(sig, pubkey, script_code, sig_version, allow_hybrid: false)
24
+ return false if sig.empty?
25
+ sig = sig.htb
26
+ hash_type = sig[-1].unpack1('C')
27
+ sig = sig[0..-2]
28
+ sighash = tx.sighash_for_input(input_index, script_code, opts: {amount: amount}, hash_type: hash_type, sig_version: sig_version)
26
29
  key_type = pubkey.start_with?('02') || pubkey.start_with?('03') ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
27
- key = Key.new(pubkey: pubkey, key_type: key_type)
28
- key.verify(sig, sighash)
30
+ begin
31
+ key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
32
+ key.verify(sig, sighash)
33
+ rescue Exception
34
+ false
35
+ end
36
+ end
37
+
38
+ # check schnorr signature.
39
+ # @param [String] sig schnorr signature with hex format.
40
+ # @param [String] pubkey a public key with hex fromat.
41
+ # @param [Symbol] sig_version whether :taproot or :tapscript
42
+ # @return [Boolean] verification result
43
+ def check_schnorr_sig(sig, pubkey, sig_version, opts = {})
44
+ return false unless [:taproot, :tapscript].include?(sig_version)
45
+ return false if prevouts.size < input_index
46
+
47
+ sig = sig.htb
48
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_SIZE) unless [64, 65].include?(sig.bytesize)
49
+
50
+ hash_type = SIGHASH_TYPE[:default]
51
+ if sig.bytesize == 65
52
+ hash_type = sig[-1].unpack1('C')
53
+ sig = sig[0..-2]
54
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) if hash_type == SIGHASH_TYPE[:default] # hash type can not specify 0x00.
55
+ end
56
+
57
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) unless (hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))
58
+
59
+ opts[:prevouts] = prevouts
60
+
61
+ begin
62
+ sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version)
63
+ key = Key.new(pubkey: "02#{pubkey}", key_type: Key::TYPES[:compressed])
64
+ key.verify(sig, sighash, algo: :schnorr)
65
+ rescue ArgumentError
66
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
67
+ end
29
68
  end
30
69
 
31
70
  def check_locktime(locktime)
@@ -77,5 +116,16 @@ module Bitcoin
77
116
  sequence_masked <= tx_sequence_masked
78
117
  end
79
118
 
119
+ def has_error?
120
+ !@error_code.nil?
121
+ end
122
+
123
+ private
124
+
125
+ def set_error(code)
126
+ @error_code = code
127
+ false
128
+ end
129
+
80
130
  end
81
131
  end