bitcoinrb 0.5.0 → 0.9.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 (68) 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 +11 -1
  6. data/bitcoinrb.gemspec +7 -6
  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 +45 -4
  11. data/lib/bitcoin/descriptor.rb +1 -1
  12. data/lib/bitcoin/errors.rb +19 -0
  13. data/lib/bitcoin/ext/array_ext.rb +22 -0
  14. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  15. data/lib/bitcoin/ext.rb +1 -0
  16. data/lib/bitcoin/ext_key.rb +36 -20
  17. data/lib/bitcoin/key.rb +85 -28
  18. data/lib/bitcoin/message/addr_v2.rb +34 -0
  19. data/lib/bitcoin/message/base.rb +16 -0
  20. data/lib/bitcoin/message/cfcheckpt.rb +2 -2
  21. data/lib/bitcoin/message/cfheaders.rb +1 -1
  22. data/lib/bitcoin/message/cfilter.rb +1 -1
  23. data/lib/bitcoin/message/fee_filter.rb +1 -1
  24. data/lib/bitcoin/message/filter_load.rb +3 -3
  25. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  26. data/lib/bitcoin/message/inventory.rb +1 -1
  27. data/lib/bitcoin/message/merkle_block.rb +1 -1
  28. data/lib/bitcoin/message/network_addr.rb +141 -18
  29. data/lib/bitcoin/message/ping.rb +1 -1
  30. data/lib/bitcoin/message/pong.rb +1 -1
  31. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  32. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  33. data/lib/bitcoin/message/tx.rb +1 -1
  34. data/lib/bitcoin/message.rb +72 -0
  35. data/lib/bitcoin/message_sign.rb +47 -0
  36. data/lib/bitcoin/mnemonic.rb +2 -2
  37. data/lib/bitcoin/network/peer_discovery.rb +1 -3
  38. data/lib/bitcoin/node/configuration.rb +3 -1
  39. data/lib/bitcoin/node/spv.rb +8 -0
  40. data/lib/bitcoin/opcodes.rb +14 -1
  41. data/lib/bitcoin/payment_code.rb +2 -2
  42. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  43. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  44. data/lib/bitcoin/psbt/input.rb +4 -4
  45. data/lib/bitcoin/psbt/output.rb +1 -1
  46. data/lib/bitcoin/psbt/tx.rb +14 -5
  47. data/lib/bitcoin/psbt.rb +8 -0
  48. data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
  49. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  50. data/lib/bitcoin/script/script.rb +80 -30
  51. data/lib/bitcoin/script/script_error.rb +27 -1
  52. data/lib/bitcoin/script/script_interpreter.rb +164 -62
  53. data/lib/bitcoin/script/tx_checker.rb +62 -14
  54. data/lib/bitcoin/secp256k1/native.rb +184 -17
  55. data/lib/bitcoin/secp256k1/ruby.rb +108 -21
  56. data/lib/bitcoin/sighash_generator.rb +157 -0
  57. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  58. data/lib/bitcoin/taproot/simple_builder.rb +155 -0
  59. data/lib/bitcoin/taproot.rb +45 -0
  60. data/lib/bitcoin/tx.rb +30 -96
  61. data/lib/bitcoin/tx_in.rb +1 -1
  62. data/lib/bitcoin/tx_out.rb +2 -3
  63. data/lib/bitcoin/util.rb +15 -6
  64. data/lib/bitcoin/version.rb +1 -1
  65. data/lib/bitcoin/wallet/account.rb +1 -1
  66. data/lib/bitcoin.rb +32 -24
  67. metadata +58 -18
  68. data/.travis.yml +0 -12
@@ -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,7 +55,9 @@ 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
@@ -63,11 +65,7 @@ module Bitcoin
63
65
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
64
66
  @stack = stack_copy
65
67
  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
68
+ redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
71
69
  return false unless eval_script(redeem_script, :base)
72
70
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
73
71
 
@@ -78,7 +76,7 @@ module Bitcoin
78
76
  return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
79
77
 
80
78
  version, program = redeem_script.witness_data
81
- return false unless verify_witness_program(witness, version, program)
79
+ return false unless verify_witness_program(witness, version, program, true)
82
80
  end
83
81
  end
84
82
 
@@ -105,21 +103,71 @@ module Bitcoin
105
103
  false
106
104
  end
107
105
 
108
- 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 = {}
109
111
  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}
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)
114
117
  script_hash = Bitcoin.sha256(script_pubkey.to_payload)
115
118
  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
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
118
121
  script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
119
- @stack = witness.stack.map{|w|w.bth}
120
122
  else
121
123
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
122
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
123
171
  elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
124
172
  return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
125
173
  else
@@ -130,19 +178,22 @@ module Bitcoin
130
178
  return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
131
179
  end
132
180
 
133
- return false unless eval_script(script_pubkey, :witness_v0)
181
+ return false unless eval_script(script_pubkey, sig_version, opts: opts)
134
182
 
135
183
  return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
136
184
  return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
137
185
  true
138
186
  end
139
187
 
140
- def eval_script(script, sig_version)
141
- 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)
142
192
  begin
143
193
  flow_stack = []
144
194
  alt_stack = []
145
- last_code_separator_index = 0
195
+ opts[:last_code_separator_pos] = 0xffffffff
196
+ opts[:begincodehash] = 0
146
197
  op_count = 0
147
198
 
148
199
  script.chunks.each_with_index do |c, index|
@@ -156,10 +207,10 @@ module Bitcoin
156
207
  if require_minimal && !minimal_push?(c.pushed_data, opcode)
157
208
  return set_error(SCRIPT_ERR_MINIMALDATA)
158
209
  end
159
- 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?
160
211
  stack << c.pushed_data.bth
161
212
  else
162
- 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
163
214
  return set_error(SCRIPT_ERR_OP_COUNT)
164
215
  end
165
216
  return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
@@ -208,9 +259,11 @@ module Bitcoin
208
259
  if need_exec
209
260
  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
210
261
  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)
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)
214
267
  end
215
268
  end
216
269
  result = cast_to_bool(value)
@@ -397,26 +450,14 @@ module Bitcoin
397
450
  a, b = pop_int(2)
398
451
  push_int(a == b ? 0 : 1)
399
452
  when OP_CODESEPARATOR
400
- last_code_separator_index = index + 1
453
+ opts[:begincodehash] = index + 1
454
+ opts[:last_code_separator_pos] = index
401
455
  when OP_CHECKSIG, OP_CHECKSIGVERIFY
402
456
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
403
457
  sig, pubkey = pop_string(2)
404
458
 
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
459
+ success = valid_sig?(sig, pubkey, sig_version, script, opts)
460
+ return false if error && !error.ok?
420
461
 
421
462
  push_int(success ? 1 : 0)
422
463
 
@@ -427,6 +468,19 @@ module Bitcoin
427
468
  return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
428
469
  end
429
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)
430
484
  when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
431
485
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
432
486
  pubkey_count = pop_int
@@ -451,7 +505,7 @@ module Bitcoin
451
505
  sigs = pop_string(sig_count)
452
506
  sigs = [sigs] if sigs.is_a?(String)
453
507
 
454
- subscript = script.subscript(last_code_separator_index..-1)
508
+ subscript = script.subscript(opts[:begincodehash]..-1)
455
509
 
456
510
  if sig_version == :base
457
511
  sigs.each do |sig|
@@ -466,7 +520,7 @@ module Bitcoin
466
520
  sig = sigs.pop
467
521
  pubkey = pubkeys.pop
468
522
  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)
523
+ ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
470
524
  if ok
471
525
  sig_count -= 1
472
526
  else
@@ -513,7 +567,6 @@ module Bitcoin
513
567
  end
514
568
  rescue Exception => e
515
569
  puts e
516
- puts e.backtrace
517
570
  return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
518
571
  end
519
572
 
@@ -572,6 +625,7 @@ module Bitcoin
572
625
 
573
626
  # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
574
627
  def cast_to_bool(v)
628
+ return false if v.empty?
575
629
  case v
576
630
  when Numeric
577
631
  return v != 0
@@ -641,24 +695,72 @@ module Bitcoin
641
695
  true
642
696
  end
643
697
 
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
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
662
764
  end
663
765
 
664
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