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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +5 -3
- data/README.md +17 -6
- data/bitcoinrb.gemspec +7 -7
- data/exe/bitcoinrbd +5 -0
- data/lib/bitcoin.rb +34 -11
- data/lib/bitcoin/bip85_entropy.rb +111 -0
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/block_header.rb +2 -0
- data/lib/bitcoin/chain_params.rb +9 -8
- data/lib/bitcoin/chainparams/regtest.yml +1 -1
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/chainparams/testnet.yml +1 -1
- data/lib/bitcoin/constants.rb +45 -12
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext.rb +5 -0
- data/lib/bitcoin/ext/ecdsa.rb +31 -0
- data/lib/bitcoin/ext/json_parser.rb +46 -0
- data/lib/bitcoin/ext_key.rb +50 -19
- data/lib/bitcoin/key.rb +46 -29
- data/lib/bitcoin/key_path.rb +12 -5
- data/lib/bitcoin/message.rb +7 -0
- data/lib/bitcoin/message/base.rb +1 -0
- data/lib/bitcoin/message/cf_parser.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +36 -0
- data/lib/bitcoin/message/cfheaders.rb +40 -0
- data/lib/bitcoin/message/cfilter.rb +35 -0
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
- data/lib/bitcoin/message/get_cfheaders.rb +24 -0
- data/lib/bitcoin/message/get_cfilters.rb +25 -0
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +3 -3
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/version.rb +7 -0
- data/lib/bitcoin/mnemonic.rb +7 -7
- data/lib/bitcoin/network/peer.rb +9 -4
- data/lib/bitcoin/network/peer_discovery.rb +1 -1
- data/lib/bitcoin/node/cli.rb +14 -10
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +9 -1
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/out_point.rb +7 -0
- data/lib/bitcoin/payment_code.rb +92 -0
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +8 -17
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +11 -16
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
- data/lib/bitcoin/rpc/request_handler.rb +2 -2
- data/lib/bitcoin/script/script.rb +68 -28
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -67
- data/lib/bitcoin/script/tx_checker.rb +64 -14
- data/lib/bitcoin/secp256k1.rb +1 -0
- data/lib/bitcoin/secp256k1/native.rb +138 -25
- data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
- data/lib/bitcoin/secp256k1/ruby.rb +82 -54
- data/lib/bitcoin/sighash_generator.rb +156 -0
- data/lib/bitcoin/slip39/sss.rb +5 -2
- data/lib/bitcoin/store.rb +2 -1
- data/lib/bitcoin/store/chain_entry.rb +1 -0
- data/lib/bitcoin/store/db/level_db.rb +2 -2
- data/lib/bitcoin/store/utxo_db.rb +226 -0
- data/lib/bitcoin/tx.rb +17 -88
- data/lib/bitcoin/tx_in.rb +4 -5
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +43 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet.rb +1 -0
- data/lib/bitcoin/wallet/account.rb +2 -1
- data/lib/bitcoin/wallet/base.rb +3 -3
- data/lib/bitcoin/wallet/db.rb +1 -1
- data/lib/bitcoin/wallet/master_key.rb +1 -0
- data/lib/bitcoin/wallet/utxo.rb +37 -0
- 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.
|
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.
|
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.
|
120
|
+
packed_size.unpack1('C')
|
116
121
|
when OP_PUSHDATA2
|
117
122
|
packed_size = buf.read(2)
|
118
|
-
packed_size.
|
123
|
+
packed_size.unpack1('v')
|
119
124
|
when OP_PUSHDATA4
|
120
125
|
packed_size = buf.read(4)
|
121
|
-
packed_size.
|
126
|
+
packed_size.unpack1('V')
|
122
127
|
else
|
123
|
-
pushcode
|
128
|
+
pushcode < OP_PUSHDATA1 ? pushcode : 0
|
124
129
|
end
|
125
|
-
if
|
126
|
-
s.chunks << [len].pack('C')
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
144
|
-
|
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].
|
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
|
334
|
-
|
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
|
-
|
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(
|
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].
|
384
|
-
v[-1] = [v[-1].
|
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].
|
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:
|
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 =
|
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
|
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
|
-
|
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.
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
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, :
|
178
|
+
return false unless eval_script(script_pubkey, sig_version, opts: opts)
|
135
179
|
|
136
|
-
return set_error(
|
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
|
-
|
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
|
-
|
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 =
|
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
|
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
|
-
|
214
|
-
|
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
|
-
|
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
|
-
|
407
|
-
if
|
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(
|
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
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
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
|