bitcoinrb 0.5.0 → 0.9.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 +11 -1
- data/bitcoinrb.gemspec +7 -6
- 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 +45 -4
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +36 -20
- data/lib/bitcoin/key.rb +85 -28
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- 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 +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.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- 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/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +4 -4
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +14 -5
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +80 -30
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -62
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/ruby.rb +108 -21
- data/lib/bitcoin/sighash_generator.rb +157 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +30 -96
- 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
- data/lib/bitcoin.rb +32 -24
- metadata +58 -18
- data/.travis.yml +0 -12
@@ -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,7 +55,9 @@ 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
|
@@ -63,11 +65,7 @@ module Bitcoin
|
|
63
65
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
|
64
66
|
@stack = stack_copy
|
65
67
|
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
|
68
|
+
redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
71
69
|
return false unless eval_script(redeem_script, :base)
|
72
70
|
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
|
73
71
|
|
@@ -78,7 +76,7 @@ module Bitcoin
|
|
78
76
|
return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
|
79
77
|
|
80
78
|
version, program = redeem_script.witness_data
|
81
|
-
return false unless verify_witness_program(witness, version, program)
|
79
|
+
return false unless verify_witness_program(witness, version, program, true)
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
@@ -105,21 +103,71 @@ module Bitcoin
|
|
105
103
|
false
|
106
104
|
end
|
107
105
|
|
108
|
-
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 = {}
|
109
111
|
if version == 0
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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)
|
114
117
|
script_hash = Bitcoin.sha256(script_pubkey.to_payload)
|
115
118
|
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
|
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
|
118
121
|
script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
|
119
|
-
@stack = witness.stack.map{|w|w.bth}
|
120
122
|
else
|
121
123
|
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
|
122
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
|
123
171
|
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
124
172
|
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
125
173
|
else
|
@@ -130,19 +178,22 @@ module Bitcoin
|
|
130
178
|
return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
131
179
|
end
|
132
180
|
|
133
|
-
return false unless eval_script(script_pubkey, :
|
181
|
+
return false unless eval_script(script_pubkey, sig_version, opts: opts)
|
134
182
|
|
135
183
|
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
|
136
184
|
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
|
137
185
|
true
|
138
186
|
end
|
139
187
|
|
140
|
-
def eval_script(script, sig_version)
|
141
|
-
|
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)
|
142
192
|
begin
|
143
193
|
flow_stack = []
|
144
194
|
alt_stack = []
|
145
|
-
|
195
|
+
opts[:last_code_separator_pos] = 0xffffffff
|
196
|
+
opts[:begincodehash] = 0
|
146
197
|
op_count = 0
|
147
198
|
|
148
199
|
script.chunks.each_with_index do |c, index|
|
@@ -156,10 +207,10 @@ module Bitcoin
|
|
156
207
|
if require_minimal && !minimal_push?(c.pushed_data, opcode)
|
157
208
|
return set_error(SCRIPT_ERR_MINIMALDATA)
|
158
209
|
end
|
159
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE) unless
|
210
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
|
160
211
|
stack << c.pushed_data.bth
|
161
212
|
else
|
162
|
-
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
|
163
214
|
return set_error(SCRIPT_ERR_OP_COUNT)
|
164
215
|
end
|
165
216
|
return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
|
@@ -208,9 +259,11 @@ module Bitcoin
|
|
208
259
|
if need_exec
|
209
260
|
return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
|
210
261
|
value = pop_string.htb
|
211
|
-
if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
|
212
|
-
|
213
|
-
|
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)
|
214
267
|
end
|
215
268
|
end
|
216
269
|
result = cast_to_bool(value)
|
@@ -397,26 +450,14 @@ module Bitcoin
|
|
397
450
|
a, b = pop_int(2)
|
398
451
|
push_int(a == b ? 0 : 1)
|
399
452
|
when OP_CODESEPARATOR
|
400
|
-
|
453
|
+
opts[:begincodehash] = index + 1
|
454
|
+
opts[:last_code_separator_pos] = index
|
401
455
|
when OP_CHECKSIG, OP_CHECKSIGVERIFY
|
402
456
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
|
403
457
|
sig, pubkey = pop_string(2)
|
404
458
|
|
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
|
459
|
+
success = valid_sig?(sig, pubkey, sig_version, script, opts)
|
460
|
+
return false if error && !error.ok?
|
420
461
|
|
421
462
|
push_int(success ? 1 : 0)
|
422
463
|
|
@@ -427,6 +468,19 @@ module Bitcoin
|
|
427
468
|
return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
|
428
469
|
end
|
429
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)
|
430
484
|
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
431
485
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
432
486
|
pubkey_count = pop_int
|
@@ -451,7 +505,7 @@ module Bitcoin
|
|
451
505
|
sigs = pop_string(sig_count)
|
452
506
|
sigs = [sigs] if sigs.is_a?(String)
|
453
507
|
|
454
|
-
subscript = script.subscript(
|
508
|
+
subscript = script.subscript(opts[:begincodehash]..-1)
|
455
509
|
|
456
510
|
if sig_version == :base
|
457
511
|
sigs.each do |sig|
|
@@ -466,7 +520,7 @@ module Bitcoin
|
|
466
520
|
sig = sigs.pop
|
467
521
|
pubkey = pubkeys.pop
|
468
522
|
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)
|
523
|
+
ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
|
470
524
|
if ok
|
471
525
|
sig_count -= 1
|
472
526
|
else
|
@@ -513,7 +567,6 @@ module Bitcoin
|
|
513
567
|
end
|
514
568
|
rescue Exception => e
|
515
569
|
puts e
|
516
|
-
puts e.backtrace
|
517
570
|
return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
|
518
571
|
end
|
519
572
|
|
@@ -572,6 +625,7 @@ module Bitcoin
|
|
572
625
|
|
573
626
|
# see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
|
574
627
|
def cast_to_bool(v)
|
628
|
+
return false if v.empty?
|
575
629
|
case v
|
576
630
|
when Numeric
|
577
631
|
return v != 0
|
@@ -641,24 +695,72 @@ module Bitcoin
|
|
641
695
|
true
|
642
696
|
end
|
643
697
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
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
|
662
764
|
end
|
663
765
|
|
664
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
|