diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 11e1b4abb51afd..e1781aea63d66d 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -217,12 +217,12 @@ bool XOnlyPubKey::IsFullyValid() const return secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data()); } -bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span sigbytes) const +bool XOnlyPubKey::VerifySchnorr(const Span msg, Span sigbytes) const { assert(sigbytes.size() == 64); secp256k1_xonly_pubkey pubkey; if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false; - return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey); + return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.data(), msg.size(), &pubkey); } static const HashWriter HASHER_TAPTWEAK{TaggedHash("TapTweak")}; diff --git a/src/pubkey.h b/src/pubkey.h index 2b655c3f73b572..37240dff37368a 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -258,7 +258,7 @@ class XOnlyPubKey * * sigbytes must be exactly 64 bytes. */ - bool VerifySchnorr(const uint256& msg, Span sigbytes) const; + bool VerifySchnorr(const Span msg, Span sigbytes) const; /** Compute the Taproot tweak as specified in BIP341, with *this as internal * key: diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 132eaf7418a118..5217f97a2e98d5 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -626,7 +626,7 @@ bool EvalScript(std::vector >& stack, const CScript& } break; - case OP_NOP1: case OP_NOP5: + case OP_NOP1: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: { if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) @@ -1257,6 +1257,37 @@ bool EvalScript(std::vector >& stack, const CScript& break; } + case OP_CHECKSIGFROMSTACK: + case OP_CHECKSIGFROMSTACKVERIFY: { + // sig data pubkey + if (stack.size() < 3) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& vchSig = stacktop(-3); + valtype& vchData = stacktop(-2); + valtype& vchPubKey = stacktop(-1); + + std::vector vchSigAll(vchSig.begin(), vchSig.begin() + vchSig.size()); + // Push SIGHASH_ALL to pass checks - stripped by MessageSignatureChecker + vchSigAll.push_back(SIGHASH_ALL); + + bool fSuccess = true; + MessageSignatureChecker checker{vchData}; + + if (!EvalChecksig(vchSigAll, vchPubKey, pbegincodehash, pend, execdata, flags, checker, sigversion, serror, fSuccess)) return false; + + if (opcode == OP_CHECKSIGFROMSTACKVERIFY) { + if (!fSuccess) return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY); + break; + } + + popstack(stack); + popstack(stack); + popstack(stack); + stack.push_back(fSuccess ? vchTrue : vchFalse); + break; + } + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1918,6 +1949,44 @@ bool GenericTransactionSignatureChecker::CheckDefaultCheckTemplateVerifyHash( template class GenericTransactionSignatureChecker; template class GenericTransactionSignatureChecker; +bool MessageSignatureChecker::CheckECDSASignature(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const +{ + valtype vchHash(CSHA256::OUTPUT_SIZE); + CSHA256().Write(this->msg.data(), this->msg.size()).Finalize(vchHash.data()); + + CPubKey pubkey(vchPubKey); + if (!pubkey.IsValid()) + return false; + + // Strip dummy SIGHASH_ALL added to pass format checks + std::vector vchSig(vchSigIn); + if (vchSig.empty()) + return false; + vchSig.pop_back(); + + if (!pubkey.Verify(uint256(vchHash), vchSig)) + return false; + + return true; +} + +bool MessageSignatureChecker::CheckSchnorrSignature(Span sig, Span pubkey_in, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const +{ + 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) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE); + + XOnlyPubKey pubkey{pubkey_in}; + + if (!pubkey.VerifySchnorr(this->msg, sig)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG); + return true; +} + static bool ExecuteWitnessScript(const Span& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror) { std::vector stack{stack_span.begin(), stack_span.end()}; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 5ce1d2b53dedb4..100a216e440afb 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -304,6 +304,20 @@ class BaseSignatureChecker virtual ~BaseSignatureChecker() {} }; +/** + * Class for performing signature checking on an arbitrary length message + */ +class MessageSignatureChecker : public BaseSignatureChecker +{ +private: + const Span msg; + +public: + MessageSignatureChecker(const Span msgIn) : msg(msgIn) {} + bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; + bool CheckSchnorrSignature(Span sig, Span pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override; +}; + /** Enum to specify what *TransactionSignatureChecker's behavior should be * when dealing with missing transaction data. */ diff --git a/src/script/script.cpp b/src/script/script.cpp index f66e0c98def26f..af84740e18c81d 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -139,7 +139,7 @@ std::string GetOpName(opcodetype opcode) case OP_CHECKLOCKTIMEVERIFY : return "OP_CHECKLOCKTIMEVERIFY"; case OP_CHECKSEQUENCEVERIFY : return "OP_CHECKSEQUENCEVERIFY"; case OP_CHECKTEMPLATEVERIFY : return "OP_CHECKTEMPLATEVERIFY"; - case OP_NOP5 : return "OP_NOP5"; + case OP_CHECKSIGFROMSTACKVERIFY: return "OP_CHECKSIGFROMSTACKVERIFY"; case OP_NOP6 : return "OP_NOP6"; case OP_NOP7 : return "OP_NOP7"; case OP_NOP8 : return "OP_NOP8"; @@ -149,7 +149,9 @@ std::string GetOpName(opcodetype opcode) // Opcode added by BIP 342 (Tapscript) case OP_CHECKSIGADD : return "OP_CHECKSIGADD"; + // Tapscript expansion case OP_INTERNALKEY : return "OP_INTERNALKEY"; + case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK"; case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; @@ -168,7 +170,8 @@ unsigned int CScript::GetSigOpCount(bool fAccurate) const opcodetype opcode; if (!GetOp(pc, opcode)) break; - if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY) + if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY || + opcode == OP_CHECKSIGFROMSTACK || opcode == OP_CHECKSIGFROMSTACKVERIFY) n++; else if (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY) { @@ -352,7 +355,7 @@ bool IsOpSuccess(const opcodetype& opcode) return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) || (opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) || (opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) || - (opcode >= 188 && opcode <= 254); + (opcode >= 189 && opcode <= 254); } bool CheckMinimalPush(const std::vector& data, opcodetype opcode) { diff --git a/src/script/script.h b/src/script/script.h index 6d7e8282c64bea..cfe2030fe05ffe 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -199,7 +199,8 @@ enum opcodetype OP_NOP3 = OP_CHECKSEQUENCEVERIFY, OP_CHECKTEMPLATEVERIFY = 0xb3, OP_NOP4 = OP_CHECKTEMPLATEVERIFY, - OP_NOP5 = 0xb4, + OP_CHECKSIGFROMSTACKVERIFY = 0xb4, + OP_NOP5 = OP_CHECKSIGFROMSTACKVERIFY, OP_NOP6 = 0xb5, OP_NOP7 = 0xb6, OP_NOP8 = 0xb7, @@ -208,7 +209,10 @@ enum opcodetype // Opcode added by BIP 342 (Tapscript) OP_CHECKSIGADD = 0xba, + + // Tapscript expansion OP_INTERNALKEY = 0xbb, + OP_CHECKSIGFROMSTACK = 0xbc, OP_INVALIDOPCODE = 0xff, }; diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 0794d3361ddadc..3826df3dce861c 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -924,4 +924,4 @@ def taproot_construct(pubkey, scripts=None, treat_internal_as_infinity=False): return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked) def is_op_success(o): - return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbc and o <= 0xfe) + return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbd and o <= 0xfe)