diff --git a/src/policy/policy.h b/src/policy/policy.h
index 6a7980c312c..938f823fbc4 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -113,7 +113,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
                                                              SCRIPT_VERIFY_CONST_SCRIPTCODE |
                                                              SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
                                                              SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
-                                                             SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE};
+                                                             SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE |
+                                                             SCRIPT_VERIFY_INTERNALKEY};
 
 /** For convenience, standard but not mandatory verify flags. */
 static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS};
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index c969ce45f12..a693d43374c 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1213,6 +1213,15 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
                 }
                 break;
 
+                case OP_INTERNALKEY: {
+                    // OP_INTERNALKEY is only available in Tapscript
+                    if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
+                    // Always present in Tapscript
+                    assert(execdata.m_internal_key);
+                    stack.emplace_back(execdata.m_internal_key->begin(), execdata.m_internal_key->end());
+                    break;
+                }
+
                 default:
                     return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
             }
@@ -1798,6 +1807,12 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
                 // Note how this condition would not be reached if an unknown OP_SUCCESSx was found
                 return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
             }
+            if (opcode == OP_INTERNALKEY) {
+                if (flags & SCRIPT_VERIFY_DISCOURAGE_INTERNALKEY)
+                    return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
+                if (flags & SCRIPT_VERIFY_INTERNALKEY) continue;
+                return set_success(serror);
+            }
             // New opcodes will be listed here. May use a different sigversion to modify existing opcodes.
             if (IsOpSuccess(opcode)) {
                 if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) {
@@ -1856,12 +1871,13 @@ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint25
     return k;
 }
 
-static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash)
+static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash, std::optional<XOnlyPubKey>& internal_key)
 {
     assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE);
     assert(program.size() >= uint256::size());
     //! The internal pubkey (x-only, so no Y coordinate parity).
     const XOnlyPubKey p{Span{control}.subspan(1, TAPROOT_CONTROL_BASE_SIZE - 1)};
+    internal_key = p;
     //! The output pubkey (taken from the scriptPubKey).
     const XOnlyPubKey q{program};
     // Compute the Merkle root from the leaf and the provided path.
@@ -1927,7 +1943,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
                 return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
             }
             execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script);
-            if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) {
+            if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash, execdata.m_internal_key)) {
                 return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
             }
             execdata.m_tapleaf_hash_init = true;
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index 836c2e7982a..c354bbf968f 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -11,6 +11,7 @@
 #include <primitives/transaction.h>
 #include <script/script_error.h> // IWYU pragma: export
 #include <span.h>
+#include <pubkey.h>
 #include <uint256.h>
 
 #include <cstddef>
@@ -143,6 +144,12 @@ enum : uint32_t {
     // Making unknown public key versions (in BIP 342 scripts) non-standard
     SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20),
 
+    // Executing OP_INTERNALKEY
+    SCRIPT_VERIFY_INTERNALKEY = (1U << 21),
+
+    // Making OP_INTERNALKEY non-standard
+    SCRIPT_VERIFY_DISCOURAGE_INTERNALKEY = (1U << 22),
+
     // Constants to point to the highest flag in use. Add new flags above this line.
     //
     SCRIPT_VERIFY_END_MARKER
@@ -221,6 +228,9 @@ struct ScriptExecutionData
 
     //! The hash of the corresponding output
     std::optional<uint256> m_output_hash;
+
+    //! The taproot internal key. */
+    std::optional<XOnlyPubKey> m_internal_key = std::nullopt;
 };
 
 /** Signature hash sizes */
diff --git a/src/script/script.cpp b/src/script/script.cpp
index 80e8d26bcfb..a1363dee5ae 100644
--- a/src/script/script.cpp
+++ b/src/script/script.cpp
@@ -149,6 +149,8 @@ std::string GetOpName(opcodetype opcode)
     // Opcode added by BIP 342 (Tapscript)
     case OP_CHECKSIGADD            : return "OP_CHECKSIGADD";
 
+    case OP_INTERNALKEY            : return "OP_INTERNALKEY";
+
     case OP_INVALIDOPCODE          : return "OP_INVALIDOPCODE";
 
     default:
diff --git a/src/script/script.h b/src/script/script.h
index 66d63fae89e..d2c04ace085 100644
--- a/src/script/script.h
+++ b/src/script/script.h
@@ -207,6 +207,7 @@ enum opcodetype
 
     // Opcode added by BIP 342 (Tapscript)
     OP_CHECKSIGADD = 0xba,
+    OP_INTERNALKEY = 0xcb,
 
     OP_INVALIDOPCODE = 0xff,
 };
diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json
index 70df0d0f697..a6e941c0743 100644
--- a/src/test/data/tx_valid.json
+++ b/src/test/data/tx_valid.json
@@ -520,5 +520,13 @@
 [[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]],
 "0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", "NONE"],
 
+ ["Test OP_INTERNALKEY"],
+ [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5",
+  0,
+  "1 0x20 0xa9e62de0f9782710f702214fc81c0f0f90fb3537987b3685caad6d52db305447",
+  155000]],
+"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251202ca3bc76489a54904ad2507005789afc1e6b362b451be89f69de39ddf9ba8abf0223cb2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac08721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
+ "DISCOURAGE_INTERNALKEY"],
+
 ["Make diffs cleaner by leaving a comment here without comma at the end"]
 ]
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index e6cf64611ef..04be9fbd543 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -65,6 +65,8 @@ static std::map<std::string, unsigned int> mapFlagNames = {
     {std::string("DISCOURAGE_UPGRADABLE_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE},
     {std::string("DISCOURAGE_OP_SUCCESS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS},
     {std::string("DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION},
+    {std::string("INTERNALKEY"), (unsigned int)SCRIPT_VERIFY_INTERNALKEY},
+    {std::string("DISCOURAGE_INTERNALKEY"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_INTERNALKEY},
 };
 
 unsigned int ParseScriptFlags(std::string strFlags)
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index 3275517888d..80aa66cfa31 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -924,4 +924,4 @@ def taproot_construct(pubkey, scripts=None, treat_internal_as_infinity=False):
     return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked)
 
 def is_op_success(o):
-    return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe)
+    return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xca) or (o >= 0xcc and o <= 0xfe)