script: add SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY
What changed, and why it matters
This commit only changes the error message/name returned when a tapscript transaction provides an empty public key. The actual consensus rule—rejecting empty public keys in tapscript—already existed. The change makes debugging and testing clearer by giving this failure its own distinct error code instead of reusing a more general one. It does not alter what transactions are accepted or rejected by the network.
No security action required. Treat as a routine refactor/test-update commit.
Security signals we found
Consensus rule already existed; no new enforcement added
Error code refactor for clearer diagnostics
Test expectations updated to match new error string
Evidence from the diff
The patch introduces a new script error constant SCRIPT_ERR_TAPSCRIPT_EMPTY_PUBKEY and uses it in EvalChecksigTapscript() when pubkey.size() == 0, replacing the previous SCRIPT_ERR_PUBKEYTYPE. It updates the corresponding error string and test expectations in script_tests.json, script_tests.cpp, and feature_taproot.py. The consensus behavior is unchanged; only the diagnostic classification and test assertions are modified.
Changed components
src/script/interpreter.cppsrc/script/script_error.cppsrc/script/script_error.hsrc/test/data/script_tests.jsonsrc/test/script_tests.cpptest/functional/feature_taproot.pyInspect captured patch +29 / −12
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 9f2776b6..ca405c25 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 fadc0426..24e2f5f0 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 44e68fe0..fbcc1bd1 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 6282f666..e276fec2 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 fc4550bb..99e38f37 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 24cb26a3..f13ca9e0 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.
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.