lib/tapyrus/script/script_interpreter.rb in tapyrus-0.1.0 vs lib/tapyrus/script/script_interpreter.rb in tapyrus-0.2.0
- old
+ new
@@ -30,36 +30,25 @@
end
# eval script
# @param [Tapyrus::Script] script_sig a signature script (unlock script which data push only)
# @param [Tapyrus::Script] script_pubkey a script pubkey (locking script)
- # @param [Tapyrus::ScriptWitness] witness a witness script
# @return [Boolean] result
- def verify_script(script_sig, script_pubkey, witness = ScriptWitness.new)
+ def verify_script(script_sig, script_pubkey)
return set_error(SCRIPT_ERR_SIG_PUSHONLY) if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !script_sig.push_only?
stack_copy = nil
- had_witness = false
- return false unless eval_script(script_sig, :base)
+ return false unless eval_script(script_sig, :base, false)
stack_copy = stack.dup if flag?(SCRIPT_VERIFY_P2SH)
- return false unless eval_script(script_pubkey, :base)
+ return false unless eval_script(script_pubkey, :base, false)
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last.htb)
- # Bare witness programs
- if flag?(SCRIPT_VERIFY_WITNESS) && script_pubkey.witness_program?
- had_witness = true
- return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
- version, program = script_pubkey.witness_data
- stack_copy = stack.dup
- return false unless verify_witness_program(witness, version, program)
- end
-
# Additional validation for spend-to-script-hash transactions
if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
tmp = stack
@stack = stack_copy
@@ -67,22 +56,12 @@
begin
redeem_script = Tapyrus::Script.parse_from_payload(stack.pop.htb)
rescue Exception => e
return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
end
- return false unless eval_script(redeem_script, :base)
+ return false unless eval_script(redeem_script, :base, true)
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
-
- # P2SH witness program
- if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
- had_witness = true
- # The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we reintroduce malleability.
- return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Tapyrus::Script.new << redeem_script.to_payload.bth)
-
- version, program = redeem_script.witness_data
- return false unless verify_witness_program(witness, version, program)
- end
end
# The CLEANSTACK check is only performed after potential P2SH evaluation,
# as the non-P2SH evaluation of a P2SH script will obviously not result in a clean stack (the P2SH inputs remain).
# The same holds for witness evaluation.
@@ -91,62 +70,26 @@
# which is not a softfork (and P2SH should be one).
raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH)
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
end
- if flag?(SCRIPT_VERIFY_WITNESS)
- raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH)
- return set_error(SCRIPT_ERR_WITNESS_UNEXPECTED) if !had_witness && !witness.empty?
- end
-
true
end
def set_error(err_code, extra_message = nil)
@error = ScriptError.new(err_code, extra_message)
false
end
- def verify_witness_program(witness, version, program)
- if version == 0
- if program.bytesize == 32
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if witness.stack.size == 0
- script_pubkey = Tapyrus::Script.parse_from_payload(witness.stack.last)
- @stack = witness.stack[0..-2].map{|w|w.bth}
- script_hash = Tapyrus.sha256(script_pubkey.to_payload)
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program
- elsif program.bytesize == 20
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless witness.stack.size == 2
- script_pubkey = Tapyrus::Script.to_p2pkh(program.bth)
- @stack = witness.stack.map{|w|w.bth}
- else
- return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
- end
- elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
- return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
- else
- return true # Higher version witness scripts return true for future softfork compatibility
- end
-
- stack.each do |s| # Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack
- return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
- end
-
- return false unless eval_script(script_pubkey, :witness_v0)
-
- return set_error(SCRIPT_ERR_EVAL_FALSE) unless stack.size == 1
- return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
- true
- end
-
- def eval_script(script, sig_version)
+ def eval_script(script, sig_version, is_redeem_script)
return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
begin
flow_stack = []
alt_stack = []
last_code_separator_index = 0
op_count = 0
+ color_id = nil
script.chunks.each_with_index do |c, index|
need_exec = !flow_stack.include?(false)
return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE
@@ -207,11 +150,11 @@
when OP_IF, OP_NOTIF
result = false
if need_exec
return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
value = pop_string.htb
- if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF)
+ if flag?(SCRIPT_VERIFY_MINIMALIF)
if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
return set_error(SCRIPT_ERR_MINIMALIF)
end
end
result = cast_to_bool(value)
@@ -407,13 +350,13 @@
if sig_version == :base
tmp = subscript.find_and_delete(Script.new << sig)
return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
subscript = tmp
end
+ return false if (sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ?
+ !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey)
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
-
success = checker.check_sig(sig, pubkey, subscript, sig_version)
# https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
return set_error(SCRIPT_ERR_SIG_NULLFAIL)
@@ -426,10 +369,23 @@
stack.pop
else
return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
end
end
+ when OP_CHECKDATASIG, OP_CHECKDATASIGVERIFY
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
+ sig, msg, pubkey = pop_string(3)
+ # check signature encoding without hashtype byte
+ return false if (sig.htb.bytesize != (Tapyrus::Key::COMPACT_SIGNATURE_SIZE - 1) && !check_ecdsa_signature_encoding(sig, true)) || !check_pubkey_encoding(pubkey)
+ digest = Tapyrus.sha256(msg)
+ success = checker.verify_sig(sig, pubkey, digest)
+ return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
+ push_int(success ? 1 : 0)
+ if opcode == OP_CHECKDATASIGVERIFY
+ stack.pop if success
+ return set_error(SCRIPT_ERR_CHECKDATASIGVERIFY) unless success
+ end
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
pubkey_count = pop_int
unless (0..MAX_PUBKEYS_PER_MULTISIG).include?(pubkey_count)
return set_error(SCRIPT_ERR_PUBKEY_COUNT)
@@ -461,14 +417,21 @@
subscript = tmp
end
end
success = true
+ current_sig_scheme = nil
while success && sig_count > 0
sig = sigs.pop
pubkey = pubkeys.pop
- return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
+ sig_scheme = sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ? :schnorr : :ecdsa
+ current_sig_scheme = sig_scheme if current_sig_scheme.nil?
+
+ return false if (sig_scheme == :schnorr ? !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey) # error already set.
+
+ return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme
+
ok = checker.check_sig(sig, pubkey, subscript, sig_version)
if ok
sig_count -= 1
else
sigs << sig
@@ -486,13 +449,12 @@
# A bug causes CHECKMULTISIG to consume one extra argument whose contents were not checked in any way.
# Unfortunately this is a potential source of mutability,
# so optionally verify it is exactly equal to zero prior to removing it from the stack.
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
- if flag?(SCRIPT_VERIFY_NULLDUMMY) && stack[-1].size > 0
- return set_error(SCRIPT_ERR_SIG_NULLDUMMY)
- end
+ return set_error(SCRIPT_ERR_SIG_NULLDUMMY) if stack[-1].size > 0
+
stack.pop
push_int(success ? 1 : 0)
if opcode == OP_CHECKMULTISIGVERIFY
if success
@@ -501,10 +463,29 @@
return set_error(SCRIPT_ERR_CHECKMULTISIGVERIFY)
end
end
when OP_RETURN
return set_error(SCRIPT_ERR_OP_RETURN)
+ when OP_COLOR
+ # Color id is not permitted in p2sh redeem script
+ return set_error(SCRIPT_ERR_OP_COLOR_UNEXPECTED) if is_redeem_script
+
+ # if Color id is already initialized this must be an extra
+ return set_error(SCRIPT_ERR_OP_COLOR_MULTIPLE) if color_id && color_id.type != Tapyrus::Color::TokenTypes::NONE
+
+ # color id is not allowed inside OP_IF
+ return set_error(SCRIPT_ERR_OP_COLOR_IN_BRANCH) unless flow_stack.empty?
+
+ # pop one stack element and verify that it exists
+ return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size() < 1
+
+ color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(stack.last.htb)
+
+ # check ColorIdentifier is valid
+ return set_error(SCRIPT_ERR_OP_COLOR_ID_INVALID) unless color_id.valid?
+
+ stack.pop
else
return set_error(SCRIPT_ERR_BAD_OPCODE)
end
end
end
@@ -584,46 +565,44 @@
else
false
end
end
- def check_signature_encoding(sig)
+ def check_ecdsa_signature_encoding(sig, data_sig = false)
return true if sig.size.zero?
- if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !Key.valid_signature_encoding?(sig.htb)
+ if !Key.valid_signature_encoding?(sig.htb, data_sig)
return set_error(SCRIPT_ERR_SIG_DER)
- elsif flag?(SCRIPT_VERIFY_LOW_S) && !low_der_signature?(sig)
+ elsif !low_der_signature?(sig, data_sig)
return false
- elsif flag?(SCRIPT_VERIFY_STRICTENC) && !defined_hashtype_signature?(sig)
+ elsif !data_sig && !defined_hashtype_signature?(sig)
return set_error(SCRIPT_ERR_SIG_HASHTYPE)
end
true
end
- def low_der_signature?(sig)
- return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb)
+ def check_schnorr_signature_encoding(sig, data_sig = false)
+ return false unless sig.htb.bytesize == (data_sig ? 64 : 65)
+ return set_error(SCRIPT_ERR_SIG_HASHTYPE) if !data_sig && !defined_hashtype_signature?(sig)
+ true
+ end
+
+ def low_der_signature?(sig, data_sig = false)
+ return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb, data_sig)
return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb)
true
end
def defined_hashtype_signature?(signature)
sig = signature.htb
return false if sig.empty?
s = sig.unpack('C*')
hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
- hash_type &= (~(Tapyrus::SIGHASH_FORK_ID)) if Tapyrus.chain_params.fork_chain? # for fork coin.
return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
true
end
- def check_pubkey_encoding(pubkey, sig_version)
- if flag?(SCRIPT_VERIFY_STRICTENC) && !Key.compress_or_uncompress_pubkey?(pubkey)
- return set_error(SCRIPT_ERR_PUBKEYTYPE)
- end
- # Only compressed keys are accepted in segwit
- if flag?(SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) &&
- sig_version == :witness_v0 && !Key.compress_pubkey?(pubkey)
- return set_error(SCRIPT_ERR_WITNESS_PUBKEYTYPE)
- end
+ def check_pubkey_encoding(pubkey)
+ return set_error(SCRIPT_ERR_PUBKEYTYPE) unless Key.compress_or_uncompress_pubkey?(pubkey)
true
end
def minimal_push?(data, opcode)
if data.bytesize.zero?
\ No newline at end of file