bitcoinrb 0.5.0 → 0.9.0

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