bitcoinrb 0.3.0 → 0.6.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +5 -3
  4. data/README.md +17 -6
  5. data/bitcoinrb.gemspec +7 -7
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +34 -11
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_filter.rb +14 -0
  10. data/lib/bitcoin/block_header.rb +2 -0
  11. data/lib/bitcoin/chain_params.rb +9 -8
  12. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  13. data/lib/bitcoin/chainparams/signet.yml +39 -0
  14. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  15. data/lib/bitcoin/constants.rb +45 -12
  16. data/lib/bitcoin/descriptor.rb +1 -1
  17. data/lib/bitcoin/errors.rb +19 -0
  18. data/lib/bitcoin/ext.rb +5 -0
  19. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  20. data/lib/bitcoin/ext/json_parser.rb +46 -0
  21. data/lib/bitcoin/ext_key.rb +50 -19
  22. data/lib/bitcoin/key.rb +46 -29
  23. data/lib/bitcoin/key_path.rb +12 -5
  24. data/lib/bitcoin/message.rb +7 -0
  25. data/lib/bitcoin/message/base.rb +1 -0
  26. data/lib/bitcoin/message/cf_parser.rb +16 -0
  27. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  28. data/lib/bitcoin/message/cfheaders.rb +40 -0
  29. data/lib/bitcoin/message/cfilter.rb +35 -0
  30. data/lib/bitcoin/message/fee_filter.rb +1 -1
  31. data/lib/bitcoin/message/filter_load.rb +3 -3
  32. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  33. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  34. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  35. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  36. data/lib/bitcoin/message/inventory.rb +1 -1
  37. data/lib/bitcoin/message/merkle_block.rb +1 -1
  38. data/lib/bitcoin/message/network_addr.rb +3 -3
  39. data/lib/bitcoin/message/ping.rb +1 -1
  40. data/lib/bitcoin/message/pong.rb +1 -1
  41. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  42. data/lib/bitcoin/message/version.rb +7 -0
  43. data/lib/bitcoin/mnemonic.rb +7 -7
  44. data/lib/bitcoin/network/peer.rb +9 -4
  45. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  46. data/lib/bitcoin/node/cli.rb +14 -10
  47. data/lib/bitcoin/node/configuration.rb +3 -1
  48. data/lib/bitcoin/node/spv.rb +9 -1
  49. data/lib/bitcoin/opcodes.rb +14 -1
  50. data/lib/bitcoin/out_point.rb +7 -0
  51. data/lib/bitcoin/payment_code.rb +92 -0
  52. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  53. data/lib/bitcoin/psbt/input.rb +8 -17
  54. data/lib/bitcoin/psbt/output.rb +1 -1
  55. data/lib/bitcoin/psbt/tx.rb +11 -16
  56. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  57. data/lib/bitcoin/rpc/request_handler.rb +2 -2
  58. data/lib/bitcoin/script/script.rb +68 -28
  59. data/lib/bitcoin/script/script_error.rb +27 -1
  60. data/lib/bitcoin/script/script_interpreter.rb +164 -67
  61. data/lib/bitcoin/script/tx_checker.rb +64 -14
  62. data/lib/bitcoin/secp256k1.rb +1 -0
  63. data/lib/bitcoin/secp256k1/native.rb +138 -25
  64. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  65. data/lib/bitcoin/secp256k1/ruby.rb +82 -54
  66. data/lib/bitcoin/sighash_generator.rb +156 -0
  67. data/lib/bitcoin/slip39/sss.rb +5 -2
  68. data/lib/bitcoin/store.rb +2 -1
  69. data/lib/bitcoin/store/chain_entry.rb +1 -0
  70. data/lib/bitcoin/store/db/level_db.rb +2 -2
  71. data/lib/bitcoin/store/utxo_db.rb +226 -0
  72. data/lib/bitcoin/tx.rb +17 -88
  73. data/lib/bitcoin/tx_in.rb +4 -5
  74. data/lib/bitcoin/tx_out.rb +2 -3
  75. data/lib/bitcoin/util.rb +43 -6
  76. data/lib/bitcoin/version.rb +1 -1
  77. data/lib/bitcoin/wallet.rb +1 -0
  78. data/lib/bitcoin/wallet/account.rb +2 -1
  79. data/lib/bitcoin/wallet/base.rb +3 -3
  80. data/lib/bitcoin/wallet/db.rb +1 -1
  81. data/lib/bitcoin/wallet/master_key.rb +1 -0
  82. data/lib/bitcoin/wallet/utxo.rb +37 -0
  83. metadata +50 -32
@@ -42,7 +42,7 @@ module Bitcoin
42
42
  nextblockhash: node.chain.next_hash(block_hash).rhex
43
43
  }
44
44
  else
45
- entry.header.to_payload.bth
45
+ entry.header.to_hex
46
46
  end
47
47
  end
48
48
 
@@ -96,7 +96,7 @@ module Bitcoin
96
96
  script = Bitcoin::Script.parse_from_payload(hex_script.htb)
97
97
  h = script.to_h
98
98
  h.delete(:hex)
99
- h[:p2sh] = script.to_p2sh.addresses.first unless script.p2sh?
99
+ h[:p2sh] = script.to_p2sh.to_addr unless script.p2sh?
100
100
  h
101
101
  rescue Exception
102
102
  raise ArgumentError.new('Script decode failed')
@@ -6,6 +6,7 @@ module Bitcoin
6
6
  # bitcoin script
7
7
  class Script
8
8
  include Bitcoin::Opcodes
9
+ include Bitcoin::HexConverter
9
10
 
10
11
  attr_accessor :chunks
11
12
 
@@ -109,25 +110,28 @@ module Bitcoin
109
110
  if opcode.pushdata?
110
111
  pushcode = opcode.ord
111
112
  packed_size = nil
113
+ if buf.eof?
114
+ s.chunks << opcode
115
+ return s
116
+ end
112
117
  len = case pushcode
113
118
  when OP_PUSHDATA1
114
119
  packed_size = buf.read(1)
115
- packed_size.unpack('C').first
120
+ packed_size.unpack1('C')
116
121
  when OP_PUSHDATA2
117
122
  packed_size = buf.read(2)
118
- packed_size.unpack('v').first
123
+ packed_size.unpack1('v')
119
124
  when OP_PUSHDATA4
120
125
  packed_size = buf.read(4)
121
- packed_size.unpack('V').first
126
+ packed_size.unpack1('V')
122
127
  else
123
- pushcode if pushcode < OP_PUSHDATA1
128
+ pushcode < OP_PUSHDATA1 ? pushcode : 0
124
129
  end
125
- if len
126
- s.chunks << [len].pack('C') if buf.eof?
127
- unless buf.eof?
128
- chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
129
- s.chunks << chunk
130
- end
130
+ if buf.eof?
131
+ s.chunks << [len].pack('C')
132
+ else buf.eof?
133
+ chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
134
+ s.chunks << chunk
131
135
  end
132
136
  else
133
137
  if Opcodes.defined?(opcode.ord)
@@ -140,15 +144,21 @@ module Bitcoin
140
144
  s
141
145
  end
142
146
 
143
- def to_payload
144
- chunks.join
147
+ # Output script payload.
148
+ # @param [Boolean] length_prefixed Flag whether the length of the pyrode should be given at the beginning.(default: false)
149
+ # @return [String] payload
150
+ def to_payload(length_prefixed = false)
151
+ p = chunks.join
152
+ length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
145
153
  end
146
154
 
147
155
  def empty?
148
156
  chunks.size == 0
149
157
  end
150
158
 
159
+ # @deprecated
151
160
  def addresses
161
+ puts "WARNING: Bitcoin::Script#addresses is deprecated. Use Bitcoin::Script#to_addr instead."
152
162
  return [p2pkh_addr] if p2pkh?
153
163
  return [p2sh_addr] if p2sh?
154
164
  return [bech32_addr] if witness_program?
@@ -156,6 +166,15 @@ module Bitcoin
156
166
  []
157
167
  end
158
168
 
169
+ # convert to address
170
+ # @return [String] if script type is p2pkh or p2sh or witness program, return address, otherwise nil.
171
+ def to_addr
172
+ return p2pkh_addr if p2pkh?
173
+ return p2sh_addr if p2sh?
174
+ return bech32_addr if witness_program?
175
+ nil
176
+ end
177
+
159
178
  # check whether standard script.
160
179
  def standard?
161
180
  p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
@@ -230,7 +249,7 @@ module Bitcoin
230
249
  return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
231
250
  return false unless chunks[1].pushdata?
232
251
 
233
- if size == (chunks[1][0].unpack('C').first + 2)
252
+ if size == (chunks[1][0].unpack1('C') + 2)
234
253
  program_size = chunks[1].pushed_data.bytesize
235
254
  return program_size >= 2 && program_size <= 40
236
255
  end
@@ -324,16 +343,21 @@ module Bitcoin
324
343
  when Integer
325
344
  opcode_to_name(c)
326
345
  when String
346
+ return c if c.empty?
327
347
  if c.pushdata?
328
348
  v = Opcodes.opcode_to_small_int(c.ord)
329
349
  if v
330
350
  v
331
351
  else
332
352
  data = c.pushed_data
333
- if data.bytesize <= 4
334
- Script.decode_number(data.bth) # for scriptnum
353
+ if data
354
+ if data.bytesize <= 4
355
+ Script.decode_number(data.bth) # for scriptnum
356
+ else
357
+ data.bth
358
+ end
335
359
  else
336
- data.bth
360
+ c.bth
337
361
  end
338
362
  end
339
363
  else
@@ -351,7 +375,7 @@ module Bitcoin
351
375
 
352
376
  # generate hash160 hash for payload
353
377
  def to_hash160
354
- Bitcoin.hash160(to_payload.bth)
378
+ Bitcoin.hash160(to_hex)
355
379
  end
356
380
 
357
381
  # script size
@@ -380,8 +404,8 @@ module Bitcoin
380
404
  hex = '0' + hex unless (hex.length % 2).zero?
381
405
  v = hex.htb.reverse # change endian
382
406
 
383
- v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack('C').first & 0x80) == 0
384
- v[-1] = [v[-1].unpack('C').first | 0x80].pack('C') if negative
407
+ v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack1('C') & 0x80) == 0
408
+ v[-1] = [v[-1].unpack1('C') | 0x80].pack('C') if negative
385
409
  v.bth
386
410
  end
387
411
 
@@ -389,7 +413,7 @@ module Bitcoin
389
413
  def self.decode_number(s)
390
414
  v = s.htb.reverse
391
415
  return 0 if v.length.zero?
392
- mbs = v[0].unpack('C').first
416
+ mbs = v[0].unpack1('C')
393
417
  v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
394
418
  result = v.bth.to_i(16)
395
419
  result = -result unless (mbs & 0x80) == 0
@@ -488,7 +512,7 @@ module Bitcoin
488
512
  end
489
513
 
490
514
  def to_h
491
- h = {asm: to_s, hex: to_payload.bth, type: type}
515
+ h = {asm: to_s, hex: to_hex, type: type}
492
516
  addrs = addresses
493
517
  unless addrs.empty?
494
518
  h[:req_sigs] = multisig? ? Bitcoin::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
@@ -504,12 +528,6 @@ module Bitcoin
504
528
  (size > 0 && op_return?) || size > Bitcoin::MAX_SCRIPT_SIZE
505
529
  end
506
530
 
507
- # convert payload to hex data.
508
- # @return [String] script with hex format.
509
- def to_hex
510
- to_payload.bth
511
- end
512
-
513
531
  private
514
532
 
515
533
  # generate p2pkh address. if script dose not p2pkh, return nil.
@@ -542,10 +560,32 @@ module Bitcoin
542
560
  def bech32_addr
543
561
  segwit_addr = Bech32::SegwitAddr.new
544
562
  segwit_addr.hrp = Bitcoin.chain_params.bech32_hrp
545
- segwit_addr.script_pubkey = to_payload.bth
563
+ segwit_addr.script_pubkey = to_hex
546
564
  segwit_addr.addr
547
565
  end
548
566
 
567
+ # Check whether push data length is valid.
568
+ # @return [Boolean] if valid return true, otherwise false.
569
+ def valid_pushdata_length?(chunk)
570
+ buf = StringIO.new(chunk)
571
+ opcode = buf.read(1).ord
572
+ offset = 1
573
+ len = case opcode
574
+ when OP_PUSHDATA1
575
+ offset += 1
576
+ buf.read(1).unpack1('C')
577
+ when OP_PUSHDATA2
578
+ offset += 2
579
+ buf.read(2).unpack1('v')
580
+ when OP_PUSHDATA4
581
+ offset += 4
582
+ buf.read(4).unpack1('V')
583
+ else
584
+ opcode
585
+ end
586
+ chunk.bytesize == len + offset
587
+ end
588
+
549
589
  end
550
590
 
551
591
  end
@@ -76,6 +76,14 @@ module Bitcoin
76
76
  'NOPx reserved for soft-fork upgrades'
77
77
  when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
78
78
  'Witness version reserved for soft-fork upgrades'
79
+ when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
80
+ 'Taproot version reserved for soft-fork upgrades'
81
+ when SCRIPT_ERR_DISCOURAGE_UNKNOWN_ANNEX
82
+ 'Unknown input annex reserved for soft-fork upgrades'
83
+ when SCRIPT_ERR_DISCOURAGE_OP_SUCCESS
84
+ 'SUCCESSx reserved for soft-fork upgrades'
85
+ when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
86
+ 'Public key version reserved for soft-fork upgrades'
79
87
  when SCRIPT_ERR_PUBKEYTYPE
80
88
  'Public key is neither compressed or uncompressed'
81
89
  when SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH
@@ -92,8 +100,22 @@ module Bitcoin
92
100
  'Witness provided for non-witness script'
93
101
  when SCRIPT_ERR_WITNESS_PUBKEYTYPE
94
102
  'Using non-compressed keys in segwit'
103
+ when SCRIPT_ERR_SCHNORR_SIG_SIZE
104
+ 'Invalid Schnorr signature size'
105
+ when SCRIPT_ERR_SCHNORR_SIG_HASHTYPE
106
+ 'Invalid Schnorr signature hash type'
107
+ when SCRIPT_ERR_SCHNORR_SIG
108
+ 'Invalid Schnorr signature'
109
+ when SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE
110
+ 'Invalid Taproot control block size'
111
+ when SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT
112
+ 'Too much signature validation relative to witness weight'
113
+ when SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG
114
+ 'OP_CHECKMULTISIG(VERIFY) is not available in tapscript'
115
+ when SCRIPT_ERR_TAPSCRIPT_MINIMALIF
116
+ 'OP_IF/NOTIF argument must be minimal in tapscript'
95
117
  when SCRIPT_ERR_OP_CODESEPARATOR
96
- 'Using OP_CODESEPARATOR in non-witness scrip'
118
+ 'Using OP_CODESEPARATOR in non-witness script'
97
119
  when SCRIPT_ERR_SIG_FINDANDDELETE
98
120
  'Signature is found in scriptCode'
99
121
  when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
@@ -103,6 +125,10 @@ module Bitcoin
103
125
  end
104
126
  end
105
127
 
128
+ def ok?
129
+ code == Bitcoin::SCRIPT_ERR_OK
130
+ end
131
+
106
132
  def self.name_to_code(name)
107
133
  NAME_MAP[name]
108
134
  end
@@ -13,7 +13,6 @@ module Bitcoin
13
13
 
14
14
  DISABLE_OPCODES = [OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_DIV, OP_MUL, OP_MOD, OP_LSHIFT, OP_RSHIFT]
15
15
 
16
-
17
16
  # syntax sugar for simple evaluation for script.
18
17
  # @param [Bitcoin::Script] script_sig a scriptSig.
19
18
  # @param [Bitcoin::Script] script_pubkey a scriptPubkey.
@@ -55,20 +54,15 @@ module Bitcoin
55
54
  return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
56
55
  version, program = script_pubkey.witness_data
57
56
  stack_copy = stack.dup
58
- return false unless verify_witness_program(witness, version, program)
57
+ return false unless verify_witness_program(witness, version, program, false)
59
58
  end
60
59
 
61
60
  # Additional validation for spend-to-script-hash transactions
62
61
  if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
63
62
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
64
- tmp = stack
65
63
  @stack = stack_copy
66
64
  raise 'stack cannot be empty.' if stack.empty?
67
- begin
68
- redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
69
- rescue Exception => e
70
- return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
71
- end
65
+ redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
72
66
  return false unless eval_script(redeem_script, :base)
73
67
  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
74
68
 
@@ -76,10 +70,10 @@ module Bitcoin
76
70
  if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
77
71
  had_witness = true
78
72
  # The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we reintroduce malleability.
79
- return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_payload.bth)
73
+ return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
80
74
 
81
75
  version, program = redeem_script.witness_data
82
- return false unless verify_witness_program(witness, version, program)
76
+ return false unless verify_witness_program(witness, version, program, true)
83
77
  end
84
78
  end
85
79
 
@@ -106,21 +100,71 @@ module Bitcoin
106
100
  false
107
101
  end
108
102
 
109
- def verify_witness_program(witness, version, program)
103
+ def verify_witness_program(witness, version, program, is_p2sh)
104
+ @stack = witness.stack.map(&:bth)
105
+ need_evaluate = false
106
+ sig_version = nil
107
+ opts = {}
110
108
  if version == 0
111
- if program.bytesize == 32
112
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if witness.stack.size == 0
113
- script_pubkey = Bitcoin::Script.parse_from_payload(witness.stack.last)
114
- @stack = witness.stack[0..-2].map{|w|w.bth}
109
+ need_evaluate = true
110
+ sig_version = :witness_v0
111
+ if program.bytesize == WITNESS_V0_SCRIPTHASH_SIZE # BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
112
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if stack.size == 0
113
+ script_pubkey = Bitcoin::Script.parse_from_payload(stack.pop.htb)
115
114
  script_hash = Bitcoin.sha256(script_pubkey.to_payload)
116
115
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program
117
- elsif program.bytesize == 20
118
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless witness.stack.size == 2
116
+ elsif program.bytesize == WITNESS_V0_KEYHASH_SIZE # BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
117
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless stack.size == 2
119
118
  script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
120
- @stack = witness.stack.map{|w|w.bth}
121
119
  else
122
120
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
123
121
  end
122
+ elsif version == 1 && program.bytesize == WITNESS_V1_TAPROOT_SIZE && !is_p2sh
123
+ # BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
124
+ return true unless flag?(SCRIPT_VERIFY_TAPROOT)
125
+ if stack.size == 0
126
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY)
127
+ elsif stack.size >= 2 && !stack.last.empty? && stack.last[0..1].to_i(16) == ANNEX_TAG
128
+ opts[:annex] = stack.pop.htb
129
+ end
130
+ if stack.size == 1 # Key path spending (stack size is 1 after removing optional annex)
131
+ result = checker.check_schnorr_sig(stack.last, program.bth, :taproot, opts)
132
+ return checker.has_error? ? set_error(checker.error_code) : result
133
+ else
134
+ sig_version = :tapscript
135
+ # Script path spending (stack size is >1 after removing optional annex)
136
+ control = stack.pop.htb
137
+ script_payload = stack.pop.htb
138
+ if control.bytesize < TAPROOT_CONTROL_BASE_SIZE || control.bytesize > TAPROOT_CONTROL_MAX_SIZE ||
139
+ (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
140
+ return set_error(SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE)
141
+ end
142
+ leaf_ver = control[0].bti & TAPROOT_LEAF_MASK
143
+ opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
144
+ return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless valid_taproot_commitment?(control, program, opts[:leaf_hash])
145
+ if (control[0].bti & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT
146
+ opts[:weight_left] = witness.to_payload.bytesize + VALIDATION_WEIGHT_OFFSET
147
+ script_pubkey = Bitcoin::Script.parse_from_payload(script_payload)
148
+ script_pubkey.chunks.each do |c|
149
+ next if c.empty?
150
+ # Note how this condition would not be reached if an unknown OP_SUCCESSx was found
151
+ if c.pushdata?
152
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
153
+ elsif Opcodes.op_success?(c.ord)
154
+ # OP_SUCCESSx processing overrides everything, including stack element size limits
155
+ return set_error(SCRIPT_ERR_DISCOURAGE_OP_SUCCESS) if flag?(SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS)
156
+ return true
157
+ else
158
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless Opcodes.defined?(c.ord, true)
159
+ end
160
+ end
161
+ return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size > MAX_STACK_SIZE
162
+ need_evaluate = true
163
+ end
164
+
165
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
166
+ return true unless need_evaluate
167
+ end
124
168
  elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
125
169
  return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
126
170
  else
@@ -131,23 +175,26 @@ module Bitcoin
131
175
  return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
132
176
  end
133
177
 
134
- return false unless eval_script(script_pubkey, :witness_v0)
178
+ return false unless eval_script(script_pubkey, sig_version, opts: opts)
135
179
 
136
- return set_error(SCRIPT_ERR_EVAL_FALSE) unless stack.size == 1
180
+ return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
137
181
  return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
138
182
  true
139
183
  end
140
184
 
141
- def eval_script(script, sig_version)
142
- return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
185
+ def eval_script(script, sig_version, opts: {})
186
+ # sig_version cannot be TAPROOT here, as it admits no script execution.
187
+ raise ArgumentError, "Invalid sig version was specified: #{sig_version}" unless [:base, :witness_v0, :tapscript].include?(sig_version)
188
+ return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE && [:base, :witness_v0].include?(sig_version)
143
189
  begin
144
190
  flow_stack = []
145
191
  alt_stack = []
146
- last_code_separator_index = 0
192
+ opts[:last_code_separator_pos] = 0xffffffff
193
+ opts[:begincodehash] = 0
147
194
  op_count = 0
148
195
 
149
196
  script.chunks.each_with_index do |c, index|
150
- need_exec = !flow_stack.include?(false)
197
+ need_exec = flow_stack.rindex(false).nil?
151
198
 
152
199
  return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE
153
200
 
@@ -157,10 +204,10 @@ module Bitcoin
157
204
  if require_minimal && !minimal_push?(c.pushed_data, opcode)
158
205
  return set_error(SCRIPT_ERR_MINIMALDATA)
159
206
  end
160
- return set_error(SCRIPT_ERR_BAD_OPCODE) unless verify_pushdata_length(c)
207
+ return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
161
208
  stack << c.pushed_data.bth
162
209
  else
163
- if opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
210
+ if [:base, :witness_v0].include?(sig_version) && opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
164
211
  return set_error(SCRIPT_ERR_OP_COUNT)
165
212
  end
166
213
  return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
@@ -209,9 +256,11 @@ module Bitcoin
209
256
  if need_exec
210
257
  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
211
258
  value = pop_string.htb
212
- if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
213
- if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
214
- return set_error(SCRIPT_ERR_MINIMALIF)
259
+ if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF) || sig_version == :tapscript
260
+ # Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF.
261
+ # Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
262
+ if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack1('C') != 1)
263
+ return set_error(sig_version == :witness_v0 ? SCRIPT_ERR_MINIMALIF : SCRIPT_ERR_TAPSCRIPT_MINIMALIF)
215
264
  end
216
265
  end
217
266
  result = cast_to_bool(value)
@@ -398,26 +447,14 @@ module Bitcoin
398
447
  a, b = pop_int(2)
399
448
  push_int(a == b ? 0 : 1)
400
449
  when OP_CODESEPARATOR
401
- last_code_separator_index = index + 1
450
+ opts[:begincodehash] = index + 1
451
+ opts[:last_code_separator_pos] = index
402
452
  when OP_CHECKSIG, OP_CHECKSIGVERIFY
403
453
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
404
454
  sig, pubkey = pop_string(2)
405
455
 
406
- subscript = script.subscript(last_code_separator_index..-1)
407
- if sig_version == :base
408
- tmp = subscript.find_and_delete(Script.new << sig)
409
- return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
410
- subscript = tmp
411
- end
412
-
413
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
414
-
415
- success = checker.check_sig(sig, pubkey, subscript, sig_version)
416
-
417
- # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
418
- if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
419
- return set_error(SCRIPT_ERR_SIG_NULLFAIL)
420
- end
456
+ success = valid_sig?(sig, pubkey, sig_version, script, opts)
457
+ return false if error && !error.ok?
421
458
 
422
459
  push_int(success ? 1 : 0)
423
460
 
@@ -428,6 +465,19 @@ module Bitcoin
428
465
  return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
429
466
  end
430
467
  end
468
+ when OP_CHECKSIGADD
469
+ # OP_CHECKSIGADD is only available in Tapscript
470
+ return set_error(SCRIPT_ERR_BAD_OPCODE) if [:base, :witness_v0].include?(sig_version)
471
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
472
+
473
+ pubkey = pop_string
474
+ num = pop_int
475
+ sig = pop_string
476
+
477
+ success = valid_sig?(sig, pubkey, sig_version, script, opts) && !sig.empty?
478
+ return false if error && !error.ok?
479
+
480
+ push_int(success ? num + 1 : num)
431
481
  when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
432
482
  return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
433
483
  pubkey_count = pop_int
@@ -452,7 +502,7 @@ module Bitcoin
452
502
  sigs = pop_string(sig_count)
453
503
  sigs = [sigs] if sigs.is_a?(String)
454
504
 
455
- subscript = script.subscript(last_code_separator_index..-1)
505
+ subscript = script.subscript(opts[:begincodehash]..-1)
456
506
 
457
507
  if sig_version == :base
458
508
  sigs.each do |sig|
@@ -467,7 +517,7 @@ module Bitcoin
467
517
  sig = sigs.pop
468
518
  pubkey = pubkeys.pop
469
519
  return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
470
- ok = checker.check_sig(sig, pubkey, subscript, sig_version)
520
+ ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
471
521
  if ok
472
522
  sig_count -= 1
473
523
  else
@@ -514,7 +564,6 @@ module Bitcoin
514
564
  end
515
565
  rescue Exception => e
516
566
  puts e
517
- puts e.backtrace
518
567
  return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
519
568
  end
520
569
 
@@ -573,6 +622,7 @@ module Bitcoin
573
622
 
574
623
  # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
575
624
  def cast_to_bool(v)
625
+ return false if v.empty?
576
626
  case v
577
627
  when Numeric
578
628
  return v != 0
@@ -609,7 +659,6 @@ module Bitcoin
609
659
  return false if sig.empty?
610
660
  s = sig.unpack('C*')
611
661
  hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
612
- hash_type &= (~(Bitcoin::SIGHASH_FORK_ID)) if Bitcoin.chain_params.fork_chain? # for fork coin.
613
662
  return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
614
663
  true
615
664
  end
@@ -643,24 +692,72 @@ module Bitcoin
643
692
  true
644
693
  end
645
694
 
646
- def verify_pushdata_length(chunk)
647
- buf = StringIO.new(chunk)
648
- opcode = buf.read(1).ord
649
- offset = 1
650
- len = case opcode
651
- when OP_PUSHDATA1
652
- offset += 1
653
- buf.read(1).unpack('C').first
654
- when OP_PUSHDATA2
655
- offset += 2
656
- buf.read(2).unpack('v').first
657
- when OP_PUSHDATA4
658
- offset += 4
659
- buf.read(4).unpack('V').first
660
- else
661
- opcode
662
- end
663
- chunk.bytesize == len + offset
695
+ # check whether valid taproot commitment.
696
+ def valid_taproot_commitment?(control, program, leaf_hash)
697
+ begin
698
+ path_len = (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
699
+ xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
700
+ p = Bitcoin::Key.new(pubkey: "02#{xonly_pubkey.bth}", key_type: Key::TYPES[:compressed])
701
+ k = leaf_hash
702
+ path_len.times do |i|
703
+ pos = (TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i)
704
+ e = control[pos...(pos + TAPROOT_CONTROL_NODE_SIZE)]
705
+ k = Bitcoin.tagged_hash('TapBranch', k.bth < e.bth ? k + e : e + k)
706
+ end
707
+ t = Bitcoin.tagged_hash('TapTweak', xonly_pubkey + k)
708
+ key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
709
+ q = key.to_point + p.to_point
710
+ return q.x == program.bti && (control[0].bti & 1) == (q.y % 2)
711
+ rescue ArgumentError
712
+ return false
713
+ end
714
+ end
715
+
716
+ def valid_sig?(sig, pubkey, sig_version, script, opts)
717
+ case sig_version
718
+ when :base, :witness_v0
719
+ return eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
720
+ when :tapscript
721
+ return eval_checksig_tapscript(sig, pubkey, opts)
722
+ end
723
+ false
724
+ end
725
+
726
+ def eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
727
+ subscript = script.subscript(opts[:begincodehash]..-1)
728
+ if sig_version == :base
729
+ tmp = subscript.find_and_delete(Script.new << sig)
730
+ return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
731
+ subscript = tmp
732
+ end
733
+ return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
734
+ success = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
735
+ # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
736
+ return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
737
+ success
738
+ end
739
+
740
+ def eval_checksig_tapscript(sig, pubkey, opts)
741
+ success = !sig.empty?
742
+ if success
743
+ # Implement the sigops/witnesssize ratio test.
744
+ opts[:weight_left] -= VALIDATION_WEIGHT_PER_SIGOP_PASSED
745
+ return set_error(SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT) if opts[:weight_left] < 0
746
+ end
747
+
748
+ pubkey_size = pubkey.htb.bytesize
749
+ if pubkey_size == 0
750
+ return set_error(SCRIPT_ERR_PUBKEYTYPE)
751
+ elsif pubkey_size == 32
752
+ if success
753
+ result = checker.check_schnorr_sig(sig, pubkey, :tapscript, opts)
754
+ return checker.has_error? ? set_error(checker.error_code) : set_error(SCRIPT_ERR_SCHNORR_SIG) unless result
755
+ end
756
+ else
757
+ return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE)
758
+ end
759
+ success
760
+ # return true
664
761
  end
665
762
 
666
763
  end