bitcoinrb 0.3.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +17 -6
- data/bitcoinrb.gemspec +9 -8
- data/exe/bitcoinrbd +5 -0
- data/lib/bitcoin.rb +37 -19
- 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 +44 -10
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext.rb +6 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext/json_parser.rb +46 -0
- data/lib/bitcoin/ext_key.rb +51 -20
- data/lib/bitcoin/key.rb +89 -30
- data/lib/bitcoin/key_path.rb +12 -5
- data/lib/bitcoin/message.rb +79 -0
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +17 -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 +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message/version.rb +7 -0
- data/lib/bitcoin/message_sign.rb +47 -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 +2 -0
- data/lib/bitcoin/payment_code.rb +92 -0
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +9 -18
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +12 -17
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
- data/lib/bitcoin/rpc/request_handler.rb +5 -5
- data/lib/bitcoin/script/script.rb +96 -39
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +166 -66
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1.rb +1 -0
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
- data/lib/bitcoin/secp256k1/ruby.rb +112 -56
- data/lib/bitcoin/sighash_generator.rb +156 -0
- data/lib/bitcoin/store.rb +1 -0
- data/lib/bitcoin/store/chain_entry.rb +1 -0
- data/lib/bitcoin/store/utxo_db.rb +226 -0
- data/lib/bitcoin/taproot.rb +9 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +139 -0
- data/lib/bitcoin/tx.rb +34 -104
- data/lib/bitcoin/tx_in.rb +4 -5
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +22 -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 +2 -2
- data/lib/bitcoin/wallet/master_key.rb +1 -0
- data/lib/bitcoin/wallet/utxo.rb +37 -0
- metadata +86 -32
- data/.travis.yml +0 -11
@@ -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
|
@@ -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,20 +55,17 @@ 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
|
62
64
|
if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
|
63
65
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
|
64
|
-
tmp = stack
|
65
66
|
@stack = stack_copy
|
66
67
|
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
|
68
|
+
redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
72
69
|
return false unless eval_script(redeem_script, :base)
|
73
70
|
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
|
74
71
|
|
@@ -76,10 +73,10 @@ module Bitcoin
|
|
76
73
|
if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
|
77
74
|
had_witness = true
|
78
75
|
# 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.
|
76
|
+
return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
|
80
77
|
|
81
78
|
version, program = redeem_script.witness_data
|
82
|
-
return false unless verify_witness_program(witness, version, program)
|
79
|
+
return false unless verify_witness_program(witness, version, program, true)
|
83
80
|
end
|
84
81
|
end
|
85
82
|
|
@@ -106,21 +103,71 @@ module Bitcoin
|
|
106
103
|
false
|
107
104
|
end
|
108
105
|
|
109
|
-
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 = {}
|
110
111
|
if version == 0
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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)
|
115
117
|
script_hash = Bitcoin.sha256(script_pubkey.to_payload)
|
116
118
|
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
|
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
|
119
121
|
script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
|
120
|
-
@stack = witness.stack.map{|w|w.bth}
|
121
122
|
else
|
122
123
|
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
|
123
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
|
124
171
|
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
125
172
|
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
126
173
|
else
|
@@ -131,23 +178,26 @@ module Bitcoin
|
|
131
178
|
return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
132
179
|
end
|
133
180
|
|
134
|
-
return false unless eval_script(script_pubkey, :
|
181
|
+
return false unless eval_script(script_pubkey, sig_version, opts: opts)
|
135
182
|
|
136
183
|
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
|
137
184
|
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
|
138
185
|
true
|
139
186
|
end
|
140
187
|
|
141
|
-
def eval_script(script, sig_version)
|
142
|
-
|
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)
|
143
192
|
begin
|
144
193
|
flow_stack = []
|
145
194
|
alt_stack = []
|
146
|
-
|
195
|
+
opts[:last_code_separator_pos] = 0xffffffff
|
196
|
+
opts[:begincodehash] = 0
|
147
197
|
op_count = 0
|
148
198
|
|
149
199
|
script.chunks.each_with_index do |c, index|
|
150
|
-
need_exec =
|
200
|
+
need_exec = flow_stack.rindex(false).nil?
|
151
201
|
|
152
202
|
return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
153
203
|
|
@@ -157,10 +207,10 @@ module Bitcoin
|
|
157
207
|
if require_minimal && !minimal_push?(c.pushed_data, opcode)
|
158
208
|
return set_error(SCRIPT_ERR_MINIMALDATA)
|
159
209
|
end
|
160
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE) unless
|
210
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
|
161
211
|
stack << c.pushed_data.bth
|
162
212
|
else
|
163
|
-
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
|
164
214
|
return set_error(SCRIPT_ERR_OP_COUNT)
|
165
215
|
end
|
166
216
|
return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
|
@@ -209,9 +259,11 @@ module Bitcoin
|
|
209
259
|
if need_exec
|
210
260
|
return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
|
211
261
|
value = pop_string.htb
|
212
|
-
if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
|
213
|
-
|
214
|
-
|
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)
|
215
267
|
end
|
216
268
|
end
|
217
269
|
result = cast_to_bool(value)
|
@@ -398,26 +450,14 @@ module Bitcoin
|
|
398
450
|
a, b = pop_int(2)
|
399
451
|
push_int(a == b ? 0 : 1)
|
400
452
|
when OP_CODESEPARATOR
|
401
|
-
|
453
|
+
opts[:begincodehash] = index + 1
|
454
|
+
opts[:last_code_separator_pos] = index
|
402
455
|
when OP_CHECKSIG, OP_CHECKSIGVERIFY
|
403
456
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
|
404
457
|
sig, pubkey = pop_string(2)
|
405
458
|
|
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
|
459
|
+
success = valid_sig?(sig, pubkey, sig_version, script, opts)
|
460
|
+
return false if error && !error.ok?
|
421
461
|
|
422
462
|
push_int(success ? 1 : 0)
|
423
463
|
|
@@ -428,6 +468,19 @@ module Bitcoin
|
|
428
468
|
return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
|
429
469
|
end
|
430
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)
|
431
484
|
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
432
485
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
433
486
|
pubkey_count = pop_int
|
@@ -452,7 +505,7 @@ module Bitcoin
|
|
452
505
|
sigs = pop_string(sig_count)
|
453
506
|
sigs = [sigs] if sigs.is_a?(String)
|
454
507
|
|
455
|
-
subscript = script.subscript(
|
508
|
+
subscript = script.subscript(opts[:begincodehash]..-1)
|
456
509
|
|
457
510
|
if sig_version == :base
|
458
511
|
sigs.each do |sig|
|
@@ -467,7 +520,7 @@ module Bitcoin
|
|
467
520
|
sig = sigs.pop
|
468
521
|
pubkey = pubkeys.pop
|
469
522
|
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)
|
523
|
+
ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
|
471
524
|
if ok
|
472
525
|
sig_count -= 1
|
473
526
|
else
|
@@ -514,7 +567,6 @@ module Bitcoin
|
|
514
567
|
end
|
515
568
|
rescue Exception => e
|
516
569
|
puts e
|
517
|
-
puts e.backtrace
|
518
570
|
return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
|
519
571
|
end
|
520
572
|
|
@@ -573,6 +625,7 @@ module Bitcoin
|
|
573
625
|
|
574
626
|
# see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
|
575
627
|
def cast_to_bool(v)
|
628
|
+
return false if v.empty?
|
576
629
|
case v
|
577
630
|
when Numeric
|
578
631
|
return v != 0
|
@@ -609,7 +662,6 @@ module Bitcoin
|
|
609
662
|
return false if sig.empty?
|
610
663
|
s = sig.unpack('C*')
|
611
664
|
hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
|
612
|
-
hash_type &= (~(Bitcoin::SIGHASH_FORK_ID)) if Bitcoin.chain_params.fork_chain? # for fork coin.
|
613
665
|
return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
|
614
666
|
true
|
615
667
|
end
|
@@ -643,24 +695,72 @@ module Bitcoin
|
|
643
695
|
true
|
644
696
|
end
|
645
697
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
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
|
664
764
|
end
|
665
765
|
|
666
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]
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
-
|
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
|