From 9d5021a05bd33c73276909eec961777867ddb412 Mon Sep 17 00:00:00 2001 From: billymcbip <245003547+billymcbip@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:41:10 +0100 Subject: [PATCH] script: add SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY Empty public keys in tapscript are rejected by consensus rules, independent of SCRIPT_VERIFY_STRICTENC. Add SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY to distinguish this from STRICTENC policy failures currently reported as SCRIPT_ERR_PUBKEYTYPE. --- src/script/interpreter.cpp | 2 +- src/script/script_error.cpp | 2 ++ src/script/script_error.h | 1 + src/test/data/script_tests.json | 13 +++++++++++++ src/test/script_tests.cpp | 1 + test/functional/feature_taproot.py | 22 +++++++++++----------- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 9f2776b68a0..ca405c25da2 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -365,7 +365,7 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr } } if (pubkey.size() == 0) { - return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY); } else if (pubkey.size() == 32) { if (success && !checker.CheckSchnorrSignature(sig, pubkey, sigversion, execdata, serror)) { return false; // serror is set diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index fadc04262c3..24e2f5f01b4 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -111,6 +111,8 @@ std::string ScriptErrorString(const ScriptError serror) return "OP_CHECKMULTISIG(VERIFY) is not available in tapscript"; case SCRIPT_ERR_TAPSCRIPT_MINIMALIF: return "OP_IF/NOTIF argument must be minimal in tapscript"; + case SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY: + return "Empty public key in tapscript"; case SCRIPT_ERR_OP_CODESEPARATOR: return "Using OP_CODESEPARATOR in non-witness script"; case SCRIPT_ERR_SIG_FINDANDDELETE: diff --git a/src/script/script_error.h b/src/script/script_error.h index 44e68fe0fae..fbcc1bd1616 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -77,6 +77,7 @@ typedef enum ScriptError_t SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT, SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG, SCRIPT_ERR_TAPSCRIPT_MINIMALIF, + SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY, /* Constant scriptCode */ SCRIPT_ERR_OP_CODESEPARATOR, diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index 6282f666496..e276fec2115 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -2667,6 +2667,19 @@ "OK", "TAPSCRIPT Test that DROP operations do not execute inside of a false IF conditional" ], +[ + [ + "aa", + "#SCRIPT# 0 CHECKSIG", + "#CONTROLBLOCK#", + 0.00000001 + ], + "", + "0x51 0x20 #TAPROOTOUTPUT#", + "P2SH,WITNESS,TAPROOT", + "TAPSCRIPT_EMPTY_PUBKEY", + "TAPSCRIPT: OP_CHECKSIG with empty pubkey must fail" +], ["NULLFAIL should cover all signatures and signatures only"], ["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG", "OK", "BIP66 and NULLFAIL-compliant"], diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index fc4550bb7b7..99e38f37588 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -92,6 +92,7 @@ static ScriptErrorDesc script_errors[]={ {SCRIPT_ERR_WITNESS_MALLEATED_P2SH, "WITNESS_MALLEATED_P2SH"}, {SCRIPT_ERR_WITNESS_UNEXPECTED, "WITNESS_UNEXPECTED"}, {SCRIPT_ERR_WITNESS_PUBKEYTYPE, "WITNESS_PUBKEYTYPE"}, + {SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY, "TAPSCRIPT_EMPTY_PUBKEY"}, {SCRIPT_ERR_OP_CODESEPARATOR, "OP_CODESEPARATOR"}, {SCRIPT_ERR_SIG_FINDANDDELETE, "SIG_FINDANDDELETE"}, }; diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 24cb26a3440..f13ca9e0475 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -631,7 +631,7 @@ ERR_PUSH_SIZE = {"err_msg": "Push value size limit exceeded"} ERR_DISABLED_OPCODE = {"err_msg": "Attempted to use a disabled opcode"} ERR_TAPSCRIPT_CHECKMULTISIG = {"err_msg": "OP_CHECKMULTISIG(VERIFY) is not available in tapscript"} ERR_TAPSCRIPT_MINIMALIF = {"err_msg": "OP_IF/NOTIF argument must be minimal in tapscript"} -ERR_PUBKEYTYPE = {"err_msg": "Public key is neither compressed or uncompressed"} +ERR_TAPSCRIPT_EMPTY_PUBKEY = {"err_msg": "Empty public key in tapscript"} ERR_STACK_SIZE = {"err_msg": "Stack size limit exceeded"} ERR_CLEANSTACK = {"err_msg": "Stack size must be exactly one after execution"} ERR_INVALID_STACK_OPERATION = {"err_msg": "Operation not valid with the current stack size"} @@ -1047,27 +1047,27 @@ def spenders_taproot_active(): add_spender(spenders, "tapscript/minimalif", leaf="t5", **common, inputs=[getter("sign"), b'\x01'], failure={"inputs": [getter("sign"), b'\x0001']}, **ERR_TAPSCRIPT_MINIMALIF) add_spender(spenders, "tapscript/minimalnotif", leaf="t6", **common, inputs=[getter("sign"), b'\x01'], failure={"inputs": [getter("sign"), b'\x0100']}, **ERR_TAPSCRIPT_MINIMALIF) # Test that 1-byte public keys (which are unknown) are acceptable but nonstandard with unrelated signatures, but 0-byte public keys are not valid. - add_spender(spenders, "tapscript/unkpk/checksig", leaf="t16", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t7"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/unkpk/checksigadd", leaf="t17", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t10"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/unkpk/checksigverify", leaf="t18", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t8"}, **ERR_PUBKEYTYPE) + add_spender(spenders, "tapscript/unkpk/checksig", leaf="t16", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t7"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/unkpk/checksigadd", leaf="t17", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t10"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/unkpk/checksigverify", leaf="t18", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t8"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) # Test that 33-byte public keys (which are unknown) are acceptable but nonstandard with valid signatures, but normal pubkeys are not valid in that case. add_spender(spenders, "tapscript/oldpk/checksig", leaf="t30", standard=False, **common, **SINGLE_SIG, sighash=bitflipper(default_sighash), failure={"leaf": "t1"}, **ERR_SCHNORR_SIG) add_spender(spenders, "tapscript/oldpk/checksigadd", leaf="t31", standard=False, **common, **SINGLE_SIG, sighash=bitflipper(default_sighash), failure={"leaf": "t2"}, **ERR_SCHNORR_SIG) add_spender(spenders, "tapscript/oldpk/checksigverify", leaf="t32", standard=False, **common, **SINGLE_SIG, sighash=bitflipper(default_sighash), failure={"leaf": "t28"}, **ERR_SCHNORR_SIG) # Test that 0-byte public keys are not acceptable. - add_spender(spenders, "tapscript/emptypk/checksig", leaf="t1", **SINGLE_SIG, **common, failure={"leaf": "t7"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/emptypk/checksigverify", leaf="t2", **SINGLE_SIG, **common, failure={"leaf": "t8"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/emptypk/checksigadd", leaf="t9", **SINGLE_SIG, **common, failure={"leaf": "t10"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/emptypk/checksigadd", leaf="t35", standard=False, **SINGLE_SIG, **common, failure={"leaf": "t10"}, **ERR_PUBKEYTYPE) + add_spender(spenders, "tapscript/emptypk/checksig", leaf="t1", **SINGLE_SIG, **common, failure={"leaf": "t7"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/emptypk/checksigverify", leaf="t2", **SINGLE_SIG, **common, failure={"leaf": "t8"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/emptypk/checksigadd", leaf="t9", **SINGLE_SIG, **common, failure={"leaf": "t10"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/emptypk/checksigadd", leaf="t35", standard=False, **SINGLE_SIG, **common, failure={"leaf": "t10"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) # Test that OP_CHECKSIGADD results are as expected add_spender(spenders, "tapscript/checksigaddresults", leaf="t28", **SINGLE_SIG, **common, failure={"leaf": "t27"}, err_msg="unknown error") add_spender(spenders, "tapscript/checksigaddoversize", leaf="t29", **SINGLE_SIG, **common, failure={"leaf": "t27"}, err_msg="unknown error") # Test that OP_CHECKSIGADD requires 3 stack elements. add_spender(spenders, "tapscript/checksigadd3args", leaf="t9", **SINGLE_SIG, **common, failure={"leaf": "t11"}, **ERR_INVALID_STACK_OPERATION) # Test that empty signatures do not cause script failure in OP_CHECKSIG and OP_CHECKSIGADD (but do fail with empty pubkey, and do fail OP_CHECKSIGVERIFY) - add_spender(spenders, "tapscript/emptysigs/checksig", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t13"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/emptysigs/nochecksigverify", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t20"}, **ERR_PUBKEYTYPE) - add_spender(spenders, "tapscript/emptysigs/checksigadd", leaf="t14", **common, inputs=[b'', getter("sign")], failure={"leaf": "t15"}, **ERR_PUBKEYTYPE) + add_spender(spenders, "tapscript/emptysigs/checksig", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t13"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/emptysigs/nochecksigverify", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t20"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) + add_spender(spenders, "tapscript/emptysigs/checksigadd", leaf="t14", **common, inputs=[b'', getter("sign")], failure={"leaf": "t15"}, **ERR_TAPSCRIPT_EMPTY_PUBKEY) # Test that scripts over 10000 bytes (and over 201 non-push ops) are acceptable. add_spender(spenders, "tapscript/no10000limit", leaf="t19", **SINGLE_SIG, **common) # Test that a stack size of 1000 elements is permitted, but 1001 isn't.