diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1567,6 +1567,10 @@ if ((flags & SCRIPT_VERIFY_SIGPUSHONLY) != 0 && !scriptSig.IsPushOnly()) { return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY); } + if ((flags & SCRIPT_VERIFY_SCRIPTSIGMINPUSHONLY) != 0 && + !scriptSig.IsMinimalPushOnly()) { + return set_error(serror, SCRIPT_ERR_SCRIPTSIGMINPUSHONLY); + } std::vector stack, stackCopy; if (!EvalScript(stack, scriptSig, flags, checker, serror)) { diff --git a/src/script/script_error.h b/src/script/script_error.h --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -60,6 +60,7 @@ SCRIPT_ERR_CLEANSTACK, SCRIPT_ERR_MINIMALIF, SCRIPT_ERR_SIG_NULLFAIL, + SCRIPT_ERR_SCRIPTSIGMINPUSHONLY, /* Schnorr */ SCRIPT_ERR_SIG_BADLENGTH, diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -82,6 +82,8 @@ case SCRIPT_ERR_SIG_NULLFAIL: return "Signature must be zero for failed CHECK(MULTI)SIG " "operation"; + case SCRIPT_ERR_SCRIPTSIGMINPUSHONLY: + return "Only minimal push operators allowed in signature scripts"; case SCRIPT_ERR_SIG_BADLENGTH: return "Signature cannot be 65 bytes in CHECKMULTISIG"; case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS: 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 @@ -111,6 +111,10 @@ // The exception to CLEANSTACK and P2SH for the recovery of coins sent // to p2sh segwit addresses is not allowed. SCRIPT_DISALLOW_SEGWIT_RECOVERY = (1U << 20), + + // In the scriptSig, using a non-push opcode or a push operation that is + // non-minimal causes script failure. + SCRIPT_VERIFY_SCRIPTSIGMINPUSHONLY = (1U << 21), }; #endif // BITCOIN_SCRIPT_SCRIPTFLAGS_H 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 @@ -1575,6 +1575,46 @@ ["0 0 0x02 0x0000", "CHECKMULTISIGVERIFY 1", "MINIMALDATA", "UNKNOWN_ERROR"], ["0 0x02 0x0000 0", "CHECKMULTISIGVERIFY 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["SCRIPTSIGMINPUSHONLY enforcement (copied from 'MINIMALDATA enforcement for PUSHDATAs')"], + +["PUSHDATA1 0x00", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY", "Empty vector {} minimally represented by OP_0"], +["0x01 0x81", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY", "{0x81} minimally represented by OP_1NEGATE"], +["0x01 0x01", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY", "{0x01} to {0x16} minimally represented by OP_1 to OP_16"], +["0x01 0x02", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x03", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x04", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x05", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x06", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x07", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x08", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x09", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x0a", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x0b", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x0c", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x0d", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x0e", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x0f", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], +["0x01 0x10", "DROP 1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY"], + +["PUSHDATA1 0x48 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "SCRIPTSIGMINPUSHONLY", + "SCRIPTSIGMINPUSHONLY", + "PUSHDATA1 of 72 bytes minimally represented by direct push"], + +["PUSHDATA2 0xFF00 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "SCRIPTSIGMINPUSHONLY", + "SCRIPTSIGMINPUSHONLY", + "PUSHDATA2 of 255 bytes minimally represented by PUSHDATA1"], + +["PUSHDATA4 0x00010000 0x11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "SCRIPTSIGMINPUSHONLY", + "SCRIPTSIGMINPUSHONLY", + "PUSHDATA4 of 256 bytes minimally represented by PUSHDATA2"], + +["More scriptSig push-only / minimal-push-only enforcement"], +["0x50", "1", "SIGPUSHONLY", "BAD_OPCODE", "PUSHONLY lets OP_RESERVED sneak past, only to fail during execution"], +["0x50", "1", "SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY", "Canonical scriptSig rule stops all non-push opcodes"], +["", "0x03 0x010000", "SCRIPTSIGMINPUSHONLY", "OK", "Canonical scriptSig rule doesn't care about scriptPubKey"], +["0x04 0x03010000", "OP_HASH160 0x14 0xc4b153359ddd9e11805e6704da4486c83a90abbf OP_EQUAL", "P2SH,SCRIPTSIGMINPUSHONLY", "OK", "Canonical scriptSig rule doesn't care about redeemScript contents"], +["PUSHDATA1 0x04 0x03010000", "OP_HASH160 0x14 0xc4b153359ddd9e11805e6704da4486c83a90abbf OP_EQUAL", "P2SH,SCRIPTSIGMINPUSHONLY", "SCRIPTSIGMINPUSHONLY", "but it does care how you push it"], + ["64/65-byte sig length tests"], ["0x41 0x303e021d4444444444444444444444444444444444444444444444444444444444021d444444444444444444444444444444444444444444444444444444444401", "0x21 0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 CHECKSIG NOT", "", "OK"], ["0x40 0x303e021d4444444444444444444444444444444444444444444444444444444444021d4444444444444444444444444444444444444444444444444444444444", "0 0x21 0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 CHECKDATASIG NOT", "", "OK"], @@ -2189,6 +2229,13 @@ "SIG_PUSHONLY", "2-of-2 with two identical keys and sigs pushed using OP_DUP" ], +[ + "0 0x47 0x3044022044b11d5a64bebbd4280c616146bc961eed3081300eef527fbe8da91f6775892a022045b044719bf5c7100617cd518fff22c102f93e6768ed986543668be43bc44d1601 DUP", + "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 2 CHECKMULTISIG", + "SCRIPTSIGMINPUSHONLY", + "SCRIPTSIGMINPUSHONLY", + "2-of-2 with two identical keys and sigs pushed using OP_DUP (minpushonly)" +], [ "0x47 0x304402204b26ae3298e583956aed912c1e6a32b94efc4cf3a97495d83f7a6edd64f8c11702204d0b112f3ebbffe8a0626deb57f62f33432fae8dc1a3de1658464bbb12f8260c01 NOP8 0x23 0x2103363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640ac", "HASH160 0x14 0x215640c2f72f0d16b4eced26762035a42ffed39a EQUAL", @@ -2217,6 +2264,13 @@ "SIG_PUSHONLY", "P2SH(P2PK) with non-push scriptSig but not P2SH" ], +[ + "0x47 0x304402204b26ae3298e583956aed912c1e6a32b94efc4cf3a97495d83f7a6edd64f8c11702204d0b112f3ebbffe8a0626deb57f62f33432fae8dc1a3de1658464bbb12f8260c01 NOP8 0x23 0x2103363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640ac", + "HASH160 0x14 0x215640c2f72f0d16b4eced26762035a42ffed39a EQUAL", + "SCRIPTSIGMINPUSHONLY", + "SCRIPTSIGMINPUSHONLY", + "P2SH(P2PK) with non-push scriptSig but not P2SH (minpushonly)" +], [ "0 0x47 0x3044022044b11d5a64bebbd4280c616146bc961eed3081300eef527fbe8da91f6775892a022045b044719bf5c7100617cd518fff22c102f93e6768ed986543668be43bc44d1601 0x47 0x3044022044b11d5a64bebbd4280c616146bc961eed3081300eef527fbe8da91f6775892a022045b044719bf5c7100617cd518fff22c102f93e6768ed986543668be43bc44d1601", "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 2 CHECKMULTISIG", @@ -2224,6 +2278,13 @@ "OK", "2-of-2 with two identical keys and sigs pushed" ], +[ + "0 0x47 0x3044022044b11d5a64bebbd4280c616146bc961eed3081300eef527fbe8da91f6775892a022045b044719bf5c7100617cd518fff22c102f93e6768ed986543668be43bc44d1601 0x47 0x3044022044b11d5a64bebbd4280c616146bc961eed3081300eef527fbe8da91f6775892a022045b044719bf5c7100617cd518fff22c102f93e6768ed986543668be43bc44d1601", + "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 2 CHECKMULTISIG", + "SCRIPTSIGMINPUSHONLY", + "OK", + "2-of-2 with two identical keys and sigs pushed (minpushonly)" +], [ "11 0x47 0x3044022022127048516d473153d1f74e46e828496776752e3255f672f760a41e83f54e6f0220502956b739ed82aad916dc4a73e1fd55d02aad514b5211f1ba7d0dadf53c637901", "0x41 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 CHECKSIG", 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 @@ -83,6 +83,7 @@ {SCRIPT_ERR_CLEANSTACK, "CLEANSTACK"}, {SCRIPT_ERR_MINIMALIF, "MINIMALIF"}, {SCRIPT_ERR_SIG_NULLFAIL, "NULLFAIL"}, + {SCRIPT_ERR_SCRIPTSIGMINPUSHONLY, "SCRIPTSIGMINPUSHONLY"}, {SCRIPT_ERR_SIG_BADLENGTH, "SIG_BADLENGTH"}, {SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, "DISCOURAGE_UPGRADABLE_NOPS"}, {SCRIPT_ERR_NONCOMPRESSED_PUBKEY, "NONCOMPRESSED_PUBKEY"}, @@ -1050,6 +1051,16 @@ .PushSigECDSA(keys.key1) .Add(CScript() << OP_DUP) .ScriptError(SCRIPT_ERR_SIG_PUSHONLY)); + tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) + << ToByteVector(keys.pubkey1C) << OP_2 + << OP_CHECKMULTISIG, + "2-of-2 with two identical keys and sigs " + "pushed using OP_DUP (minpushonly)", + SCRIPT_VERIFY_SCRIPTSIGMINPUSHONLY) + .Num(0) + .PushSigECDSA(keys.key1) + .Add(CScript() << OP_DUP) + .ScriptError(SCRIPT_ERR_SCRIPTSIGMINPUSHONLY)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, @@ -1079,6 +1090,15 @@ .Add(CScript() << OP_NOP8) .PushRedeem() .ScriptError(SCRIPT_ERR_SIG_PUSHONLY)); + tests.push_back( + TestBuilder( + CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, + "P2SH(P2PK) with non-push scriptSig but not P2SH (minpushonly)", + SCRIPT_VERIFY_SCRIPTSIGMINPUSHONLY, true) + .PushSigECDSA(keys.key2) + .Add(CScript() << OP_NOP8) + .PushRedeem() + .ScriptError(SCRIPT_ERR_SCRIPTSIGMINPUSHONLY)); tests.push_back( TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey1C) << OP_2 @@ -1088,6 +1108,16 @@ .Num(0) .PushSigECDSA(keys.key1) .PushSigECDSA(keys.key1)); + tests.push_back( + TestBuilder( + CScript() << OP_2 << ToByteVector(keys.pubkey1C) + << ToByteVector(keys.pubkey1C) << OP_2 + << OP_CHECKMULTISIG, + "2-of-2 with two identical keys and sigs pushed (minpushonly)", + SCRIPT_VERIFY_SCRIPTSIGMINPUSHONLY) + .Num(0) + .PushSigECDSA(keys.key1) + .PushSigECDSA(keys.key1)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK with unnecessary input but no CLEANSTACK", diff --git a/src/test/scriptflags.cpp b/src/test/scriptflags.cpp --- a/src/test/scriptflags.cpp +++ b/src/test/scriptflags.cpp @@ -34,6 +34,7 @@ {"CHECKDATASIG", SCRIPT_VERIFY_CHECKDATASIG_SIGOPS}, {"SCHNORR", SCRIPT_ENABLE_SCHNORR}, {"DISALLOW_SEGWIT_RECOVERY", SCRIPT_DISALLOW_SEGWIT_RECOVERY}, + {"SCRIPTSIGMINPUSHONLY", SCRIPT_VERIFY_SCRIPTSIGMINPUSHONLY}, }; uint32_t ParseScriptFlags(std::string strFlags) {