diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1636,6 +1636,13 @@ CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end()); popstack(stack); + // Bail out early if ALLOW_SEGWIT_RECOVERY is set, the redeem script is + // a p2sh segwit program and it was the only item pushed into the stack + if ((flags & SCRIPT_ALLOW_SEGWIT_RECOVERY) != 0 && stack.empty() && + pubKey2.IsWitnessProgram()) { + return set_success(serror); + } + if (!EvalScript(stack, pubKey2, flags, checker, serror)) { // serror is set return false; diff --git a/src/script/script.h b/src/script/script.h --- a/src/script/script.h +++ b/src/script/script.h @@ -634,6 +634,7 @@ bool IsPayToScriptHash() const; bool IsCommitment(const std::vector &data) const; bool IsWitnessProgram(int &version, std::vector &program) const; + bool IsWitnessProgram() const; /** * Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it diff --git a/src/script/script.cpp b/src/script/script.cpp --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -455,6 +455,13 @@ return false; } +// Wrapper returning only the predicate +bool CScript::IsWitnessProgram() const { + int version; + std::vector program; + return IsWitnessProgram(version, program); +} + bool CScript::IsPushOnly(const_iterator pc) const { while (pc < end()) { opcodetype opcode; 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,9 @@ // Are Schnorr signatures enabled for OP_CHECK(DATA)SIG(VERIFY)? // SCRIPT_ENABLE_SCHNORR = (1U << 19), + + // Allows the recovery of coins sent to p2sh segwit addresses + SCRIPT_ALLOW_SEGWIT_RECOVERY = (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_ALLOW_SEGWIT_RECOVERY; /** * 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,104 @@ "PUBKEYTYPE", "CHECKDATASIGVERIFY with invalid hybrid pubkey" ], +[ + "0x16 0x001491b24bf9f5288532960ac687abb035127b1d28a5", + "HASH160 0x14 0x17743beb429c55c942d2ec703b98c4d57c2df5c6 EQUAL", + "CLEANSTACK,P2SH", + "CLEANSTACK", + "v0 P2SH-P2WPKH but no SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x16 0x001491b24bf9f5288532960ac687abb035127b1d28a5", + "HASH160 0x14 0x17743beb429c55c942d2ec703b98c4d57c2df5c6 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "OK", + "v0 P2SH-P2WPKH with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0 0x16 0x001491b24bf9f5288532960ac687abb035127b1d28a5", + "HASH160 0x14 0x17743beb429c55c942d2ec703b98c4d57c2df5c6 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "v0 P2SH-P2WPKH with extra stack item and SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x22 0x00205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "HASH160 0x14 0x17a6be2f8fe8e94f033e53d17beefda0f3ac4409 EQUAL", + "CLEANSTACK,P2SH", + "CLEANSTACK", + "v0 P2SH-P2WSH but no SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x22 0x00205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "HASH160 0x14 0x17a6be2f8fe8e94f033e53d17beefda0f3ac4409 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "OK", + "v0 P2SH-P2WSH with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0 0x22 0x00205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "HASH160 0x14 0x17a6be2f8fe8e94f033e53d17beefda0f3ac4409 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "v0 P2SH-P2WSH with extra stack item and SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x03 0x00015a", + "HASH160 0x14 0x40b6941895022d458de8f4bbfe27f3aaa4fb9a74 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "Invalid witness program (too short) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x04 0x00025a01", + "HASH160 0x14 0x86123d8e050333a605e434ecf73128d83815b36f EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "OK", + "Valid witness program (min allowed length) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x2a 0x00285a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627", + "HASH160 0x14 0xdf7b93f88e83471b479fb219ae90e5b633d6b750 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "OK", + "Valid witness program (max allowed length) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x2b 0x00295a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728", + "HASH160 0x14 0x13aa4fcfd630508e0794dca320cac172c5790aea EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "Invalid witness program (too long) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x22 0x60205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "HASH160 0x14 0x9b0c7017004d3818b7c833ddb3cb5547a22034d0 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "OK", + "Valid witness program (max allowed version) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x22 0x4f205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "HASH160 0x14 0x97aa1e96e49ca6d744d7344f649dd9f94bcc35eb EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "Invalid witness program (invalid version -1) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x23 0x0111205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "HASH160 0x14 0x4b5321beb1c09f593ff3c02be4af21c7f949e101 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "Invalid witness program (invalid version 17) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], +[ + "0x23 0x00205a0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f51", + "HASH160 0x14 0x8eb812176c9e71732584123dd06d3246e659b199 EQUAL", + "ALLOW_SEGWIT_RECOVERY,CLEANSTACK,P2SH", + "CLEANSTACK", + "Invalid witness program (more than 2 stack items) with SCRIPT_ALLOW_SEGWIT_RECOVERY" +], ["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,114 @@ .Num(0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); + // Tests SCRIPT_ALLOW_SEGWIT_RECOVERY + const uint32_t allowSegwitRecoveryFlags = SCRIPT_VERIFY_CLEANSTACK | + SCRIPT_VERIFY_P2SH | + SCRIPT_ALLOW_SEGWIT_RECOVERY; + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(keys.pubkey0.GetID()), + "v0 P2SH-P2WPKH but no SCRIPT_ALLOW_SEGWIT_RECOVERY", + SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(keys.pubkey0.GetID()), + "v0 P2SH-P2WPKH with SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem()); + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(keys.pubkey0.GetID()), + "v0 P2SH-P2WPKH with extra stack item and " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .Num(0) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + uint256 dummy256(std::vector( + {90, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31})); + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(dummy256), + "v0 P2SH-P2WSH but no SCRIPT_ALLOW_SEGWIT_RECOVERY", + SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(dummy256), + "v0 P2SH-P2WSH with SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem()); + tests.push_back(TestBuilder(CScript() << OP_0 << ToByteVector(dummy256), + "v0 P2SH-P2WSH with extra stack item and " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .Num(0) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + // Tests the limits of IsWitnessProgram along with + // SCRIPT_ALLOW_SEGWIT_RECOVERY + std::vector shortprogram({90, 1}); + tests.push_back( + TestBuilder(CScript() << OP_0 + << std::vector(shortprogram.begin(), + shortprogram.end() - 1), + "Invalid witness program (too short) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << OP_0 << shortprogram, + "Valid witness program (min allowed length) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem()); + std::vector longprogram( + {90, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}); + tests.push_back( + TestBuilder(CScript() << OP_0 + << std::vector(longprogram.begin(), + longprogram.end() - 1), + "Valid witness program (max allowed length) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem()); + tests.push_back(TestBuilder(CScript() << OP_0 << longprogram, + "Invalid witness program (too long) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << OP_16 << ToByteVector(dummy256), + "Valid witness program (max allowed version) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem()); + tests.push_back( + TestBuilder(CScript() << OP_1NEGATE << ToByteVector(dummy256), + "Invalid witness program (invalid version -1) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << 17 << ToByteVector(dummy256), + "Invalid witness program (invalid version 17) with " + "SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + tests.push_back( + TestBuilder(CScript() << OP_0 << ToByteVector(dummy256) << OP_1, + "Invalid witness program (more than 2 stack " + "items) with SCRIPT_ALLOW_SEGWIT_RECOVERY", + allowSegwitRecoveryFlags, true) + .PushRedeem() + .ScriptError(SCRIPT_ERR_CLEANSTACK)); + std::set tests_set; { @@ -2293,4 +2401,42 @@ BOOST_CHECK(s == expect); } +BOOST_AUTO_TEST_CASE(IsWitnessProgram) { + // Valid version: [0,16] + // Valid program_len: [2,40] + for (int version = -1; version <= 17; version++) { + for (unsigned int program_len = 1; program_len <= 41; program_len++) { + CScript script; + std::vector program(program_len, '\42'); + int parsed_version; + std::vector parsed_program; + script << version << program; + bool result = + script.IsWitnessProgram(parsed_version, parsed_program); + bool expected = version >= 0 && version <= 16 && program_len >= 2 && + program_len <= 40; + BOOST_CHECK_EQUAL(result, expected); + if (result) { + BOOST_CHECK_EQUAL(version, parsed_version); + BOOST_CHECK(program == parsed_program); + } + } + } + // Tests with 1 and 3 stack elements + { + CScript script; + script << OP_0; + BOOST_CHECK_MESSAGE( + !script.IsWitnessProgram(), + "Failed IsWitnessProgram check with 1 stack element"); + } + { + CScript script; + script << OP_0 << std::vector(20, '\42') << OP_1; + BOOST_CHECK_MESSAGE( + !script.IsWitnessProgram(), + "Failed IsWitnessProgram check with 3 stack elements"); + } +} + BOOST_AUTO_TEST_SUITE_END() 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}, + {"ALLOW_SEGWIT_RECOVERY", SCRIPT_ALLOW_SEGWIT_RECOVERY}, }; 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_ALLOW_SEGWIT_RECOVERY; /* 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