bitcoinrb 0.3.2 → 0.8.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/.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
|