From 72422ce396b8eba7b1a72c171c2f07dae691d1b5 Mon Sep 17 00:00:00 2001 From: Johnson Lau Date: Fri, 11 Sep 2020 14:34:02 -0700 Subject: Implement Tapscript script validation rules (BIP 342) This adds a new `SigVersion::TAPSCRIPT`, makes the necessary interpreter changes to make it implement BIP342, and uses them for leaf version 0xc0 in Taproot script path spends. --- src/script/interpreter.cpp | 186 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 168 insertions(+), 18 deletions(-) (limited to 'src/script/interpreter.cpp') diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index a9d28c25d2..5735e7df66 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -342,13 +342,10 @@ public: }; } -/** Helper for OP_CHECKSIG and OP_CHECKSIGVERIFY - * - * A return value of false means the script fails entirely. When true is returned, the - * fSuccess variable indicates whether the signature check itself succeeded. - */ -static bool EvalChecksig(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess) +static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess) { + assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0); + // Subset of script starting at the most recent codeseparator CScript scriptCode(pbegincodehash, pend); @@ -371,6 +368,66 @@ static bool EvalChecksig(const valtype& vchSig, const valtype& vchPubKey, CScrip return true; } +static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success) +{ + assert(sigversion == SigVersion::TAPSCRIPT); + + /* + * The following validation sequence is consensus critical. Please note how -- + * upgradable public key versions precede other rules; + * the script execution fails when using empty signature with invalid public key; + * the script execution fails when using non-empty invalid signature. + */ + success = !sig.empty(); + if (success) { + // Implement the sigops/witnesssize ratio test. + // Passing with an upgradable public key version is also counted. + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + } + if (pubkey.size() == 0) { + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + } else if (pubkey.size() == 32) { + if (success && !checker.CheckSchnorrSignature(sig, pubkey, sigversion, execdata, serror)) { + return false; // serror is set + } + } else { + /* + * New public key version softforks should be defined before this `else` block. + * Generally, the new code should not do anything but failing the script execution. To avoid + * consensus bugs, it should not modify any existing values (including `success`). + */ + if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE); + } + } + + return true; +} + +/** Helper for OP_CHECKSIG, OP_CHECKSIGVERIFY, and (in Tapscript) OP_CHECKSIGADD. + * + * A return value of false means the script fails entirely. When true is returned, the + * success variable indicates whether the signature check itself succeeded. + */ +static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success) +{ + switch (sigversion) { + case SigVersion::BASE: + case SigVersion::WITNESS_V0: + return EvalChecksigPreTapscript(sig, pubkey, pbegincodehash, pend, flags, checker, sigversion, serror, success); + case SigVersion::TAPSCRIPT: + return EvalChecksigTapscript(sig, pubkey, execdata, flags, checker, sigversion, serror, success); + case SigVersion::TAPROOT: + // Key path spending in Taproot has no script, so this is unreachable. + break; + } + assert(false); +} + bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) { static const CScriptNum bnZero(0); @@ -381,6 +438,9 @@ bool EvalScript(std::vector >& stack, const CScript& // static const valtype vchZero(0); static const valtype vchTrue(1, 1); + // sigversion cannot be TAPROOT here, as it admits no script execution. + assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT); + CScript::const_iterator pc = script.begin(); CScript::const_iterator pend = script.end(); CScript::const_iterator pbegincodehash = script.begin(); @@ -389,15 +449,18 @@ bool EvalScript(std::vector >& stack, const CScript& ConditionStack vfExec; std::vector altstack; set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR); - if (script.size() > MAX_SCRIPT_SIZE) + if ((sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) && script.size() > MAX_SCRIPT_SIZE) { return set_error(serror, SCRIPT_ERR_SCRIPT_SIZE); + } int nOpCount = 0; bool fRequireMinimal = (flags & SCRIPT_VERIFY_MINIMALDATA) != 0; + uint32_t opcode_pos = 0; + execdata.m_codeseparator_pos = 0xFFFFFFFFUL; + execdata.m_codeseparator_pos_init = true; try { - while (pc < pend) - { + for (; pc < pend; ++opcode_pos) { bool fExec = vfExec.all_true(); // @@ -408,9 +471,12 @@ bool EvalScript(std::vector >& stack, const CScript& if (vchPushValue.size() > MAX_SCRIPT_ELEMENT_SIZE) return set_error(serror, SCRIPT_ERR_PUSH_SIZE); - // Note how OP_RESERVED does not count towards the opcode limit. - if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT) - return set_error(serror, SCRIPT_ERR_OP_COUNT); + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) { + // Note how OP_RESERVED does not count towards the opcode limit. + if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT) { + return set_error(serror, SCRIPT_ERR_OP_COUNT); + } + } if (opcode == OP_CAT || opcode == OP_SUBSTR || @@ -568,6 +634,15 @@ bool EvalScript(std::vector >& stack, const CScript& if (stack.size() < 1) return set_error(serror, SCRIPT_ERR_UNBALANCED_CONDITIONAL); valtype& vch = stacktop(-1); + // Tapscript requires minimal IF/NOTIF inputs as a consensus rule. + if (sigversion == SigVersion::TAPSCRIPT) { + // The input argument to the OP_IF and OP_NOTIF opcodes must be either + // exactly 0 (the empty vector) or exactly 1 (the one-byte vector with value 1). + if (vch.size() > 1 || (vch.size() == 1 && vch[0] != 1)) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_MINIMALIF); + } + } + // Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF. if (sigversion == SigVersion::WITNESS_V0 && (flags & SCRIPT_VERIFY_MINIMALIF)) { if (vch.size() > 1) return set_error(serror, SCRIPT_ERR_MINIMALIF); @@ -1001,6 +1076,7 @@ bool EvalScript(std::vector >& stack, const CScript& // Hash starts after the code separator pbegincodehash = pc; + execdata.m_codeseparator_pos = opcode_pos; } break; @@ -1015,7 +1091,7 @@ bool EvalScript(std::vector >& stack, const CScript& valtype& vchPubKey = stacktop(-1); bool fSuccess = true; - if (!EvalChecksig(vchSig, vchPubKey, pbegincodehash, pend, flags, checker, sigversion, serror, fSuccess)) return false; + if (!EvalChecksig(vchSig, vchPubKey, pbegincodehash, pend, execdata, flags, checker, sigversion, serror, fSuccess)) return false; popstack(stack); popstack(stack); stack.push_back(fSuccess ? vchTrue : vchFalse); @@ -1029,9 +1105,32 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_CHECKSIGADD: + { + // OP_CHECKSIGADD is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + // (sig num pubkey -- num) + if (stack.size() < 3) return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype& sig = stacktop(-3); + const CScriptNum num(stacktop(-2), fRequireMinimal); + const valtype& pubkey = stacktop(-1); + + bool success = true; + if (!EvalChecksig(sig, pubkey, pbegincodehash, pend, execdata, flags, checker, sigversion, serror, success)) return false; + popstack(stack); + popstack(stack); + popstack(stack); + stack.push_back((num + (success ? 1 : 0)).getvch()); + } + break; + case OP_CHECKMULTISIG: case OP_CHECKMULTISIGVERIFY: { + if (sigversion == SigVersion::TAPSCRIPT) return set_error(serror, SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG); + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) int i = 1; @@ -1392,10 +1491,19 @@ static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak"); template bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache) { - uint8_t ext_flag; + uint8_t ext_flag, key_version; switch (sigversion) { case SigVersion::TAPROOT: ext_flag = 0; + // key_version is not used and left uninitialized. + break; + case SigVersion::TAPSCRIPT: + ext_flag = 1; + // key_version must be 0 for now, representing the current version of + // 32-byte public keys in the tapscript signature opcode execution. + // An upgradable public key version (with a size not 32-byte) may + // request a different key_version with a new sigversion. + key_version = 0; break; default: assert(false); @@ -1452,6 +1560,15 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata ss << sha_single_output.GetSHA256(); } + // Additional data for BIP 342 signatures + if (sigversion == SigVersion::TAPSCRIPT) { + assert(execdata.m_tapleaf_hash_init); + ss << execdata.m_tapleaf_hash; + ss << key_version; + assert(execdata.m_codeseparator_pos_init); + ss << execdata.m_codeseparator_pos; + } + hash_out = ss.GetSHA256(); return true; } @@ -1561,9 +1678,13 @@ bool GenericTransactionSignatureChecker::CheckECDSASignature(const std::vecto template bool GenericTransactionSignatureChecker::CheckSchnorrSignature(Span sig, Span pubkey_in, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror) const { - assert(sigversion == SigVersion::TAPROOT); + assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT); // Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this. assert(pubkey_in.size() == 32); + // Note that in Tapscript evaluation, empty signatures are treated specially (invalid signature that does not + // abort script execution). This is implemented in EvalChecksigTapscript, which won't invoke + // CheckSchnorrSignature in that case. In other contexts, they are invalid like every other signature with + // size different from 64 or 65. if (sig.size() != 64 && sig.size() != 65) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE); XOnlyPubKey pubkey{pubkey_in}; @@ -1674,6 +1795,28 @@ static bool ExecuteWitnessScript(const Span& stack_span, const CS { std::vector stack{stack_span.begin(), stack_span.end()}; + if (sigversion == SigVersion::TAPSCRIPT) { + // OP_SUCCESSx processing overrides everything, including stack element size limits + CScript::const_iterator pc = scriptPubKey.begin(); + while (pc < scriptPubKey.end()) { + opcodetype opcode; + if (!scriptPubKey.GetOp(pc, opcode)) { + // Note how this condition would not be reached if an unknown OP_SUCCESSx was found + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + } + // New opcodes will be listed here. May use a different sigversion to modify existing opcodes. + if (IsOpSuccess(opcode)) { + if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS); + } + return set_success(serror); + } + } + + // Tapscript enforces initial stack size limits (altstack is empty here) + if (stack.size() > MAX_STACK_SIZE) return set_error(serror, SCRIPT_ERR_STACK_SIZE); + } + // Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack for (const valtype& elem : stack) { if (elem.size() > MAX_SCRIPT_ELEMENT_SIZE) return set_error(serror, SCRIPT_ERR_PUSH_SIZE); @@ -1688,12 +1831,12 @@ static bool ExecuteWitnessScript(const Span& stack_span, const CS return true; } -static bool VerifyTaprootCommitment(const std::vector& control, const std::vector& program, const CScript& script) +static bool VerifyTaprootCommitment(const std::vector& control, const std::vector& program, const CScript& script, uint256& tapleaf_hash) { const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; const XOnlyPubKey p{uint256(std::vector(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; const XOnlyPubKey q{uint256(program)}; - uint256 tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256(); + tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256(); uint256 k = tapleaf_hash; for (int i = 0; i < path_len; ++i) { CHashWriter ss_branch{HASHER_TAPBRANCH}; @@ -1766,9 +1909,16 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) { return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE); } - if (!VerifyTaprootCommitment(control, program, exec_script)) { + if (!VerifyTaprootCommitment(control, program, exec_script, execdata.m_tapleaf_hash)) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); } + execdata.m_tapleaf_hash_init = true; + if ((control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) { + // Tapscript (leaf version 0xc0) + execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack, PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET; + execdata.m_validation_weight_left_init = true; + return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror); + } if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) { return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION); } -- cgit v1.2.3