diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1587,6 +1587,7 @@ bool VerifyScript(const CScript &scriptSig, const CScript &scriptPubKey, uint32_t flags, const BaseSignatureChecker &checker, ScriptError *serror) { + bool onlyRedeemScriptInScriptSig = false; set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR); // If FORKID is enabled, we also ensure strict encoding. @@ -1635,6 +1636,7 @@ const valtype &pubKeySerialized = stack.back(); CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end()); popstack(stack); + onlyRedeemScriptInScriptSig = stack.empty(); if (!EvalScript(stack, pubKey2, flags, checker, serror)) { // serror is set @@ -1652,7 +1654,11 @@ // 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. - if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0) { + // The CLEANSTACK_EXCEPTION ignores the CLEANSTACK check when the scriptSig + // for a P2SH coin contains only the redeem script + if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0 && + !((flags & SCRIPT_ENABLE_CLEANSTACK_EXCEPTION) != 0 && + onlyRedeemScriptInScriptSig)) { // Disallow CLEANSTACK without P2SH, as otherwise a switch // CLEANSTACK->P2SH+CLEANSTACK would be possible, which is not a // softfork (and P2SH should be one). diff --git a/src/script/script_flags.h b/src/script/script_flags.h --- a/src/script/script_flags.h +++ b/src/script/script_flags.h @@ -102,6 +102,11 @@ // Are Schnorr signatures enabled for OP_CHECK(DATA)SIG(VERIFY)? // SCRIPT_ENABLE_SCHNORR = (1U << 19), + + // Enables an exception to the cleanstack rule, allowing P2SH coins + // with only a redeem script in the scriptSig to leave multiple + // stack elements after evaluation + SCRIPT_ENABLE_CLEANSTACK_EXCEPTION = (1U << 20), }; #endif // BITCOIN_SCRIPT_SCRIPTFLAGS_H diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -29,7 +29,7 @@ SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | SCRIPT_VERIFY_MINIMALIF | SCRIPT_VERIFY_NULLFAIL | SCRIPT_VERIFY_COMPRESSED_PUBKEYTYPE | SCRIPT_ENABLE_SIGHASH_FORKID | SCRIPT_ENABLE_REPLAY_PROTECTION | - SCRIPT_ENABLE_CHECKDATASIG; + SCRIPT_ENABLE_CHECKDATASIG | SCRIPT_ENABLE_CLEANSTACK_EXCEPTION; /** * Valid signature cache, to avoid doing expensive ECDSA signature checking diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -2660,6 +2660,20 @@ "PUBKEYTYPE", "CHECKDATASIGVERIFY with invalid hybrid pubkey" ], +[ + "0x16 0x001491b24bf9f5288532960ac687abb035127b1d28a5", + "HASH160 0x14 0x17743beb429c55c942d2ec703b98c4d57c2df5c6 EQUAL", + "CLEANSTACK,P2SH", + "CLEANSTACK", + "P2SH with only a redeem script in the scriptSig and dirty stack, but no CLEANSTACK_EXCEPTION" +], +[ + "0x16 0x001491b24bf9f5288532960ac687abb035127b1d28a5", + "HASH160 0x14 0x17743beb429c55c942d2ec703b98c4d57c2df5c6 EQUAL", + "CLEANSTACK,CLEANSTACK_EXCEPTION,P2SH", + "OK", + "P2SH with only a redeem script in the scriptSig and dirty stack, with CLEANSTACK_EXCEPTION" +], ["CHECKSEQUENCEVERIFY tests"], ["", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "INVALID_STACK_OPERATION", "CSV automatically fails on a empty stack"], diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -1531,6 +1531,23 @@ .Num(0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); + // Tests SCRIPT_ENABLE_CLEANSTACK_EXCEPTION + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(keys.pubkey0.GetID()), + "P2SH with only a redeem script in the scriptSig and dirty " + "stack, but no CLEANSTACK_EXCEPTION", + SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(keys.pubkey0.GetID()), + "P2SH with only a redeem script in the scriptSig and dirty " + "stack, with CLEANSTACK_EXCEPTION", + SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH | + SCRIPT_ENABLE_CLEANSTACK_EXCEPTION, + true) + .PushRedeem()); + std::set tests_set; { diff --git a/src/test/scriptflags.cpp b/src/test/scriptflags.cpp --- a/src/test/scriptflags.cpp +++ b/src/test/scriptflags.cpp @@ -33,6 +33,7 @@ {"REPLAY_PROTECTION", SCRIPT_ENABLE_REPLAY_PROTECTION}, {"CHECKDATASIG", SCRIPT_ENABLE_CHECKDATASIG}, {"SCHNORR", SCRIPT_ENABLE_SCHNORR}, + {"CLEANSTACK_EXCEPTION", SCRIPT_ENABLE_CLEANSTACK_EXCEPTION}, }; uint32_t ParseScriptFlags(std::string strFlags) { diff --git a/src/test/sigcache_tests.cpp b/src/test/sigcache_tests.cpp --- a/src/test/sigcache_tests.cpp +++ b/src/test/sigcache_tests.cpp @@ -35,7 +35,7 @@ SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | SCRIPT_VERIFY_MINIMALIF | SCRIPT_VERIFY_NULLFAIL | SCRIPT_VERIFY_COMPRESSED_PUBKEYTYPE | SCRIPT_ENABLE_SIGHASH_FORKID | SCRIPT_ENABLE_REPLAY_PROTECTION | - SCRIPT_ENABLE_CHECKDATASIG; + SCRIPT_ENABLE_CHECKDATASIG | SCRIPT_ENABLE_CLEANSTACK_EXCEPTION; /* We will be testing that these flags DO affect the cache entry. The expected * behaviour is that flags which are not explicitly listed as invariant in * script/sigcache.cpp will affect the cache entry. Here we will thus enforce