diff --git a/src/consensus/params.h b/src/consensus/params.h index 628fb858c2f6..29c29d718421 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -35,6 +35,7 @@ enum DeploymentPos : uint16_t { DEPLOYMENT_CHECKTEMPLATEVERIFY, // Deployment of CTV (BIP 119) DEPLOYMENT_ANYPREVOUT, DEPLOYMENT_OP_CAT, + DEPLOYMENT_CHECKSIGFROMSTACK, // Deployment of CSFS (BIP 348) // NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp MAX_VERSION_BITS_DEPLOYMENTS }; diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 8b5736f17b18..3899d58fde67 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -27,6 +27,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.name =*/ "opcat", /*.gbt_force =*/ true, }, + { + /*.name =*/ "checksigfromstack", + /*.gbt_force =*/ true, + }, }; std::string DeploymentName(Consensus::BuriedDeployment dep) @@ -95,6 +99,8 @@ const std::map g_verify_flag_names{ FLAG_NAME(DISCOURAGE_ANYPREVOUT), FLAG_NAME(OP_CAT), FLAG_NAME(DISCOURAGE_OP_CAT), + FLAG_NAME(CHECKSIGFROMSTACK), + FLAG_NAME(DISCOURAGE_CHECKSIGFROMSTACK), }; #undef FLAG_NAME diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 71dcbe30db6d..2a6b2f39ebde 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -145,6 +145,7 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .never = true}; consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .never = true}; consensus.vDeployments[Consensus::DEPLOYMENT_OP_CAT] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .never = true}; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKSIGFROMSTACK] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .never = true}; consensus.nMinimumChainWork = uint256{"000000000000000000000000000000000000000088e186b70e0862c193ec44d6"}; consensus.defaultAssumeValid = uint256{"000000000000000000011c5890365bdbe5d25b97ce0057589acaef4f1a57263f"}; // 856760 @@ -264,6 +265,7 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .never = true}; consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .never = true}; consensus.vDeployments[Consensus::DEPLOYMENT_OP_CAT] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .never = true}; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKSIGFROMSTACK] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .never = true}; consensus.nMinimumChainWork = uint256{"000000000000000000000000000000000000000000000f209695166be8b61fa9"}; consensus.defaultAssumeValid = uint256{"000000000000000465b1a66c9f386308e8c75acef9201f3f577811da09fc90ad"}; // 2873500 @@ -355,6 +357,7 @@ class CTestNet4Params : public CChainParams { consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.activate = 0x30000000, .abandon = 0, .never = true}; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKSIGFROMSTACK] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .never = true}; consensus.nMinimumChainWork = uint256{"00000000000000000000000000000000000000000000005faa15d02e6202f3ba"}; consensus.defaultAssumeValid = uint256{"000000005be348057db991fa5d89fe7c4695b667cfb311391a8db374b6f681fd"}; // 39550 @@ -508,6 +511,7 @@ class SigNetParams : public CChainParams { .activate = 0x62000100, .abandon = 0x42000100, }; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKSIGFROMSTACK] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .never = true}; RenounceDeployments(options.renounce, consensus.vDeployments); @@ -584,6 +588,7 @@ class CRegTestParams : public CChainParams consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .always = true}; consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .always = true}; consensus.vDeployments[Consensus::DEPLOYMENT_OP_CAT] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .always = true}; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKSIGFROMSTACK] = SetupDeployment{.activate = 0x62000100, .abandon = 0x42000100, .always = true}; consensus.nMinimumChainWork = uint256{}; consensus.defaultAssumeValid = uint256{}; diff --git a/src/policy/policy.h b/src/policy/policy.h index ee93555f8e1d..67e75f86fdf9 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -121,6 +121,7 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH | SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH | SCRIPT_VERIFY_ANYPREVOUT | + SCRIPT_VERIFY_CHECKSIGFROMSTACK | SCRIPT_VERIFY_OP_CAT }; diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 13e3c2dbe07d..1ffe59dac417 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -229,12 +229,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 ae34ddd0afdd..4f32f5383fe2 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -263,7 +263,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/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index ef05d4c3d2d2..b68fe8dd4019 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1396,6 +1396,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY); SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ANYPREVOUT); SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_OP_CAT); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKSIGFROMSTACK); return softforks; } } // anon namespace diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index b113efb54d55..df8ea4e0fb2f 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -343,6 +343,72 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu return true; } +/** + * Returns false when script execution must immediately fail. + * `success_out` must be set when returning 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_out) +{ + assert(sigversion == SigVersion::TAPSCRIPT); + + /* + * Implementation follows the BIP348 spec as closely as possible for correctness. + */ + + // If the public key size is zero, the script MUST fail and terminate immediately. + if (pubkey_in.size() == 0) { + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + } + + // If the public key size is 32 bytes, it is considered to be a public key as described in BIP 340: + // If the signature is not the empty vector, the signature is validated against the public key and message according to BIP 340. Validation failure in this case immediately terminates script execution with failure. + if (pubkey_in.size() == 32) { + if (!sig.empty()) { + 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); + } + } + } + + // If the public key size is not zero and not 32 bytes; the public key is of an unknown public key type. + // Signature verification for unknown public key types succeeds as if signature verification for a known + // public key type had succeeded. + if (pubkey_in.size() != 0 && pubkey_in.size() != 32) { + /* + * New public key version softforks should be defined before this `if` block. + * Generally, the new code should not do anything but terminating the script execution. To avoid + * consensus bugs, it should not modify any existing values (including `success_out`). + */ + if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE); + } + } + + // If the script did not fail and terminate before this step, regardless of the public key type: + + if (sig.empty()) { + // If the signature is the empty vector: An empty vector is pushed onto the stack, and execution continues with the next opcode. + success_out = false; + } else { + // If the signature is not the empty vector: + // - The opcode is counted towards the sigops budget as described in BIP 342. + // - A 1-byte value 0x01 is pushed onto the stack. + 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); + } + success_out = true; + } + + 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); @@ -1283,6 +1349,40 @@ 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 (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) { + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + } + + // If fewer than 3 elements are on the stack, the script MUST fail and terminate immediately + if (stack.size() < 3) { + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + } + + // The public key (top element) + // message (second to top element), + // and signature (third from top element) are read from the stack. + const valtype& pubkey = stacktop(-1); + const valtype& msg = stacktop(-2); + const valtype& sig = stacktop(-3); + + bool push_success = true; + if (!EvalChecksigFromStack(sig, msg, pubkey, execdata, flags, sigversion, serror, push_success)) { + return false; // serror set by EvalChecksigFromStack + } + + popstack(stack); + popstack(stack); + popstack(stack); + + stack.push_back(push_success ? vchTrue : vchFalse); + + break; + } + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1988,6 +2088,12 @@ std::optional CheckTapscriptOpSuccess(const CScript& exec_script, unsigned } else if (!(flags & SCRIPT_VERIFY_OP_CAT)) { return set_success(serror); } + } else if (opcode == OP_CHECKSIGFROMSTACK) { + if (flags & SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_CHECKSIGFROMSTACK); + } else if (!(flags & SCRIPT_VERIFY_CHECKSIGFROMSTACK)) { + return set_success(serror); + } } else { // OP_SUCCESS behaviour if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) { diff --git a/src/script/interpreter.h b/src/script/interpreter.h index ff041ca611ee..dc9720e6e072 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -163,6 +163,12 @@ enum : uint32_t { SCRIPT_VERIFY_OP_CAT = (1U << 26), SCRIPT_VERIFY_DISCOURAGE_OP_CAT = (1U << 27), + // Validating OP_CHECKSIGFROMSTACK + SCRIPT_VERIFY_CHECKSIGFROMSTACK = (1U << 28), + + // Making OP_CHECKSIGFROMSTACK non-standard + SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK = (1U << 29), + // 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 fc77d70a9e9e..1cca9d5b9204 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"; + // Opcodes added by BIP 348 + 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 1bd615c6e816..857f96a28595 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -209,6 +209,9 @@ enum opcodetype // Opcode added by BIP 342 (Tapscript) OP_CHECKSIGADD = 0xba, + // Opcode added by BIP 348 aka OP_SUCCESS204 + OP_CHECKSIGFROMSTACK = 0xcc, + OP_INVALIDOPCODE = 0xff, }; diff --git a/src/script/script_error.h b/src/script/script_error.h index 83826bfc301a..85477fe580a5 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -62,6 +62,7 @@ typedef enum ScriptError_t SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE, SCRIPT_ERR_DISCOURAGE_ANYPREVOUT, SCRIPT_ERR_DISCOURAGE_OP_CAT, + SCRIPT_ERR_DISCOURAGE_OP_CHECKSIGFROMSTACK, /* segregated witness */ SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH, diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index 185f94beec73..0b7364683107 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -488,5 +488,13 @@ "020000000297e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a2000000000151000000000b4c12e6dbe974dadd18ca139e6bce183817ac609f73213aa8aaeae5f123d6b6000000000151000000000ae80300000000000017a9143f163a8747557345ce2e6fe00c1894f2f281795e87d00700000000000017a9144cf13dfda93a7413b7e646611735656e5457657087b80b00000000000017a914868998b49df649c37a88d48c9d4a5b37290e507287a00f00000000000017a914034f9914a77571a6396482e9881745c92c3037c687881300000000000017a914a8238003e1732e2baf4334a8546d72be99af9bae87701700000000000017a91491dbac5d67d5941115a03fc7eaec09f31a5b4dfc87581b00000000000017a914e0c0f19fec3b2993b9c116c798b5429d4515596687401f00000000000017a914d6b40d98d94530f1a1eb57614680813c81a95ccd87282300000000000017a914fb0bfb072bb79611a4323981828108a3cf54b0a687102700000000000017a9149e2d11f06ba667e981b802af10be8dabd08eafff8700000000", "DEFAULT_CHECK_TEMPLATE_VERIFY_HASH"], +["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 e087557925c9..539a4592bfe4 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -672,5 +672,34 @@ "0200000002d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000001510000000082b77fb944a40756a6341ee425c85d5850f2acbe6d51a7bcd211929a764b8ec8000000000100000000000ae80300000000000017a914d63e77972529f4db5b32efaa4e06f66ae0b5dc0987d00700000000000017a91488b6705f8c9568c52b55ed712c257f84f64a49f587b80b00000000000017a9142be57e9a179f8d9ff8f33a788d4b54512ea9e36087a00f00000000000017a91429261b4f65796f618908de9f51669014e2e2e04f87881300000000000017a914e3a1e1d24cbba3ca9369248082988bad3ceafcfb87701700000000000017a91403801b0a9591f3b5a00a5ea60fb34dc12b4a691187581b00000000000017a91465248bc2c732db2d88db0b0d677c1514b101025b87401f00000000000017a91420021e3dc4e80c7192c1a3cd04026d22d0f8d38287282300000000000017a914df27596dbff2028791bd7692846e65d16d8fed0d87102700000000000017a9142ed128e911cab04d3277d3635f79d5e3d7e6f4c48700000000", "CLEANSTACK,DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH"], +["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/script_tests.cpp b/src/test/script_tests.cpp index 40b4830c2f94..720e263e4a6b 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -92,6 +92,7 @@ static ScriptErrorDesc script_errors[]={ {SCRIPT_ERR_OP_CODESEPARATOR, "OP_CODESEPARATOR"}, {SCRIPT_ERR_SIG_FINDANDDELETE, "SIG_FINDANDDELETE"}, {SCRIPT_ERR_DISCOURAGE_OP_CAT, "DISCOURAGE_OP_CAT"}, + {SCRIPT_ERR_DISCOURAGE_OP_CHECKSIGFROMSTACK, "DISCOURAGE_OP_CHECKSIGFROMSTACK"}, }; std::string FormatScriptFlags(uint32_t flags) @@ -1748,8 +1749,8 @@ BOOST_AUTO_TEST_CASE(formatscriptflags) // quick check that FormatScriptFlags reports any unknown/unexpected bits BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_P2SH), "P2SH"); BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_P2SH | (1u<<31)), "P2SH,0x80000000"); - BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_TAPROOT | (1u<<28)), "TAPROOT,0x10000000"); - BOOST_CHECK_EQUAL(FormatScriptFlags(1u<<28), "0x10000000"); + BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_TAPROOT | (1u<<30)), "TAPROOT,0x40000000"); + BOOST_CHECK_EQUAL(FormatScriptFlags(1u<<30), "0x40000000"); } diff --git a/src/validation.cpp b/src/validation.cpp index 261549219d35..5cd99eaa7ee3 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1255,6 +1255,7 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws) { Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY, SCRIPT_VERIFY_DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH }, { Consensus::DEPLOYMENT_ANYPREVOUT, SCRIPT_VERIFY_DISCOURAGE_ANYPREVOUT }, { Consensus::DEPLOYMENT_OP_CAT, SCRIPT_VERIFY_DISCOURAGE_OP_CAT }, + { Consensus::DEPLOYMENT_CHECKSIGFROMSTACK, SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK }, }); // Check input scripts and signatures. @@ -2429,6 +2430,11 @@ unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Chainstat flags |= SCRIPT_VERIFY_OP_CAT; } + // Enforce OP_CHECKSIGFROMSTACK (BIP348) + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CHECKSIGFROMSTACK)) { + flags |= SCRIPT_VERIFY_CHECKSIGFROMSTACK; + } + return flags; } diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index f45a11dd6c59..cb538680409d 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -52,10 +52,12 @@ OP_16, OP_2DROP, OP_2DUP, + OP_3DUP, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKSIG, OP_CHECKSIGADD, + OP_CHECKSIGFROMSTACK, OP_CHECKSIGVERIFY, OP_CODESEPARATOR, OP_DROP, @@ -1076,6 +1078,13 @@ def big_spend_inputs(ctx): # == Test for sigops ratio limit == + # BIP348 CSFS signatures are embedded directly into the tapleaves vs the witness stack + # since they do not introspect directly + CSFS_MSG = b'\x00\x00' + # Signature should pass even if random unknown key is used, just use real privkey + # to pass in case it's the defined pubkey + CSFS_SIG = sign_schnorr(secs[1], CSFS_MSG) + # Given a number n, and a public key pk, functions that produce a (CScript, sigops). Each script takes as # input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and # will execute sigops signature checks. @@ -1092,7 +1101,15 @@ def big_spend_inputs(ctx): lambda n, pk: (CScript([OP_DROP, OP_0, pk, OP_CHECKSIG, OP_NOT, OP_VERIFY, pk] + [OP_2DUP, OP_CHECKSIG, OP_VERIFY] * n + [OP_CHECKSIG]), n + 1), # n OP_CHECKSIGADDs and 1 OP_CHECKSIG, but also an OP_CHECKSIGADD with an empty signature. lambda n, pk: (CScript([OP_DROP, OP_0, OP_10, pk, OP_CHECKSIGADD, OP_10, OP_EQUALVERIFY, pk] + [OP_2DUP, OP_16, OP_SWAP, OP_CHECKSIGADD, b'\x11', OP_EQUALVERIFY] * n + [OP_CHECKSIG]), n + 1), + # n OP_CHECKSIGFROMSTACKs, dropping the signature given, and just validate against embedded sigs + lambda n, pk: (CScript([OP_2DROP, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n), + # 1 CHECKSIGVERIFY followed by n OP_CHECKSIGFROMSTACKs, all signatures non-empty and validated + lambda n, pk: (CScript([OP_DROP, pk, OP_CHECKSIGVERIFY, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n+1), + # 1 empty CHECKSIG followed by 1 empty OP_CHECKSIGFROMSTACKs, then finally n OP_CHECKSIGFROMSTACKs + lambda n, pk: (CScript([OP_2DROP, OP_0, pk, OP_CHECKSIG, OP_DROP, OP_0, CSFS_MSG, pk, OP_CHECKSIGFROMSTACK, OP_DROP, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n), + ] + for annex in [None, bytes([ANNEX_TAG]) + random.randbytes(random.randrange(1000))]: for hashtype in [SIGHASH_DEFAULT, SIGHASH_ALL]: for pubkey in [pubs[1], random.randbytes(random.choice([x for x in range(2, 81) if x != 32]))]: @@ -1254,6 +1271,67 @@ def spenders_taproot_nonstandard(): return spenders +def bip348_csfs_spenders(): + secs = [generate_privkey() for _ in range(2)] + pubs = [compute_xonly_pubkey(sec)[0] for sec in secs] + + CSFS_MSG = random.randbytes(random.randrange(0, 520)) + + # Grow, shrink the message being signed, and pick random bytes + TRUNC_CSFS_MSG = CSFS_MSG[:] if len(CSFS_MSG) > 0 else None + if TRUNC_CSFS_MSG is not None: + prune_index = random.randrange(len(TRUNC_CSFS_MSG)) + TRUNC_CSFS_MSG = TRUNC_CSFS_MSG[:prune_index] + TRUNC_CSFS_MSG[prune_index+1:] + extendable_length = 520 - len(CSFS_MSG) + EXTEND_CSFS_MSG = None + if extendable_length > 0: + EXTEND_CSFS_MSG = CSFS_MSG + random.randbytes(random.randrange(1, extendable_length + 1)) + OTHER_CSFS_MSG = CSFS_MSG + + while OTHER_CSFS_MSG == CSFS_MSG: + OTHER_CSFS_MSG = random.randbytes(random.randrange(0, 520)) + + UNK_PUBKEY = random.randbytes(random.randrange(1, 520)) + while len(UNK_PUBKEY) == 32: + UNK_PUBKEY = random.randbytes(random.randrange(1, 520)) + + # Sigops ratio test is included elsewhere to mix and match with other sigops + scripts = [ + ("simple_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK, OP_1, OP_EQUAL])), + ("simple_fail_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK, OP_0, OP_EQUAL])), + ("unk_pubkey_csfs", CScript([CSFS_MSG, UNK_PUBKEY, OP_CHECKSIGFROMSTACK])), + ("onearg_csfs", CScript([pubs[0], OP_CHECKSIGFROMSTACK])), + ("twoargs_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK])), + ("empty_pk_csfs", CScript([CSFS_MSG, OP_0, OP_CHECKSIGFROMSTACK, OP_0, OP_EQUAL])), + ] + + tap = taproot_construct(pubs[0], scripts) + + spenders = [] + + # "sighash" is actually the bip340 message being directly verified against + add_spender(spenders, comment="bip348_csfs/simple", tap=tap, leaf="simple_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"sighash": OTHER_CSFS_MSG}, **ERR_SIG_SCHNORR) + if TRUNC_CSFS_MSG is not None: + add_spender(spenders, comment="bip348_csfs/trunc_msg", tap=tap, leaf="onearg_csfs", key=secs[0], inputs=[getter("sign"), CSFS_MSG], standard=len(CSFS_MSG)<=80, sighash=CSFS_MSG, failure={"inputs": [getter("sign"), TRUNC_CSFS_MSG]}, **ERR_SIG_SCHNORR) + if EXTEND_CSFS_MSG is not None: + add_spender(spenders, comment="bip348_csfs/extend_msg", tap=tap, leaf="onearg_csfs", key=secs[0], inputs=[getter("sign"), CSFS_MSG], standard=len(CSFS_MSG)<=80, sighash=CSFS_MSG, failure={"inputs": [getter("sign"), EXTEND_CSFS_MSG]}, **ERR_SIG_SCHNORR) + + # Empty signature pushes zero onto stack and continues, unless the pubkey is empty + add_spender(spenders, comment="bip348_csfs/simple_fail", tap=tap, leaf="simple_fail_csfs", inputs=[b''], failure={"leaf": "empty_pk_csfs", "inputs": [OTHER_CSFS_MSG]}, **ERR_UNKNOWN_PUBKEY) + + # Unknown pubkey of non-zero size is unconditionally valid regardless of signature (but signature must exist) + add_spender(spenders, comment="bip348_csfs/unk_pubkey", tap=tap, leaf="unk_pubkey_csfs", standard=False, key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"inputs": []}, **ERR_STACK_EMPTY) + + # You need three args for CSFS regardless of what is passed + add_spender(spenders, comment="bip348_csfs/onearg", tap=tap, leaf="onearg_csfs", key=secs[0], inputs=[getter("sign"), CSFS_MSG], standard=len(CSFS_MSG)<=80, sighash=CSFS_MSG, failure={"inputs": []}, **ERR_STACK_EMPTY) + add_spender(spenders, comment="bip348_csfs/twoarg", tap=tap, leaf="twoargs_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"inputs": []}, **ERR_STACK_EMPTY) + + # If a known pubkey's signature is not 64 bytes or empty it MUST fail immediately + add_spender(spenders, comment="bip348_csfs/simple_65_sig", tap=tap, leaf="simple_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"leaf": "simple_fail_csfs", "inputs": [zero_appender(getter("sign"))]}, **ERR_SIG_SCHNORR) + add_spender(spenders, comment="bip348_csfs/simple_63_sig", tap=tap, leaf="simple_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"leaf": "simple_fail_csfs", "inputs": [byte_popper(getter("sign"))]}, **ERR_SIG_SCHNORR) + + return spenders + # Consensus validation flags to use in dumps for tests with "legacy/" or "inactive/" prefix. LEGACY_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY" # Consensus validation flags to use in dumps for all other tests. @@ -1784,7 +1862,8 @@ def run_test(self): self.gen_test_vectors() self.log.info("Post-activation tests...") - self.test_spenders(self.nodes[0], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3]) + consensus_spenders = spenders_taproot_active() + bip348_csfs_spenders() + self.test_spenders(self.nodes[0], consensus_spenders, input_counts=[1, 2, 2, 2, 2, 3]) # Run each test twice; once in isolation, and once combined with others. Testing in isolation # means that the standardness is verified in every test (as combined transactions are only standard # when all their inputs are standard). diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index f54fd0851b1d..80d3f2d2297a 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -196,7 +196,7 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash): assert_equal(gdi_result, { "hash": blockhash, "height": height, - "script_flags": ["ANYPREVOUT","CHECKLOCKTIMEVERIFY","CHECKSEQUENCEVERIFY","DEFAULT_CHECK_TEMPLATE_VERIFY_HASH","DERSIG","NULLDUMMY","OP_CAT","P2SH","TAPROOT","WITNESS"], + "script_flags": ["ANYPREVOUT","CHECKLOCKTIMEVERIFY","CHECKSEQUENCEVERIFY","CHECKSIGFROMSTACK","DEFAULT_CHECK_TEMPLATE_VERIFY_HASH","DERSIG","NULLDUMMY","OP_CAT","P2SH","TAPROOT","WITNESS"], "deployments": { 'bip34': {'type': 'buried', 'active': True, 'height': 2}, 'bip66': {'type': 'buried', 'active': True, 'height': 3}, @@ -261,7 +261,21 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash): 'height': 0, 'active': True, }, - } + "checksigfromstack": { + "type": "heretical", + "height": 0, + "active": True, + "heretical": { + "binana-id": "BIN-2024-0001-000", + "start_time": -1, + "timeout": 9223372036854775807, + "period": 144, + "status": "active", + "since": 0, + "status_next": "active" + }, + }, + } }) def _test_getdeploymentinfo(self): diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 939c7cbef617..7ce11cbae673 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -272,7 +272,6 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): aux = bytes(32) assert len(key) == 32 - assert len(msg) == 32 assert len(aux) == 32 sec = int.from_bytes(key, 'big') diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index b386c452f77a..05c5909212c5 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -254,6 +254,8 @@ def __new__(cls, n): # BIP 342 opcodes (Tapscript) OP_CHECKSIGADD = CScriptOp(0xba) +OP_CHECKSIGFROMSTACK = CScriptOp(0xcc) + OP_INVALIDOPCODE = CScriptOp(0xff) OPCODE_NAMES.update({ @@ -969,4 +971,6 @@ def is_op_success(o): # assume OP_CAT is activated in tests if o == OP_CAT: return False + if o == OP_CHECKSIGFROMSTACK: + return False 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)