bitcoinrb 0.3.1 → 0.7.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 +6 -3
- data/README.md +17 -6
- data/bitcoinrb.gemspec +9 -8
- data/exe/bitcoinrbd +5 -0
- data/lib/bitcoin.rb +35 -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 +45 -12
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext.rb +5 -0
- data/lib/bitcoin/ext/ecdsa.rb +31 -0
- data/lib/bitcoin/ext/json_parser.rb +46 -0
- data/lib/bitcoin/ext_key.rb +50 -19
- data/lib/bitcoin/key.rb +46 -29
- data/lib/bitcoin/key_path.rb +12 -5
- data/lib/bitcoin/message.rb +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/version.rb +7 -0
- data/lib/bitcoin/mnemonic.rb +7 -7
- data/lib/bitcoin/network/peer.rb +9 -4
- data/lib/bitcoin/network/peer_discovery.rb +1 -1
- data/lib/bitcoin/node/cli.rb +14 -10
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +9 -1
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/out_point.rb +7 -0
- data/lib/bitcoin/payment_code.rb +92 -0
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +8 -17
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +11 -16
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +68 -28
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -67
- data/lib/bitcoin/script/tx_checker.rb +64 -14
- data/lib/bitcoin/secp256k1.rb +1 -0
- data/lib/bitcoin/secp256k1/native.rb +138 -25
- data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
- data/lib/bitcoin/secp256k1/ruby.rb +82 -54
- data/lib/bitcoin/sighash_generator.rb +156 -0
- data/lib/bitcoin/store.rb +2 -1
- data/lib/bitcoin/store/chain_entry.rb +1 -0
- data/lib/bitcoin/store/db/level_db.rb +2 -2
- data/lib/bitcoin/store/utxo_db.rb +226 -0
- data/lib/bitcoin/tx.rb +17 -88
- data/lib/bitcoin/tx_in.rb +4 -5
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +34 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet.rb +1 -0
- data/lib/bitcoin/wallet/account.rb +2 -1
- data/lib/bitcoin/wallet/base.rb +3 -3
- data/lib/bitcoin/wallet/db.rb +1 -1
- data/lib/bitcoin/wallet/master_key.rb +1 -0
- data/lib/bitcoin/wallet/utxo.rb +37 -0
- metadata +66 -32
@@ -76,6 +76,14 @@ module Bitcoin
|
|
76
76
|
'NOPx reserved for soft-fork upgrades'
|
77
77
|
when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
|
78
78
|
'Witness version reserved for soft-fork upgrades'
|
79
|
+
when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
|
80
|
+
'Taproot version reserved for soft-fork upgrades'
|
81
|
+
when SCRIPT_ERR_DISCOURAGE_UNKNOWN_ANNEX
|
82
|
+
'Unknown input annex reserved for soft-fork upgrades'
|
83
|
+
when SCRIPT_ERR_DISCOURAGE_OP_SUCCESS
|
84
|
+
'SUCCESSx reserved for soft-fork upgrades'
|
85
|
+
when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
|
86
|
+
'Public key version reserved for soft-fork upgrades'
|
79
87
|
when SCRIPT_ERR_PUBKEYTYPE
|
80
88
|
'Public key is neither compressed or uncompressed'
|
81
89
|
when SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH
|
@@ -92,8 +100,22 @@ module Bitcoin
|
|
92
100
|
'Witness provided for non-witness script'
|
93
101
|
when SCRIPT_ERR_WITNESS_PUBKEYTYPE
|
94
102
|
'Using non-compressed keys in segwit'
|
103
|
+
when SCRIPT_ERR_SCHNORR_SIG_SIZE
|
104
|
+
'Invalid Schnorr signature size'
|
105
|
+
when SCRIPT_ERR_SCHNORR_SIG_HASHTYPE
|
106
|
+
'Invalid Schnorr signature hash type'
|
107
|
+
when SCRIPT_ERR_SCHNORR_SIG
|
108
|
+
'Invalid Schnorr signature'
|
109
|
+
when SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE
|
110
|
+
'Invalid Taproot control block size'
|
111
|
+
when SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT
|
112
|
+
'Too much signature validation relative to witness weight'
|
113
|
+
when SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG
|
114
|
+
'OP_CHECKMULTISIG(VERIFY) is not available in tapscript'
|
115
|
+
when SCRIPT_ERR_TAPSCRIPT_MINIMALIF
|
116
|
+
'OP_IF/NOTIF argument must be minimal in tapscript'
|
95
117
|
when SCRIPT_ERR_OP_CODESEPARATOR
|
96
|
-
'Using OP_CODESEPARATOR in non-witness
|
118
|
+
'Using OP_CODESEPARATOR in non-witness script'
|
97
119
|
when SCRIPT_ERR_SIG_FINDANDDELETE
|
98
120
|
'Signature is found in scriptCode'
|
99
121
|
when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
|
@@ -103,6 +125,10 @@ module Bitcoin
|
|
103
125
|
end
|
104
126
|
end
|
105
127
|
|
128
|
+
def ok?
|
129
|
+
code == Bitcoin::SCRIPT_ERR_OK
|
130
|
+
end
|
131
|
+
|
106
132
|
def self.name_to_code(name)
|
107
133
|
NAME_MAP[name]
|
108
134
|
end
|
@@ -13,7 +13,6 @@ module Bitcoin
|
|
13
13
|
|
14
14
|
DISABLE_OPCODES = [OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_DIV, OP_MUL, OP_MOD, OP_LSHIFT, OP_RSHIFT]
|
15
15
|
|
16
|
-
|
17
16
|
# syntax sugar for simple evaluation for script.
|
18
17
|
# @param [Bitcoin::Script] script_sig a scriptSig.
|
19
18
|
# @param [Bitcoin::Script] script_pubkey a scriptPubkey.
|
@@ -55,20 +54,15 @@ module Bitcoin
|
|
55
54
|
return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
|
56
55
|
version, program = script_pubkey.witness_data
|
57
56
|
stack_copy = stack.dup
|
58
|
-
return false unless verify_witness_program(witness, version, program)
|
57
|
+
return false unless verify_witness_program(witness, version, program, false)
|
59
58
|
end
|
60
59
|
|
61
60
|
# Additional validation for spend-to-script-hash transactions
|
62
61
|
if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
|
63
62
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
|
64
|
-
tmp = stack
|
65
63
|
@stack = stack_copy
|
66
64
|
raise 'stack cannot be empty.' if stack.empty?
|
67
|
-
|
68
|
-
redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
69
|
-
rescue Exception => e
|
70
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
|
71
|
-
end
|
65
|
+
redeem_script = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
72
66
|
return false unless eval_script(redeem_script, :base)
|
73
67
|
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
|
74
68
|
|
@@ -76,10 +70,10 @@ module Bitcoin
|
|
76
70
|
if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
|
77
71
|
had_witness = true
|
78
72
|
# The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we reintroduce malleability.
|
79
|
-
return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.
|
73
|
+
return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Bitcoin::Script.new << redeem_script.to_hex)
|
80
74
|
|
81
75
|
version, program = redeem_script.witness_data
|
82
|
-
return false unless verify_witness_program(witness, version, program)
|
76
|
+
return false unless verify_witness_program(witness, version, program, true)
|
83
77
|
end
|
84
78
|
end
|
85
79
|
|
@@ -106,21 +100,71 @@ module Bitcoin
|
|
106
100
|
false
|
107
101
|
end
|
108
102
|
|
109
|
-
def verify_witness_program(witness, version, program)
|
103
|
+
def verify_witness_program(witness, version, program, is_p2sh)
|
104
|
+
@stack = witness.stack.map(&:bth)
|
105
|
+
need_evaluate = false
|
106
|
+
sig_version = nil
|
107
|
+
opts = {}
|
110
108
|
if version == 0
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
109
|
+
need_evaluate = true
|
110
|
+
sig_version = :witness_v0
|
111
|
+
if program.bytesize == WITNESS_V0_SCRIPTHASH_SIZE # BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
|
112
|
+
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if stack.size == 0
|
113
|
+
script_pubkey = Bitcoin::Script.parse_from_payload(stack.pop.htb)
|
115
114
|
script_hash = Bitcoin.sha256(script_pubkey.to_payload)
|
116
115
|
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program
|
117
|
-
elsif program.bytesize == 20
|
118
|
-
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless
|
116
|
+
elsif program.bytesize == WITNESS_V0_KEYHASH_SIZE # BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
|
117
|
+
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless stack.size == 2
|
119
118
|
script_pubkey = Bitcoin::Script.to_p2pkh(program.bth)
|
120
|
-
@stack = witness.stack.map{|w|w.bth}
|
121
119
|
else
|
122
120
|
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
|
123
121
|
end
|
122
|
+
elsif version == 1 && program.bytesize == WITNESS_V1_TAPROOT_SIZE && !is_p2sh
|
123
|
+
# BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
|
124
|
+
return true unless flag?(SCRIPT_VERIFY_TAPROOT)
|
125
|
+
if stack.size == 0
|
126
|
+
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY)
|
127
|
+
elsif stack.size >= 2 && !stack.last.empty? && stack.last[0..1].to_i(16) == ANNEX_TAG
|
128
|
+
opts[:annex] = stack.pop.htb
|
129
|
+
end
|
130
|
+
if stack.size == 1 # Key path spending (stack size is 1 after removing optional annex)
|
131
|
+
result = checker.check_schnorr_sig(stack.last, program.bth, :taproot, opts)
|
132
|
+
return checker.has_error? ? set_error(checker.error_code) : result
|
133
|
+
else
|
134
|
+
sig_version = :tapscript
|
135
|
+
# Script path spending (stack size is >1 after removing optional annex)
|
136
|
+
control = stack.pop.htb
|
137
|
+
script_payload = stack.pop.htb
|
138
|
+
if control.bytesize < TAPROOT_CONTROL_BASE_SIZE || control.bytesize > TAPROOT_CONTROL_MAX_SIZE ||
|
139
|
+
(control.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
|
140
|
+
return set_error(SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE)
|
141
|
+
end
|
142
|
+
leaf_ver = control[0].bti & TAPROOT_LEAF_MASK
|
143
|
+
opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
|
144
|
+
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless valid_taproot_commitment?(control, program, opts[:leaf_hash])
|
145
|
+
if (control[0].bti & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT
|
146
|
+
opts[:weight_left] = witness.to_payload.bytesize + VALIDATION_WEIGHT_OFFSET
|
147
|
+
script_pubkey = Bitcoin::Script.parse_from_payload(script_payload)
|
148
|
+
script_pubkey.chunks.each do |c|
|
149
|
+
next if c.empty?
|
150
|
+
# Note how this condition would not be reached if an unknown OP_SUCCESSx was found
|
151
|
+
if c.pushdata?
|
152
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
|
153
|
+
elsif Opcodes.op_success?(c.ord)
|
154
|
+
# OP_SUCCESSx processing overrides everything, including stack element size limits
|
155
|
+
return set_error(SCRIPT_ERR_DISCOURAGE_OP_SUCCESS) if flag?(SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS)
|
156
|
+
return true
|
157
|
+
else
|
158
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) unless Opcodes.defined?(c.ord, true)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size > MAX_STACK_SIZE
|
162
|
+
need_evaluate = true
|
163
|
+
end
|
164
|
+
|
165
|
+
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
|
166
|
+
return true unless need_evaluate
|
167
|
+
end
|
124
168
|
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
125
169
|
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
126
170
|
else
|
@@ -131,23 +175,26 @@ module Bitcoin
|
|
131
175
|
return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
132
176
|
end
|
133
177
|
|
134
|
-
return false unless eval_script(script_pubkey, :
|
178
|
+
return false unless eval_script(script_pubkey, sig_version, opts: opts)
|
135
179
|
|
136
|
-
return set_error(
|
180
|
+
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
|
137
181
|
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
|
138
182
|
true
|
139
183
|
end
|
140
184
|
|
141
|
-
def eval_script(script, sig_version)
|
142
|
-
|
185
|
+
def eval_script(script, sig_version, opts: {})
|
186
|
+
# sig_version cannot be TAPROOT here, as it admits no script execution.
|
187
|
+
raise ArgumentError, "Invalid sig version was specified: #{sig_version}" unless [:base, :witness_v0, :tapscript].include?(sig_version)
|
188
|
+
return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE && [:base, :witness_v0].include?(sig_version)
|
143
189
|
begin
|
144
190
|
flow_stack = []
|
145
191
|
alt_stack = []
|
146
|
-
|
192
|
+
opts[:last_code_separator_pos] = 0xffffffff
|
193
|
+
opts[:begincodehash] = 0
|
147
194
|
op_count = 0
|
148
195
|
|
149
196
|
script.chunks.each_with_index do |c, index|
|
150
|
-
need_exec =
|
197
|
+
need_exec = flow_stack.rindex(false).nil?
|
151
198
|
|
152
199
|
return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
153
200
|
|
@@ -157,10 +204,10 @@ module Bitcoin
|
|
157
204
|
if require_minimal && !minimal_push?(c.pushed_data, opcode)
|
158
205
|
return set_error(SCRIPT_ERR_MINIMALDATA)
|
159
206
|
end
|
160
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE) unless
|
207
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) unless c.valid_pushdata_length?
|
161
208
|
stack << c.pushed_data.bth
|
162
209
|
else
|
163
|
-
if opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
|
210
|
+
if [:base, :witness_v0].include?(sig_version) && opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
|
164
211
|
return set_error(SCRIPT_ERR_OP_COUNT)
|
165
212
|
end
|
166
213
|
return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
|
@@ -209,9 +256,11 @@ module Bitcoin
|
|
209
256
|
if need_exec
|
210
257
|
return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
|
211
258
|
value = pop_string.htb
|
212
|
-
if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
|
213
|
-
|
214
|
-
|
259
|
+
if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF) || sig_version == :tapscript
|
260
|
+
# Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF.
|
261
|
+
# Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
|
262
|
+
if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack1('C') != 1)
|
263
|
+
return set_error(sig_version == :witness_v0 ? SCRIPT_ERR_MINIMALIF : SCRIPT_ERR_TAPSCRIPT_MINIMALIF)
|
215
264
|
end
|
216
265
|
end
|
217
266
|
result = cast_to_bool(value)
|
@@ -398,26 +447,14 @@ module Bitcoin
|
|
398
447
|
a, b = pop_int(2)
|
399
448
|
push_int(a == b ? 0 : 1)
|
400
449
|
when OP_CODESEPARATOR
|
401
|
-
|
450
|
+
opts[:begincodehash] = index + 1
|
451
|
+
opts[:last_code_separator_pos] = index
|
402
452
|
when OP_CHECKSIG, OP_CHECKSIGVERIFY
|
403
453
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
|
404
454
|
sig, pubkey = pop_string(2)
|
405
455
|
|
406
|
-
|
407
|
-
if
|
408
|
-
tmp = subscript.find_and_delete(Script.new << sig)
|
409
|
-
return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
|
410
|
-
subscript = tmp
|
411
|
-
end
|
412
|
-
|
413
|
-
return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
|
414
|
-
|
415
|
-
success = checker.check_sig(sig, pubkey, subscript, sig_version)
|
416
|
-
|
417
|
-
# https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
|
418
|
-
if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
|
419
|
-
return set_error(SCRIPT_ERR_SIG_NULLFAIL)
|
420
|
-
end
|
456
|
+
success = valid_sig?(sig, pubkey, sig_version, script, opts)
|
457
|
+
return false if error && !error.ok?
|
421
458
|
|
422
459
|
push_int(success ? 1 : 0)
|
423
460
|
|
@@ -428,6 +465,19 @@ module Bitcoin
|
|
428
465
|
return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
|
429
466
|
end
|
430
467
|
end
|
468
|
+
when OP_CHECKSIGADD
|
469
|
+
# OP_CHECKSIGADD is only available in Tapscript
|
470
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE) if [:base, :witness_v0].include?(sig_version)
|
471
|
+
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
|
472
|
+
|
473
|
+
pubkey = pop_string
|
474
|
+
num = pop_int
|
475
|
+
sig = pop_string
|
476
|
+
|
477
|
+
success = valid_sig?(sig, pubkey, sig_version, script, opts) && !sig.empty?
|
478
|
+
return false if error && !error.ok?
|
479
|
+
|
480
|
+
push_int(success ? num + 1 : num)
|
431
481
|
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
432
482
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
433
483
|
pubkey_count = pop_int
|
@@ -452,7 +502,7 @@ module Bitcoin
|
|
452
502
|
sigs = pop_string(sig_count)
|
453
503
|
sigs = [sigs] if sigs.is_a?(String)
|
454
504
|
|
455
|
-
subscript = script.subscript(
|
505
|
+
subscript = script.subscript(opts[:begincodehash]..-1)
|
456
506
|
|
457
507
|
if sig_version == :base
|
458
508
|
sigs.each do |sig|
|
@@ -467,7 +517,7 @@ module Bitcoin
|
|
467
517
|
sig = sigs.pop
|
468
518
|
pubkey = pubkeys.pop
|
469
519
|
return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
|
470
|
-
ok = checker.check_sig(sig, pubkey, subscript, sig_version)
|
520
|
+
ok = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
|
471
521
|
if ok
|
472
522
|
sig_count -= 1
|
473
523
|
else
|
@@ -514,7 +564,6 @@ module Bitcoin
|
|
514
564
|
end
|
515
565
|
rescue Exception => e
|
516
566
|
puts e
|
517
|
-
puts e.backtrace
|
518
567
|
return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
|
519
568
|
end
|
520
569
|
|
@@ -573,6 +622,7 @@ module Bitcoin
|
|
573
622
|
|
574
623
|
# see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49
|
575
624
|
def cast_to_bool(v)
|
625
|
+
return false if v.empty?
|
576
626
|
case v
|
577
627
|
when Numeric
|
578
628
|
return v != 0
|
@@ -609,7 +659,6 @@ module Bitcoin
|
|
609
659
|
return false if sig.empty?
|
610
660
|
s = sig.unpack('C*')
|
611
661
|
hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
|
612
|
-
hash_type &= (~(Bitcoin::SIGHASH_FORK_ID)) if Bitcoin.chain_params.fork_chain? # for fork coin.
|
613
662
|
return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
|
614
663
|
true
|
615
664
|
end
|
@@ -643,24 +692,72 @@ module Bitcoin
|
|
643
692
|
true
|
644
693
|
end
|
645
694
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
695
|
+
# check whether valid taproot commitment.
|
696
|
+
def valid_taproot_commitment?(control, program, leaf_hash)
|
697
|
+
begin
|
698
|
+
path_len = (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
|
699
|
+
xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
|
700
|
+
p = Bitcoin::Key.new(pubkey: "02#{xonly_pubkey.bth}", key_type: Key::TYPES[:compressed])
|
701
|
+
k = leaf_hash
|
702
|
+
path_len.times do |i|
|
703
|
+
pos = (TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i)
|
704
|
+
e = control[pos...(pos + TAPROOT_CONTROL_NODE_SIZE)]
|
705
|
+
k = Bitcoin.tagged_hash('TapBranch', k.bth < e.bth ? k + e : e + k)
|
706
|
+
end
|
707
|
+
t = Bitcoin.tagged_hash('TapTweak', xonly_pubkey + k)
|
708
|
+
key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
|
709
|
+
q = key.to_point + p.to_point
|
710
|
+
return q.x == program.bti && (control[0].bti & 1) == (q.y % 2)
|
711
|
+
rescue ArgumentError
|
712
|
+
return false
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
def valid_sig?(sig, pubkey, sig_version, script, opts)
|
717
|
+
case sig_version
|
718
|
+
when :base, :witness_v0
|
719
|
+
return eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
|
720
|
+
when :tapscript
|
721
|
+
return eval_checksig_tapscript(sig, pubkey, opts)
|
722
|
+
end
|
723
|
+
false
|
724
|
+
end
|
725
|
+
|
726
|
+
def eval_checksig_pre_tapscript(sig_version, sig, pubkey, script, opts)
|
727
|
+
subscript = script.subscript(opts[:begincodehash]..-1)
|
728
|
+
if sig_version == :base
|
729
|
+
tmp = subscript.find_and_delete(Script.new << sig)
|
730
|
+
return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
|
731
|
+
subscript = tmp
|
732
|
+
end
|
733
|
+
return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
|
734
|
+
success = checker.check_sig(sig, pubkey, subscript, sig_version, allow_hybrid: !flag?(SCRIPT_VERIFY_STRICTENC))
|
735
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
|
736
|
+
return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
|
737
|
+
success
|
738
|
+
end
|
739
|
+
|
740
|
+
def eval_checksig_tapscript(sig, pubkey, opts)
|
741
|
+
success = !sig.empty?
|
742
|
+
if success
|
743
|
+
# Implement the sigops/witnesssize ratio test.
|
744
|
+
opts[:weight_left] -= VALIDATION_WEIGHT_PER_SIGOP_PASSED
|
745
|
+
return set_error(SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT) if opts[:weight_left] < 0
|
746
|
+
end
|
747
|
+
|
748
|
+
pubkey_size = pubkey.htb.bytesize
|
749
|
+
if pubkey_size == 0
|
750
|
+
return set_error(SCRIPT_ERR_PUBKEYTYPE)
|
751
|
+
elsif pubkey_size == 32
|
752
|
+
if success
|
753
|
+
result = checker.check_schnorr_sig(sig, pubkey, :tapscript, opts)
|
754
|
+
return checker.has_error? ? set_error(checker.error_code) : set_error(SCRIPT_ERR_SCHNORR_SIG) unless result
|
755
|
+
end
|
756
|
+
else
|
757
|
+
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE)
|
758
|
+
end
|
759
|
+
success
|
760
|
+
# return true
|
664
761
|
end
|
665
762
|
|
666
763
|
end
|
@@ -4,28 +4,67 @@ 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
|
+
opts[:prevouts] = prevouts
|
60
|
+
|
61
|
+
begin
|
62
|
+
sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version)
|
63
|
+
key = Key.new(pubkey: "02#{pubkey}", key_type: Key::TYPES[:compressed])
|
64
|
+
key.verify(sig, sighash, algo: :schnorr)
|
65
|
+
rescue ArgumentError
|
66
|
+
return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
|
67
|
+
end
|
29
68
|
end
|
30
69
|
|
31
70
|
def check_locktime(locktime)
|
@@ -77,5 +116,16 @@ module Bitcoin
|
|
77
116
|
sequence_masked <= tx_sequence_masked
|
78
117
|
end
|
79
118
|
|
119
|
+
def has_error?
|
120
|
+
!@error_code.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def set_error(code)
|
126
|
+
@error_code = code
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
80
130
|
end
|
81
131
|
end
|