bitcoinrb 0.3.2 → 0.8.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +17 -6
  6. data/bitcoinrb.gemspec +9 -8
  7. data/exe/bitcoinrbd +5 -0
  8. data/lib/bitcoin.rb +37 -19
  9. data/lib/bitcoin/bip85_entropy.rb +111 -0
  10. data/lib/bitcoin/block_filter.rb +14 -0
  11. data/lib/bitcoin/block_header.rb +2 -0
  12. data/lib/bitcoin/chain_params.rb +9 -8
  13. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  14. data/lib/bitcoin/chainparams/signet.yml +39 -0
  15. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  16. data/lib/bitcoin/constants.rb +44 -10
  17. data/lib/bitcoin/descriptor.rb +1 -1
  18. data/lib/bitcoin/errors.rb +19 -0
  19. data/lib/bitcoin/ext.rb +6 -0
  20. data/lib/bitcoin/ext/array_ext.rb +22 -0
  21. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  22. data/lib/bitcoin/ext/json_parser.rb +46 -0
  23. data/lib/bitcoin/ext_key.rb +51 -20
  24. data/lib/bitcoin/key.rb +89 -30
  25. data/lib/bitcoin/key_path.rb +12 -5
  26. data/lib/bitcoin/message.rb +79 -0
  27. data/lib/bitcoin/message/addr_v2.rb +34 -0
  28. data/lib/bitcoin/message/base.rb +17 -0
  29. data/lib/bitcoin/message/cf_parser.rb +16 -0
  30. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  31. data/lib/bitcoin/message/cfheaders.rb +40 -0
  32. data/lib/bitcoin/message/cfilter.rb +35 -0
  33. data/lib/bitcoin/message/fee_filter.rb +1 -1
  34. data/lib/bitcoin/message/filter_load.rb +3 -3
  35. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  36. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  37. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  38. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  39. data/lib/bitcoin/message/inventory.rb +1 -1
  40. data/lib/bitcoin/message/merkle_block.rb +1 -1
  41. data/lib/bitcoin/message/network_addr.rb +141 -18
  42. data/lib/bitcoin/message/ping.rb +1 -1
  43. data/lib/bitcoin/message/pong.rb +1 -1
  44. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  45. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  46. data/lib/bitcoin/message/tx.rb +1 -1
  47. data/lib/bitcoin/message/version.rb +7 -0
  48. data/lib/bitcoin/message_sign.rb +47 -0
  49. data/lib/bitcoin/mnemonic.rb +7 -7
  50. data/lib/bitcoin/network/peer.rb +9 -4
  51. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  52. data/lib/bitcoin/node/cli.rb +14 -10
  53. data/lib/bitcoin/node/configuration.rb +3 -1
  54. data/lib/bitcoin/node/spv.rb +9 -1
  55. data/lib/bitcoin/opcodes.rb +14 -1
  56. data/lib/bitcoin/out_point.rb +2 -0
  57. data/lib/bitcoin/payment_code.rb +92 -0
  58. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  59. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  60. data/lib/bitcoin/psbt/input.rb +9 -18
  61. data/lib/bitcoin/psbt/output.rb +1 -1
  62. data/lib/bitcoin/psbt/tx.rb +12 -17
  63. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  64. data/lib/bitcoin/rpc/request_handler.rb +5 -5
  65. data/lib/bitcoin/script/script.rb +96 -39
  66. data/lib/bitcoin/script/script_error.rb +27 -1
  67. data/lib/bitcoin/script/script_interpreter.rb +166 -66
  68. data/lib/bitcoin/script/tx_checker.rb +62 -14
  69. data/lib/bitcoin/secp256k1.rb +1 -0
  70. data/lib/bitcoin/secp256k1/native.rb +184 -17
  71. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  72. data/lib/bitcoin/secp256k1/ruby.rb +112 -56
  73. data/lib/bitcoin/sighash_generator.rb +156 -0
  74. data/lib/bitcoin/store.rb +1 -0
  75. data/lib/bitcoin/store/chain_entry.rb +1 -0
  76. data/lib/bitcoin/store/utxo_db.rb +226 -0
  77. data/lib/bitcoin/taproot.rb +9 -0
  78. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  79. data/lib/bitcoin/taproot/simple_builder.rb +139 -0
  80. data/lib/bitcoin/tx.rb +34 -104
  81. data/lib/bitcoin/tx_in.rb +4 -5
  82. data/lib/bitcoin/tx_out.rb +2 -3
  83. data/lib/bitcoin/util.rb +22 -6
  84. data/lib/bitcoin/version.rb +1 -1
  85. data/lib/bitcoin/wallet.rb +1 -0
  86. data/lib/bitcoin/wallet/account.rb +2 -1
  87. data/lib/bitcoin/wallet/base.rb +2 -2
  88. data/lib/bitcoin/wallet/master_key.rb +1 -0
  89. data/lib/bitcoin/wallet/utxo.rb +37 -0
  90. metadata +86 -32
  91. data/.travis.yml +0 -11
@@ -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
@@ -3,6 +3,7 @@ module Bitcoin
3
3
  class ScriptInterpreter
4
4
 
5
5
  include Bitcoin::Opcodes
6
+ using Bitcoin::Ext::ArrayExt
6
7
 
7
8
  attr_reader :stack
8
9
  attr_reader :debug
@@ -13,7 +14,6 @@ module Bitcoin
13
14
 
14
15
  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
16
 
16
-
17
17
  # syntax sugar for simple evaluation for script.
18
18
  # @param [Bitcoin::Script] script_sig a scriptSig.
19
19
  # @param [Bitcoin::Script] script_pubkey a scriptPubkey.
@@ -55,20 +55,17 @@ module Bitcoin
55
55
  return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
56
56
  version, program = script_pubkey.witness_data
57
57
  stack_copy = stack.dup
58
- return false unless verify_witness_program(witness, version, program)
58
+ return false unless verify_witness_program(witness, version, program, false)
59
+ # Bypass the cleanstack check at the end. The actual stack is obviously not clean for witness programs.
60
+ stack.resize!(1, Script.encode_number(0))
59
61
  end
60
62
 
61
63
  # Additional validation for spend-to-script-hash transactions
62
64
  if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
63
65
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
64
- tmp = stack
65
66
  @stack = stack_copy
66
67
  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
68
+ redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
72
69
  return false unless eval_script(redeem_script, :base)
73
70
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
74
71
 
@@ -76,10 +73,10 @@ module Bitcoin
76
73
  if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
77
74
  had_witness = true
78
75
  # 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)
76
+ return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
80
77
 
81
78
  version, program = redeem_script.witness_data
82
- return false unless verify_witness_program(witness, version, program)
79
+ return false unless verify_witness_program(witness, version, program, true)
83
80
  end
84
81
  end
85
82
 
@@ -106,21 +103,71 @@ module Bitcoin
106
103
  false
107
104
  end
108
105
 
109
- def verify_witness_program(witness, version, program)
106
+ def verify_witness_program(witness, version, program, is_p2sh)
107
+ @stack = witness.stack.map(&:bth)
108
+ need_evaluate = false
109
+ sig_version = nil
110
+ opts = {}
110
111
  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}
112
+ need_evaluate = true
113
+ sig_version = :witness_v0
114
+ if program.bytesize == WITNESS_V0_SCRIPTHASH_SIZE # BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
115
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if stack.size == 0
116
+ script_pubkey = Bitcoin::Script.parse_from_payload(stack.pop.htb)
115
117
  script_hash = Bitcoin.sha256(script_pubkey.to_payload)
116
118
  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
+ elsif program.bytesize == WITNESS_V0_KEYHASH_SIZE # BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
120
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless stack.size == 2
119
121
  script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
120
- @stack = witness.stack.map{|w|w.bth}
121
122
  else
122
123
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
123
124
  end
125
+ elsif version == 1 && program.bytesize == WITNESS_V1_TAPROOT_SIZE && !is_p2sh
126
+ # BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
127
+ return true unless flag?(SCRIPT_VERIFY_TAPROOT)
128
+ if stack.size == 0
129
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY)
130
+ elsif stack.size >= 2 && !stack.last.empty? && stack.last[0..1].to_i(16) == ANNEX_TAG
131
+ opts[:annex] = stack.pop.htb
132
+ end
133
+ if stack.size == 1 # Key path spending (stack size is 1 after removing optional annex)
134
+ result = checker.check_schnorr_sig(stack.last, program.bth, :taproot, opts)
135
+ return checker.has_error? ? set_error(checker.error_code) : result
136
+ else
137
+ sig_version = :tapscript
138
+ # Script path spending (stack size is >1 after removing optional annex)
139
+ control = stack.pop.htb
140
+ script_payload = stack.pop.htb
141
+ if control.bytesize < TAPROOT_CONTROL_BASE_SIZE || control.bytesize > TAPROOT_CONTROL_MAX_SIZE ||
142
+ (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
143
+ return set_error(SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE)
144
+ end
145
+ leaf_ver = control[0].bti & TAPROOT_LEAF_MASK
146
+ opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
147
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless valid_taproot_commitment?(control, program, opts[:leaf_hash])
148
+ if (control[0].bti & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT
149
+ opts[:weight_left] = witness.to_payload.bytesize + VALIDATION_WEIGHT_OFFSET
150
+ script_pubkey = Bitcoin::Script.parse_from_payload(script_payload)
151
+ script_pubkey.chunks.each do |c|
152
+ next if c.empty?
153
+ # Note how this condition would not be reached if an unknown OP_SUCCESSx was found
154
+ if c.pushdata?
155
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
156
+ elsif Opcodes.op_success?(c.ord)
157
+ # OP_SUCCESSx processing overrides everything, including stack element size limits
158
+ return set_error(SCRIPT_ERR_DISCOURAGE_OP_SUCCESS) if flag?(SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS)
159
+ return true
160
+ else
161
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless Opcodes.defined?(c.ord, true)
162
+ end
163
+ end
164
+ return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size > MAX_STACK_SIZE
165
+ need_evaluate = true
166
+ end
167
+
168
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
169
+ return true unless need_evaluate
170
+ end
124
171
  elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
125
172
  return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
126
173
  else
@@ -131,23 +178,26 @@ module Bitcoin
131
178
  return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
132
179
  end
133
180
 
134
- return false unless eval_script(script_pubkey, :witness_v0)
181
+ return false unless eval_script(script_pubkey, sig_version, opts: opts)
135
182
 
136
183
  return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
137
184
  return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
138
185
  true
139
186
  end
140
187
 
141
- def eval_script(script, sig_version)
142
- return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
188
+ def eval_script(script, sig_version, opts: {})
189
+ # sig_version cannot be TAPROOT here, as it admits no script execution.
190
+ raise ArgumentError, "Invalid sig version was specified: #{sig_version}" unless [:base, :witness_v0, :tapscript].include?(sig_version)
191
+ return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE && [:base, :witness_v0].include?(sig_version)
143
192
  begin
144
193
  flow_stack = []
145
194
  alt_stack = []
146
- last_code_separator_index = 0
195
+ opts[:last_code_separator_pos] = 0xffffffff
196
+ opts[:begincodehash] = 0
147
197
  op_count = 0
148
198
 
149
199
  script.chunks.each_with_index do |c, index|
150
- need_exec = !flow_stack.include?(false)
200
+ need_exec = flow_stack.rindex(false).nil?
151
201
 
152
202
  return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE
153
203
 
@@ -157,10 +207,10 @@ module Bitcoin
157
207
  if require_minimal && !minimal_push?(c.pushed_data, opcode)
158
208
  return set_error(SCRIPT_ERR_MINIMALDATA)
159
209
  end
160
- return set_error(SCRIPT_ERR_BAD_OPCODE) unless verify_pushdata_length(c)
210
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
161
211
  stack << c.pushed_data.bth
162
212
  else
163
- if opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
213
+ if [:base, :witness_v0].include?(sig_version) && opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
164
214
  return set_error(SCRIPT_ERR_OP_COUNT)
165
215
  end
166
216
  return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
@@ -209,9 +259,11 @@ module Bitcoin
209
259
  if need_exec
210
260
  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
211
261
  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)
262
+ if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF) || sig_version == :tapscript
263
+ # Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF.
264
+ # Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
265
+ if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack1('C') != 1)
266
+ return set_error(sig_version == :witness_v0 ? SCRIPT_ERR_MINIMALIF : SCRIPT_ERR_TAPSCRIPT_MINIMALIF)
215
267
  end
216
268
  end
217
269
  result = cast_to_bool(value)
@@ -398,26 +450,14 @@ module Bitcoin
398
450
  a, b = pop_int(2)
399
451
  push_int(a == b ? 0 : 1)
400
452
  when OP_CODESEPARATOR
401
- last_code_separator_index = index + 1
453
+ opts[:begincodehash] = index + 1
454
+ opts[:last_code_separator_pos] = index
402
455
  when OP_CHECKSIG, OP_CHECKSIGVERIFY
403
456
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
404
457
  sig, pubkey = pop_string(2)
405
458
 
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
459
+ success = valid_sig?(sig, pubkey, sig_version, script, opts)
460
+ return false if error && !error.ok?
421
461
 
422
462
  push_int(success ? 1 : 0)
423
463
 
@@ -428,6 +468,19 @@ module Bitcoin
428
468
  return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
429
469
  end
430
470
  end
471
+ when OP_CHECKSIGADD
472
+ # OP_CHECKSIGADD is only available in Tapscript
473
+ return set_error(SCRIPT_ERR_BAD_OPCODE) if [:base, :witness_v0].include?(sig_version)
474
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
475
+
476
+ pubkey = pop_string
477
+ num = pop_int
478
+ sig = pop_string
479
+
480
+ success = valid_sig?(sig, pubkey, sig_version, script, opts) && !sig.empty?
481
+ return false if error && !error.ok?
482
+
483
+ push_int(success ? num + 1 : num)
431
484
  when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
432
485
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
433
486
  pubkey_count = pop_int
@@ -452,7 +505,7 @@ module Bitcoin
452
505
  sigs = pop_string(sig_count)
453
506
  sigs = [sigs] if sigs.is_a?(String)
454
507
 
455
- subscript = script.subscript(last_code_separator_index..-1)
508
+ subscript = script.subscript(opts[:begincodehash]..-1)
456
509
 
457
510
  if sig_version == :base
458
511
  sigs.each do |sig|
@@ -467,7 +520,7 @@ module Bitcoin
467
520
  sig = sigs.pop
468
521
  pubkey = pubkeys.pop
469
522
  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)
523
+ ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
471
524
  if ok
472
525
  sig_count -= 1
473
526
  else
@@ -514,7 +567,6 @@ module Bitcoin
514
567
  end
515
568
  rescue Exception => e
516
569
  puts e
517
- puts e.backtrace
518
570
  return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
519
571
  end
520
572
 
@@ -573,6 +625,7 @@ module Bitcoin
573
625
 
574
626
  # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
575
627
  def cast_to_bool(v)
628
+ return false if v.empty?
576
629
  case v
577
630
  when Numeric
578
631
  return v != 0
@@ -609,7 +662,6 @@ module Bitcoin
609
662
  return false if sig.empty?
610
663
  s = sig.unpack('C*')
611
664
  hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
612
- hash_type &= (~(Bitcoin::SIGHASH_FORK_ID)) if Bitcoin.chain_params.fork_chain? # for fork coin.
613
665
  return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
614
666
  true
615
667
  end
@@ -643,24 +695,72 @@ module Bitcoin
643
695
  true
644
696
  end
645
697
 
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
698
+ # check whether valid taproot commitment.
699
+ def valid_taproot_commitment?(control, program, leaf_hash)
700
+ begin
701
+ path_len = (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
702
+ xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
703
+ p = Bitcoin::Key.from_xonly_pubkey(xonly_pubkey.bth)
704
+ k = leaf_hash
705
+ path_len.times do |i|
706
+ pos = (TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i)
707
+ e = control[pos...(pos + TAPROOT_CONTROL_NODE_SIZE)]
708
+ k = Bitcoin.tagged_hash('TapBranch', k.bth < e.bth ? k + e : e + k)
709
+ end
710
+ t = Bitcoin.tagged_hash('TapTweak', xonly_pubkey + k)
711
+ key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
712
+ q = key.to_point + p.to_point
713
+ return q.x == program.bti && (control[0].bti & 1) == (q.y % 2)
714
+ rescue ArgumentError
715
+ return false
716
+ end
717
+ end
718
+
719
+ def valid_sig?(sig, pubkey, sig_version, script, opts)
720
+ case sig_version
721
+ when :base, :witness_v0
722
+ return eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
723
+ when :tapscript
724
+ return eval_checksig_tapscript(sig, pubkey, opts)
725
+ end
726
+ false
727
+ end
728
+
729
+ def eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
730
+ subscript = script.subscript(opts[:begincodehash]..-1)
731
+ if sig_version == :base
732
+ tmp = subscript.find_and_delete(Script.new << sig)
733
+ return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
734
+ subscript = tmp
735
+ end
736
+ return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
737
+ success = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
738
+ # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
739
+ return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
740
+ success
741
+ end
742
+
743
+ def eval_checksig_tapscript(sig, pubkey, opts)
744
+ success = !sig.empty?
745
+ if success
746
+ # Implement the sigops/witnesssize ratio test.
747
+ opts[:weight_left] -= VALIDATION_WEIGHT_PER_SIGOP_PASSED
748
+ return set_error(SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT) if opts[:weight_left] < 0
749
+ end
750
+
751
+ pubkey_size = pubkey.htb.bytesize
752
+ if pubkey_size == 0
753
+ return set_error(SCRIPT_ERR_PUBKEYTYPE)
754
+ elsif pubkey_size == 32
755
+ if success
756
+ result = checker.check_schnorr_sig(sig, pubkey, :tapscript, opts)
757
+ return checker.has_error? ? set_error(checker.error_code) : set_error(SCRIPT_ERR_SCHNORR_SIG) unless result
758
+ end
759
+ else
760
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE)
761
+ end
762
+ success
763
+ # return true
664
764
  end
665
765
 
666
766
  end
@@ -4,28 +4,65 @@ 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
+ begin
60
+ sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version, prevouts: prevouts)
61
+ key = Key.from_xonly_pubkey(pubkey)
62
+ key.verify(sig, sighash, algo: :schnorr)
63
+ rescue ArgumentError
64
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
65
+ end
29
66
  end
30
67
 
31
68
  def check_locktime(locktime)
@@ -77,5 +114,16 @@ module Bitcoin
77
114
  sequence_masked <= tx_sequence_masked
78
115
  end
79
116
 
117
+ def has_error?
118
+ !@error_code.nil?
119
+ end
120
+
121
+ private
122
+
123
+ def set_error(code)
124
+ @error_code = code
125
+ false
126
+ end
127
+
80
128
  end
81
129
  end