bitcoinrb 0.3.2 → 0.8.0

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