From f3a04a58b2dfa374ec234f8ca4c92aba0570741f Mon Sep 17 00:00:00 2001 From: kevkevinpal Date: Wed, 27 Nov 2024 09:09:44 -0500 Subject: [PATCH] Implement OP_CHECKSIGFROMSTACK Some code and ideas from Elements by stevenroose, and sanket1729 Porting help from moonsettler Tests added to the transaction tests framework. --- src/policy/policy.h | 3 +- src/pubkey.cpp | 4 +- src/pubkey.h | 2 +- src/script/interpreter.cpp | 73 ++++++++++++++++++++++++ src/script/interpreter.h | 6 ++ src/script/script.cpp | 3 + src/script/script.h | 3 + src/test/data/tx_invalid.json | 8 +++ src/test/data/tx_valid.json | 29 ++++++++++ src/test/transaction_tests.cpp | 1 + test/functional/test_framework/script.py | 2 +- 11 files changed, 129 insertions(+), 5 deletions(-) diff --git a/src/policy/policy.h b/src/policy/policy.h index 424db10055b7a..958274841bd2b 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -138,7 +138,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI SCRIPT_VERIFY_CONST_SCRIPTCODE | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION | SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS | - SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE}; + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE | + SCRIPT_VERIFY_CHECKSIGFROMSTACK}; /** For convenience, standard but not mandatory verify flags. */ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS}; diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 11e1b4abb51af..e1781aea63d66 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 15d7e7bc07dbb..4103ca951d1d5 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 c969ce45f1249..16e2df120c491 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -343,6 +343,48 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu return true; } +static bool EvalChecksigFromStack(const valtype& sig, const valtype& msg, const valtype& pubkey_in, ScriptExecutionData& execdata, unsigned int flags, SigVersion sigversion, ScriptError* serror, bool& success) +{ + assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || 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 && sigversion == SigVersion::TAPSCRIPT) { + // 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_in.size() == 0) { + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + } else if (pubkey_in.size() == 32) { + if (!success) return true; + if (sig.size() != 64) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE); + + XOnlyPubKey pubkey{pubkey_in}; + + if (!pubkey.VerifySchnorr(msg, sig)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG); + } 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; +} + 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); @@ -1213,6 +1255,31 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_CHECKSIGFROMSTACK: { + // DISCOURAGE for OP_CHECKSIGFROMSTACK is handled in OP_SUCCESS handling + // OP_CHECKSIGFROMSTACK is only available in Tapscript + if (opcode == OP_CHECKSIGFROMSTACK && (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0)) { + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + } + + // sig message pubkey + if (stack.size() < 3) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype& vchSigIn = stacktop(-3); + const valtype& vchMsg = stacktop(-2); + const valtype& vchPubKey = stacktop(-1); + + bool fSuccess = true; + if (!EvalChecksigFromStack(vchSigIn, vchMsg, vchPubKey, execdata, flags, sigversion, serror, fSuccess)) return false; + + popstack(stack); + popstack(stack); + popstack(stack); + stack.push_back(fSuccess ? vchTrue : vchFalse); + break; + } + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1798,6 +1865,12 @@ static bool ExecuteWitnessScript(const Span& stack_span, const CS // Note how this condition would not be reached if an unknown OP_SUCCESSx was found return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } + if (opcode == OP_CHECKSIGFROMSTACK) { + if (flags & SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK) + return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS); + if (flags & SCRIPT_VERIFY_CHECKSIGFROMSTACK) continue; + return set_success(serror); + } // 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) { diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 836c2e7982a20..f6c420bb60817 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -143,6 +143,12 @@ enum : uint32_t { // Making unknown public key versions (in BIP 342 scripts) non-standard SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20), + // Validating OP_CHECKSIGFROMSTACK + SCRIPT_VERIFY_CHECKSIGFROMSTACK = (1U << 21), + + // Making OP_CHECKSIGFROMSTACK non-standard + SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK = (1U << 22), + // Constants to point to the highest flag in use. Add new flags above this line. // SCRIPT_VERIFY_END_MARKER diff --git a/src/script/script.cpp b/src/script/script.cpp index a3d13b9845a39..67839b38d7952 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -149,6 +149,9 @@ std::string GetOpName(opcodetype opcode) // Opcode added by BIP 342 (Tapscript) case OP_CHECKSIGADD : return "OP_CHECKSIGADD"; + // Tapscript expansion + case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK"; + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; default: diff --git a/src/script/script.h b/src/script/script.h index dbbed45c6004d..1f2d44659e987 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -208,6 +208,9 @@ enum opcodetype // Opcode added by BIP 342 (Tapscript) OP_CHECKSIGADD = 0xba, + // Tapscript expansion + OP_CHECKSIGFROMSTACK = 0xcc, + OP_INVALIDOPCODE = 0xff, }; diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index a47bc8f3666d7..37c3936f2e7cc 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -393,5 +393,13 @@ ["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 1, "2 0x48 0x3045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 3 CHECKMULTISIG"]], "0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000", "CONST_SCRIPTCODE"], + ["Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data"], + [[["a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597", + 0, + "1 0x20 0x6e929e9354a357e9a1254feac061741a11c66508786c66b3b29edc79b9c46e19", + 155000]], +"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f04902000000000022512040104c71081b266fdf4008db8b0a1c3291f2e1cb680753936de9b76dac45a6ef0340b5258eeb9df148d499d14b8e23fe5315a230b7f1dee497a04605426ffe068f2e0920c9b63ba28b1c6cea39c0e659af1658825d23e859c5ae773a0be996f1c4744520feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef204835c505a762f5e55c2e8eda1c05437d973809a0236178510208a6ac3f7632bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "P2SH,WITNESS,TAPROOT,CHECKSIGFROMSTACK"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 70df0d0f697d0..bb52927010ad5 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -520,5 +520,34 @@ [[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]], "0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", "NONE"], + ["Test OP_CHECKSIGFROMSTACK"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0xed98cc178a5e3f2537ec8bf5ab9a14e56b8a188d666ba6ce788405e849ba7da8", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f19560340cd3e61f2754dd13e51a6d86d18092f795c626d36deaf0cf076a87648d9f4e4cfceaaa8e8a7eee1ee13dd09ef2c14eedd475f4e9adcf8a2391b910271b2203aa24320deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK"], + ["Test OP_CHECKSIGFROMSTACK succeeds with unknown key type"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0xde96616e5e3961cbbd7bab3ea0e6b6e1ace088299857136fbb3703454c784afb", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f19560340cd3e61f2754dd13e51a6d86d18092f795c626d36deaf0cf076a87648d9f4e4cfceaaa8e8a7eee1ee13dd09ef2c14eedd475f4e9adcf8a2391b910271b2203aa24420deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef21038fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK,DISCOURAGE_UPGRADABLE_PUBKEYTYPE"], + ["Test OP_CHECKSIGFROMSTACK yields 0 for 0-sig"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0x7f3db202bc0db8c15de91c5da0dd64bd52ae81f5847cda623e1304c524cad314", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603004520deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK"], + ["Test OP_CHECKSIGFROMSTACK, shorter message"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0x313a784205aef89c9d203c1e4cfacd2e31fa55f42dcd77e5e2db9d0513d50827", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603403c5a935ce7a3856bc3e75eae403a21ff2e5a9f919c0f6f4d6bf7f58c834c13484882fc6f98587fe48e6945a49c0ca4fc62fb5f641a216ea62ac2dbc0071976833411636865636b73696766726f6d737461636b208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 9bacbb4d8948a..bc0a8320fae1b 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -65,6 +65,7 @@ static std::map mapFlagNames = { {std::string("DISCOURAGE_UPGRADABLE_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE}, {std::string("DISCOURAGE_OP_SUCCESS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS}, {std::string("DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION}, + {std::string("DISCOURAGE_CHECKSIGFROMSTACK"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK}, }; unsigned int ParseScriptFlags(std::string strFlags) diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 3275517888d11..95535ef3a25ec 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 >= 0xbb 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 >= 0xbb and o <= 0xcb) or (o >= 0xcd and o <= 0xfe)