bitcoinrb 0.5.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 -4
- data/README.md +10 -0
- data/bitcoinrb.gemspec +4 -4
- data/lib/bitcoin.rb +29 -16
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/constants.rb +43 -3
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/ecdsa.rb +31 -0
- data/lib/bitcoin/ext_key.rb +35 -19
- data/lib/bitcoin/key.rb +43 -26
- data/lib/bitcoin/message/cfcheckpt.rb +2 -2
- data/lib/bitcoin/message/cfheaders.rb +1 -1
- data/lib/bitcoin/message/cfilter.rb +1 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- 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/mnemonic.rb +2 -2
- data/lib/bitcoin/network/peer_discovery.rb +1 -3
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +8 -0
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/payment_code.rb +2 -2
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +3 -3
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +4 -4
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/script/script.rb +52 -19
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +161 -62
- data/lib/bitcoin/script/tx_checker.rb +64 -14
- data/lib/bitcoin/secp256k1/native.rb +138 -25
- data/lib/bitcoin/secp256k1/ruby.rb +78 -19
- data/lib/bitcoin/sighash_generator.rb +156 -0
- data/lib/bitcoin/tx.rb +13 -80
- data/lib/bitcoin/tx_in.rb +1 -1
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +15 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +1 -1
- metadata +19 -15
@@ -63,7 +63,7 @@ module Bitcoin
|
|
63
63
|
response = http.request(request)
|
64
64
|
body = response.body
|
65
65
|
response = Bitcoin::Ext::JsonParser.new(body.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }).parse
|
66
|
-
raise response['error'].
|
66
|
+
raise response['error'].to_json if response['error']
|
67
67
|
response['result']
|
68
68
|
end
|
69
69
|
|
@@ -110,25 +110,28 @@ module Bitcoin
|
|
110
110
|
if opcode.pushdata?
|
111
111
|
pushcode = opcode.ord
|
112
112
|
packed_size = nil
|
113
|
+
if buf.eof?
|
114
|
+
s.chunks << opcode
|
115
|
+
return s
|
116
|
+
end
|
113
117
|
len = case pushcode
|
114
118
|
when OP_PUSHDATA1
|
115
119
|
packed_size = buf.read(1)
|
116
|
-
packed_size.
|
120
|
+
packed_size.unpack1('C')
|
117
121
|
when OP_PUSHDATA2
|
118
122
|
packed_size = buf.read(2)
|
119
|
-
packed_size.
|
123
|
+
packed_size.unpack1('v')
|
120
124
|
when OP_PUSHDATA4
|
121
125
|
packed_size = buf.read(4)
|
122
|
-
packed_size.
|
126
|
+
packed_size.unpack1('V')
|
123
127
|
else
|
124
|
-
pushcode
|
128
|
+
pushcode < OP_PUSHDATA1 ? pushcode : 0
|
125
129
|
end
|
126
|
-
if
|
127
|
-
s.chunks << [len].pack('C')
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
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
|
132
135
|
end
|
133
136
|
else
|
134
137
|
if Opcodes.defined?(opcode.ord)
|
@@ -141,8 +144,12 @@ module Bitcoin
|
|
141
144
|
s
|
142
145
|
end
|
143
146
|
|
144
|
-
|
145
|
-
|
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
|
146
153
|
end
|
147
154
|
|
148
155
|
def empty?
|
@@ -242,7 +249,7 @@ module Bitcoin
|
|
242
249
|
return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
|
243
250
|
return false unless chunks[1].pushdata?
|
244
251
|
|
245
|
-
if size == (chunks[1][0].
|
252
|
+
if size == (chunks[1][0].unpack1('C') + 2)
|
246
253
|
program_size = chunks[1].pushed_data.bytesize
|
247
254
|
return program_size >= 2 && program_size <= 40
|
248
255
|
end
|
@@ -343,10 +350,14 @@ module Bitcoin
|
|
343
350
|
v
|
344
351
|
else
|
345
352
|
data = c.pushed_data
|
346
|
-
if data
|
347
|
-
|
353
|
+
if data
|
354
|
+
if data.bytesize <= 4
|
355
|
+
Script.decode_number(data.bth) # for scriptnum
|
356
|
+
else
|
357
|
+
data.bth
|
358
|
+
end
|
348
359
|
else
|
349
|
-
|
360
|
+
c.bth
|
350
361
|
end
|
351
362
|
end
|
352
363
|
else
|
@@ -393,8 +404,8 @@ module Bitcoin
|
|
393
404
|
hex = '0' + hex unless (hex.length % 2).zero?
|
394
405
|
v = hex.htb.reverse # change endian
|
395
406
|
|
396
|
-
v = v << (negative ? 0x80 : 0x00) unless (v[-1].
|
397
|
-
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
|
398
409
|
v.bth
|
399
410
|
end
|
400
411
|
|
@@ -402,7 +413,7 @@ module Bitcoin
|
|
402
413
|
def self.decode_number(s)
|
403
414
|
v = s.htb.reverse
|
404
415
|
return 0 if v.length.zero?
|
405
|
-
mbs = v[0].
|
416
|
+
mbs = v[0].unpack1('C')
|
406
417
|
v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
|
407
418
|
result = v.bth.to_i(16)
|
408
419
|
result = -result unless (mbs & 0x80) == 0
|
@@ -553,6 +564,28 @@ module Bitcoin
|
|
553
564
|
segwit_addr.addr
|
554
565
|
end
|
555
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
|
+
|
556
589
|
end
|
557
590
|
|
558
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,7 +54,7 @@ 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
|
@@ -63,11 +62,7 @@ module Bitcoin
|
|
63
62
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
|
64
63
|
@stack = stack_copy
|
65
64
|
raise 'stack cannot be empty.' if stack.empty?
|
66
|
-
|
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
|
65
|
+
redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
71
66
|
return false unless eval_script(redeem_script, :base)
|
72
67
|
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
|
73
68
|
|
@@ -78,7 +73,7 @@ module Bitcoin
|
|
78
73
|
return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
|
79
74
|
|
80
75
|
version, program = redeem_script.witness_data
|
81
|
-
return false unless verify_witness_program(witness, version, program)
|
76
|
+
return false unless verify_witness_program(witness, version, program, true)
|
82
77
|
end
|
83
78
|
end
|
84
79
|
|
@@ -105,21 +100,71 @@ module Bitcoin
|
|
105
100
|
false
|
106
101
|
end
|
107
102
|
|
108
|
-
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 = {}
|
109
108
|
if version == 0
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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)
|
114
114
|
script_hash = Bitcoin.sha256(script_pubkey.to_payload)
|
115
115
|
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
|
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
|
118
118
|
script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
|
119
|
-
@stack = witness.stack.map{|w|w.bth}
|
120
119
|
else
|
121
120
|
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
|
122
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
|
123
168
|
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
124
169
|
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
125
170
|
else
|
@@ -130,19 +175,22 @@ module Bitcoin
|
|
130
175
|
return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
131
176
|
end
|
132
177
|
|
133
|
-
return false unless eval_script(script_pubkey, :
|
178
|
+
return false unless eval_script(script_pubkey, sig_version, opts: opts)
|
134
179
|
|
135
180
|
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
|
136
181
|
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
|
137
182
|
true
|
138
183
|
end
|
139
184
|
|
140
|
-
def eval_script(script, sig_version)
|
141
|
-
|
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)
|
142
189
|
begin
|
143
190
|
flow_stack = []
|
144
191
|
alt_stack = []
|
145
|
-
|
192
|
+
opts[:last_code_separator_pos] = 0xffffffff
|
193
|
+
opts[:begincodehash] = 0
|
146
194
|
op_count = 0
|
147
195
|
|
148
196
|
script.chunks.each_with_index do |c, index|
|
@@ -156,10 +204,10 @@ module Bitcoin
|
|
156
204
|
if require_minimal && !minimal_push?(c.pushed_data, opcode)
|
157
205
|
return set_error(SCRIPT_ERR_MINIMALDATA)
|
158
206
|
end
|
159
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE) unless
|
207
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
|
160
208
|
stack << c.pushed_data.bth
|
161
209
|
else
|
162
|
-
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
|
163
211
|
return set_error(SCRIPT_ERR_OP_COUNT)
|
164
212
|
end
|
165
213
|
return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
|
@@ -208,9 +256,11 @@ module Bitcoin
|
|
208
256
|
if need_exec
|
209
257
|
return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
|
210
258
|
value = pop_string.htb
|
211
|
-
if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
|
212
|
-
|
213
|
-
|
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)
|
214
264
|
end
|
215
265
|
end
|
216
266
|
result = cast_to_bool(value)
|
@@ -397,26 +447,14 @@ module Bitcoin
|
|
397
447
|
a, b = pop_int(2)
|
398
448
|
push_int(a == b ? 0 : 1)
|
399
449
|
when OP_CODESEPARATOR
|
400
|
-
|
450
|
+
opts[:begincodehash] = index + 1
|
451
|
+
opts[:last_code_separator_pos] = index
|
401
452
|
when OP_CHECKSIG, OP_CHECKSIGVERIFY
|
402
453
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
|
403
454
|
sig, pubkey = pop_string(2)
|
404
455
|
|
405
|
-
|
406
|
-
if
|
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
|
456
|
+
success = valid_sig?(sig, pubkey, sig_version, script, opts)
|
457
|
+
return false if error && !error.ok?
|
420
458
|
|
421
459
|
push_int(success ? 1 : 0)
|
422
460
|
|
@@ -427,6 +465,19 @@ module Bitcoin
|
|
427
465
|
return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
|
428
466
|
end
|
429
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)
|
430
481
|
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
431
482
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
432
483
|
pubkey_count = pop_int
|
@@ -451,7 +502,7 @@ module Bitcoin
|
|
451
502
|
sigs = pop_string(sig_count)
|
452
503
|
sigs = [sigs] if sigs.is_a?(String)
|
453
504
|
|
454
|
-
subscript = script.subscript(
|
505
|
+
subscript = script.subscript(opts[:begincodehash]..-1)
|
455
506
|
|
456
507
|
if sig_version == :base
|
457
508
|
sigs.each do |sig|
|
@@ -466,7 +517,7 @@ module Bitcoin
|
|
466
517
|
sig = sigs.pop
|
467
518
|
pubkey = pubkeys.pop
|
468
519
|
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)
|
520
|
+
ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
|
470
521
|
if ok
|
471
522
|
sig_count -= 1
|
472
523
|
else
|
@@ -513,7 +564,6 @@ module Bitcoin
|
|
513
564
|
end
|
514
565
|
rescue Exception => e
|
515
566
|
puts e
|
516
|
-
puts e.backtrace
|
517
567
|
return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
|
518
568
|
end
|
519
569
|
|
@@ -572,6 +622,7 @@ module Bitcoin
|
|
572
622
|
|
573
623
|
# see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
|
574
624
|
def cast_to_bool(v)
|
625
|
+
return false if v.empty?
|
575
626
|
case v
|
576
627
|
when Numeric
|
577
628
|
return v != 0
|
@@ -641,24 +692,72 @@ module Bitcoin
|
|
641
692
|
true
|
642
693
|
end
|
643
694
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
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
|
662
761
|
end
|
663
762
|
|
664
763
|
end
|