diff --git a/deploy-config/hardhat.json b/deploy-config/hardhat.json
index cc9b968f4..6f65b4940 100644
--- a/deploy-config/hardhat.json
+++ b/deploy-config/hardhat.json
@@ -74,6 +74,7 @@
   "zkRangeHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
   "zkAggregationHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
   "nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000",
+  "tdxVerifier": "0x0000000000000000000000000000000000000001",
   "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001",
   "multiproofGenesisBlockNumber": 0,
   "multiproofBlockInterval": 100,
diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json
index 3cfbe7621..a09893dcc 100644
--- a/deploy-config/sepolia.json
+++ b/deploy-config/sepolia.json
@@ -1,5 +1,5 @@
 {
-  "finalSystemOwner": "0x6e427c3212C0b63BE0C382F97715D49b011bFF33",
+  "finalSystemOwner": "0x8C1a617BdB47342F9C17Ac8750E0b070c372C721",
   "superchainConfigGuardian": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301",
   "l1StartingBlockTag": "0x48f520cf4ddaf34c8336e6e490632ea3cf1e5e93b0b2bc6e917557e31845371b",
   "l1ChainID": 11155111,
@@ -58,7 +58,7 @@
   "useFaultProofs": true,
   "useRevenueShare": true,
   "chainFeesRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301",
-  "teeImageHash": "0xd04c8147410930fade36986c9357fd9a7591231e38e6e87e51183bd5909f509c",
+  "teeImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6",
   "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360",
   "multiproofGameType": 621,
   "teeProposer": "0xdb84125f2f4229c81c579f41bc129c71b174eb58",
@@ -66,10 +66,11 @@
   "zkRangeHash": "0x51708a6b4a3b800a14607e902c1aad47719905c12698a3e01ca8b3321761bc52",
   "zkAggregationHash": "0x005aa369dd9445e172018a9eaa4a0f9767b2c2079ece90ca120422b3c4c65f11",
   "nitroEnclaveVerifier": "0x77461a6434fFE3435206B19658F33274f3104e07",
+  "tdxVerifier": "0x5431a6Fd5365e9629BD2B0969e54c4f09d6781bA",
   "multiproofGenesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79",
   "multiproofGenesisBlockNumber": 37223829,
   "multiproofBlockInterval": 600,
   "multiproofIntermediateBlockInterval": 30,
-  "risc0VerifierRouter": "0xb121b667dd2cf27f95f9f5107137696f56f188f6",
+  "risc0VerifierRouter": "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187",
   "risc0SetBuilderImageId": "0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9"
 }
diff --git a/interfaces/multiproof/tee/ITDXVerifier.sol b/interfaces/multiproof/tee/ITDXVerifier.sol
new file mode 100644
index 000000000..24fc2a6ed
--- /dev/null
+++ b/interfaces/multiproof/tee/ITDXVerifier.sol
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { ZkCoProcessorType, ZkCoProcessorConfig } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+
+/// @notice Statuses that a TDX quote/collateral verifier may emit.
+/// @dev Unknown is index 0 so uninitialized values fail closed.
+enum TDXVerificationResult {
+    Unknown,
+    Success,
+    InvalidQuote,
+    QuoteSignatureInvalid,
+    RootCaNotTrusted,
+    PckCertChainInvalid,
+    TcbInfoInvalid,
+    QeIdentityInvalid,
+    TcbStatusNotAllowed,
+    CollateralExpired,
+    InvalidTimestamp,
+    ReportDataMismatch
+}
+
+/// @notice Intel TDX TCB status reduced to the statuses this contract's policy needs.
+/// @dev Unknown is index 0 so uninitialized values fail closed.
+enum TDXTcbStatus {
+    Unknown,
+    UpToDate,
+    SwHardeningNeeded,
+    ConfigurationNeeded,
+    ConfigurationAndSwHardeningNeeded,
+    OutOfDate,
+    OutOfDateConfigurationNeeded,
+    Revoked
+}
+
+/// @notice Public journal emitted by the off-chain/ZK TDX DCAP verifier.
+/// @param result Overall verification result after quote and collateral validation in the guest.
+/// @param tcbStatus Intel TDX TCB status for the platform.
+/// @param timestamp Quote timestamp in milliseconds since Unix epoch.
+/// @param collateralExpiration Earliest expiration timestamp in seconds across accepted collateral.
+/// @param rootCaHash Hash of the Intel root CA used to validate the PCK/collateral signing chains.
+/// @param pckCertHash Hash of the PCK leaf certificate that signed the quote attestation key chain.
+/// @param tcbInfoHash Hash of the TCB info collateral consumed by the guest.
+/// @param qeIdentityHash Hash of the QE identity collateral consumed by the guest.
+/// @param publicKey Uncompressed secp256k1 public key: 0x04 || x || y.
+/// @param signer Ethereum address derived from publicKey.
+/// @param imageHash Multiproof-compatible image hash derived from MRTD and RTMR0-3.
+/// @param mrTdHash keccak256 hash of the 48-byte MRTD measurement.
+/// @param reportDataPrefix First 32 bytes of TDREPORT.REPORTDATA.
+/// @param reportDataSuffix Last 32 bytes of TDREPORT.REPORTDATA, available for app-specific binding.
+struct TDXVerifierJournal {
+    TDXVerificationResult result;
+    TDXTcbStatus tcbStatus;
+    uint64 timestamp;
+    uint64 collateralExpiration;
+    bytes32 rootCaHash;
+    bytes32 pckCertHash;
+    bytes32 tcbInfoHash;
+    bytes32 qeIdentityHash;
+    bytes publicKey;
+    address signer;
+    bytes32 imageHash;
+    bytes32 mrTdHash;
+    bytes32 reportDataPrefix;
+    bytes32 reportDataSuffix;
+}
+
+/// @title ITDXVerifier
+/// @notice Interface for Intel TDX quote verification used by TDX-aware TEE prover registries.
+interface ITDXVerifier {
+    /// @notice Verifies a ZK proof of Intel TDX DCAP quote verification and returns attested signer metadata.
+    /// @param output ABI-encoded TDXVerifierJournal public values from the ZK verifier guest.
+    /// @param zkCoprocessor ZK proving system used to generate the proof.
+    /// @param proofBytes ZK proof bytes.
+    /// @return journal Verified TDX attestation metadata.
+    function verify(
+        bytes calldata output,
+        ZkCoProcessorType zkCoprocessor,
+        bytes calldata proofBytes
+    )
+        external
+        returns (TDXVerifierJournal memory journal);
+
+    /// @notice Retrieves the configuration for a specific coprocessor.
+    function getZkConfig(ZkCoProcessorType zkCoprocessor) external view returns (ZkCoProcessorConfig memory);
+
+    /// @notice Returns whether a TCB status is accepted by verifier policy.
+    function allowedTcbStatuses(TDXTcbStatus status) external view returns (bool);
+
+    /// @notice Updates the address authorized to submit verified proofs.
+    function setProofSubmitter(address newProofSubmitter) external;
+}
diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol
index d4943d2f5..250fa6ff3 100644
--- a/scripts/deploy/Deploy.s.sol
+++ b/scripts/deploy/Deploy.s.sol
@@ -238,6 +238,7 @@ contract Deploy is Deployer {
                 multiproofConfigHash: cfg.multiproofConfigHash(),
                 multiproofGameType: cfg.multiproofGameType(),
                 nitroEnclaveVerifier: cfg.nitroEnclaveVerifier(),
+                tdxVerifier: cfg.tdxVerifier(),
                 l2ChainID: cfg.l2ChainID(),
                 multiproofBlockInterval: cfg.multiproofBlockInterval(),
                 multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(),
diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol
index 890843946..5473e27cf 100644
--- a/scripts/deploy/DeployConfig.s.sol
+++ b/scripts/deploy/DeployConfig.s.sol
@@ -92,6 +92,7 @@ contract DeployConfig is Script {
     bytes32 public zkRangeHash;
     bytes32 public zkAggregationHash;
     address public nitroEnclaveVerifier;
+    address public tdxVerifier;
     bytes32 public multiproofGenesisOutputRoot;
     uint256 public multiproofGenesisBlockNumber;
     uint256 public multiproofBlockInterval;
@@ -204,6 +205,7 @@ contract DeployConfig is Script {
         zkRangeHash = stdJson.readBytes32(_json, "$.zkRangeHash");
         zkAggregationHash = stdJson.readBytes32(_json, "$.zkAggregationHash");
         nitroEnclaveVerifier = _readOr(_json, "$.nitroEnclaveVerifier", address(0));
+        tdxVerifier = _readOr(_json, "$.tdxVerifier", address(0));
         multiproofGenesisOutputRoot = bytes32(_readOr(_json, "$.multiproofGenesisOutputRoot", uint256(1)));
         multiproofGenesisBlockNumber = _readOr(_json, "$.multiproofGenesisBlockNumber", 0);
         multiproofBlockInterval = _readOr(_json, "$.multiproofBlockInterval", 100);
diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol
index 75c5dec36..4536e267b 100644
--- a/scripts/deploy/DeployImplementations.s.sol
+++ b/scripts/deploy/DeployImplementations.s.sol
@@ -44,6 +44,7 @@ import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol";
 import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol";
 import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol";
 import { GameType } from "src/dispute/lib/Types.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 
 contract DeployImplementations is Script {
     struct Input {
@@ -64,6 +65,7 @@ contract DeployImplementations is Script {
         bytes32 multiproofConfigHash;
         uint256 multiproofGameType;
         address nitroEnclaveVerifier;
+        address tdxVerifier;
         uint256 l2ChainID;
         uint256 multiproofBlockInterval;
         uint256 multiproofIntermediateBlockInterval;
@@ -613,7 +615,9 @@ contract DeployImplementations is Script {
         address teeVerifierImpl;
         {
             TEEProverRegistry scgImpl = new TEEProverRegistry(
-                INitroEnclaveVerifier(_input.nitroEnclaveVerifier), IDisputeGameFactory(address(1))
+                INitroEnclaveVerifier(_input.nitroEnclaveVerifier),
+                ITDXVerifier(_input.tdxVerifier),
+                IDisputeGameFactory(address(1))
             );
             vm.label(address(scgImpl), "TEEProverRegistryImpl");
             _output.teeProverRegistryImpl = scgImpl;
@@ -678,6 +682,7 @@ contract DeployImplementations is Script {
             "DeployImplementations: disputeGameFinalityDelaySeconds not set"
         );
         require(_input.mipsVersion != 0, "DeployImplementations: mipsVersion not set");
+        require(_input.tdxVerifier != address(0), "DeployImplementations: tdxVerifier not set");
         require(
             address(_input.superchainConfigProxy) != address(0), "DeployImplementations: superchainConfigProxy not set"
         );
diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol
index 13cfb48a0..d142b952c 100644
--- a/scripts/multiproof/DeployDevNoNitro.s.sol
+++ b/scripts/multiproof/DeployDevNoNitro.s.sol
@@ -26,10 +26,14 @@ pragma solidity 0.8.15;
  * SIGNER REGISTRATION (SIMPLIFIED)
  * ─────────────────────────────────────────────────────────────────────────────────
  *
- * After deployment, register a signer with a single call:
+ * After deployment, register one dev Nitro signer and one dev TDX signer:
  *
  *   cast send $TEE_PROVER_REGISTRY \
- *     "addDevSigner(address,bytes32)" $SIGNER_ADDRESS $TEE_IMAGE_HASH \
+ *     "addDevSigner(address,bytes32)" $NITRO_SIGNER_ADDRESS $TEE_IMAGE_HASH \
+ *     --private-key $OWNER_KEY --rpc-url $RPC_URL
+ *
+ *   cast send $TEE_PROVER_REGISTRY \
+ *     "addDevTDXSigner(address,bytes32)" $TDX_SIGNER_ADDRESS $TEE_IMAGE_HASH \
  *     --private-key $OWNER_KEY --rpc-url $RPC_URL
  *
  * No attestation, PCR0 registration, or certificate validation required.
@@ -72,6 +76,7 @@ import { IVerifier } from "interfaces/multiproof/IVerifier.sol";
 import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol";
 import { DevTEEProverRegistry } from "src/multiproof/mocks/MockDevTEEProverRegistry.sol";
 import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol";
 
 import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol";
@@ -89,6 +94,7 @@ contract DeployDevNoNitro is Script {
     DeployConfig public constant cfg =
         DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig"))))));
 
+    address public tdxVerifierAddr;
     address public teeProverRegistryProxy;
     address public teeVerifier;
     address public disputeGameFactory;
@@ -99,6 +105,7 @@ contract DeployDevNoNitro is Script {
     function setUp() public {
         DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" });
         cfg.read(Config.deployConfigPath());
+        tdxVerifierAddr = cfg.tdxVerifier();
     }
 
     function run() public {
@@ -110,8 +117,10 @@ contract DeployDevNoNitro is Script {
         console.log("TEE Proposer:", cfg.teeProposer());
         console.log("TEE Challenger:", cfg.teeChallenger());
         console.log("Game Type:", cfg.multiproofGameType());
+        console.log("TDXVerifier:", tdxVerifierAddr);
         console.log("");
         console.log("NOTE: Using DevTEEProverRegistry - NO attestation required.");
+        require(tdxVerifierAddr != address(0), "tdxVerifier must be set in config");
 
         vm.startBroadcast();
 
@@ -128,7 +137,11 @@ contract DeployDevNoNitro is Script {
     function _deployTEEContracts(GameType gameType) internal {
         address owner = cfg.finalSystemOwner();
         address teeRegistryImpl = address(
-            new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(disputeGameFactory))
+            new DevTEEProverRegistry(
+                INitroEnclaveVerifier(address(0)),
+                ITDXVerifier(tdxVerifierAddr),
+                IDisputeGameFactory(disputeGameFactory)
+            )
         );
         address[] memory initialProposers = new address[](2);
         initialProposers[0] = cfg.teeProposer();
@@ -197,6 +210,7 @@ contract DeployDevNoNitro is Script {
         console.log("========================================");
         console.log("\nTEE Contracts:");
         console.log("  DevTEEProverRegistry:", teeProverRegistryProxy);
+        console.log("  TDXVerifier:", tdxVerifierAddr);
         console.log("  TEEVerifier:", teeVerifier);
         console.log("\nInfrastructure:");
         console.log("  DisputeGameFactory:", disputeGameFactory);
@@ -208,9 +222,13 @@ contract DeployDevNoNitro is Script {
         console.log("  TEE Image Hash:", vm.toString(cfg.teeImageHash()));
         console.log("  Config Hash:", vm.toString(cfg.multiproofConfigHash()));
         console.log("========================================");
-        console.log("\n>>> NEXT STEP - Register dev signer (NO ATTESTATION NEEDED) <<<");
+        console.log("\n>>> NEXT STEP - Register dev Nitro and TDX signers (NO ATTESTATION NEEDED) <<<");
+        console.log("\ncast send", teeProverRegistryProxy);
+        console.log('  "addDevSigner(address,bytes32)" <NITRO_SIGNER_ADDRESS>');
+        console.log(" ", vm.toString(cfg.teeImageHash()));
+        console.log("  --private-key <OWNER_KEY> --rpc-url <RPC>");
         console.log("\ncast send", teeProverRegistryProxy);
-        console.log('  "addDevSigner(address,bytes32)" <SIGNER_ADDRESS>');
+        console.log('  "addDevTDXSigner(address,bytes32)" <TDX_SIGNER_ADDRESS>');
         console.log(" ", vm.toString(cfg.teeImageHash()));
         console.log("  --private-key <OWNER_KEY> --rpc-url <RPC>");
         console.log("\n========================================\n");
@@ -219,6 +237,7 @@ contract DeployDevNoNitro is Script {
     function _writeOutput() internal {
         string memory key = "deployment";
         vm.serializeAddress(key, "TEEProverRegistry", teeProverRegistryProxy);
+        vm.serializeAddress(key, "TDXVerifier", tdxVerifierAddr);
         vm.serializeAddress(key, "TEEVerifier", teeVerifier);
         vm.serializeAddress(key, "DisputeGameFactory", disputeGameFactory);
         vm.serializeAddress(key, "AnchorStateRegistry", address(mockAnchorRegistry));
diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol
index ed34a3f5e..5a3cae27f 100644
--- a/scripts/multiproof/DeployDevWithNitro.s.sol
+++ b/scripts/multiproof/DeployDevWithNitro.s.sol
@@ -37,6 +37,7 @@ pragma solidity 0.8.15;
  */
 
 import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 import { Proxy } from "src/universal/Proxy.sol";
 import { Script } from "forge-std/Script.sol";
 import { console2 as console } from "forge-std/console2.sol";
@@ -74,6 +75,7 @@ contract DeployDevWithNitro is Script {
         DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig"))))));
 
     address public nitroEnclaveVerifierAddr;
+    address public tdxVerifierAddr;
     address public teeProverRegistryProxy;
     address public teeVerifier;
     address public disputeGameFactory;
@@ -91,10 +93,12 @@ contract DeployDevWithNitro is Script {
 
         // NitroEnclaveVerifier must be pre-deployed (via DeployRiscZeroStack.s.sol)
         nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier();
+        tdxVerifierAddr = cfg.tdxVerifier();
         require(
             nitroEnclaveVerifierAddr != address(0),
             "nitroEnclaveVerifier must be set in config (deploy via DeployRiscZeroStack.s.sol first)"
         );
+        require(tdxVerifierAddr != address(0), "tdxVerifier must be set in config");
 
         console.log("=== Deploying Dev Infrastructure (WITH NITRO) ===");
         console.log("Chain ID:", block.chainid);
@@ -103,6 +107,7 @@ contract DeployDevWithNitro is Script {
         console.log("TEE Challenger:", cfg.teeChallenger());
         console.log("Game Type:", cfg.multiproofGameType());
         console.log("NitroEnclaveVerifier:", nitroEnclaveVerifierAddr);
+        console.log("TDXVerifier:", tdxVerifierAddr);
         console.log("");
         console.log("NOTE: Using REAL TEEProverRegistry - ZK attestation proof REQUIRED.");
 
@@ -122,7 +127,9 @@ contract DeployDevWithNitro is Script {
         address owner = cfg.finalSystemOwner();
         address teeRegistryImpl = address(
             new TEEProverRegistry(
-                INitroEnclaveVerifier(nitroEnclaveVerifierAddr), IDisputeGameFactory(disputeGameFactory)
+                INitroEnclaveVerifier(nitroEnclaveVerifierAddr),
+                ITDXVerifier(tdxVerifierAddr),
+                IDisputeGameFactory(disputeGameFactory)
             )
         );
         address[] memory initialProposers = new address[](2);
@@ -192,6 +199,7 @@ contract DeployDevWithNitro is Script {
         console.log("========================================");
         console.log("\nTEE Contracts:");
         console.log("  NitroEnclaveVerifier:", nitroEnclaveVerifierAddr);
+        console.log("  TDXVerifier:", tdxVerifierAddr);
         console.log("  TEEProverRegistry:", teeProverRegistryProxy);
         console.log("  TEEVerifier:", teeVerifier);
         console.log("\nInfrastructure:");
@@ -204,9 +212,12 @@ contract DeployDevWithNitro is Script {
         console.log("  TEE Image Hash:", vm.toString(cfg.teeImageHash()));
         console.log("  Config Hash:", vm.toString(cfg.multiproofConfigHash()));
         console.log("========================================");
-        console.log("\n>>> NEXT STEP: Register signer with ZK attestation proof <<<");
+        console.log("\n>>> NEXT STEP: Register one Nitro signer and one TDX signer <<<");
         console.log("\n  cast send", teeProverRegistryProxy);
-        console.log('    "registerSigner(bytes,bytes)" <ZK_OUTPUT> <ZK_PROOF_BYTES>');
+        console.log('    "registerSigner(bytes,bytes)" <NITRO_OUTPUT> <NITRO_PROOF_BYTES>');
+        console.log("    --private-key <OWNER_OR_MANAGER_KEY> --rpc-url <RPC>");
+        console.log("\n  cast send", teeProverRegistryProxy);
+        console.log('    "registerTDXSigner(bytes,bytes)" <TDX_OUTPUT> <TDX_PROOF_BYTES>');
         console.log("    --private-key <OWNER_OR_MANAGER_KEY> --rpc-url <RPC>");
         console.log("\n========================================\n");
     }
@@ -216,6 +227,7 @@ contract DeployDevWithNitro is Script {
         vm.serializeAddress(key, "TEEProverRegistry", teeProverRegistryProxy);
         vm.serializeAddress(key, "TEEVerifier", teeVerifier);
         vm.serializeAddress(key, "NitroEnclaveVerifier", nitroEnclaveVerifierAddr);
+        vm.serializeAddress(key, "TDXVerifier", tdxVerifierAddr);
         vm.serializeAddress(key, "DisputeGameFactory", disputeGameFactory);
         vm.serializeAddress(key, "AnchorStateRegistry", address(mockAnchorRegistry));
         vm.serializeAddress(key, "DelayedWETH", mockDelayedWETH);
diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol
new file mode 100644
index 000000000..bc9776b49
--- /dev/null
+++ b/scripts/multiproof/DeployDevWithTDX.s.sol
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.15;
+
+/**
+ * @title DeployDevWithTDX
+ * @notice Development deployment using the TDX signer-registration path.
+ *
+ * This deploys the same local multiproof testing infrastructure as the existing
+ * dev scripts, but configures TEEProverRegistry for TDX signer registration. Deploy
+ * TDXVerifier first with DeployTDXVerifier.s.sol and pass its address to run().
+ * The default run(address) entrypoint configures DEFAULT_TDX_REGISTRATION_MANAGER
+ * as the registry manager so it can submit TDX signer registrations.
+ */
+
+import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
+import { Proxy } from "src/universal/Proxy.sol";
+import { Script } from "forge-std/Script.sol";
+import { console2 as console } from "forge-std/console2.sol";
+import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
+import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
+import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol";
+import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
+import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol";
+import { GameType, Hash } from "src/dispute/lib/Types.sol";
+
+import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol";
+import { Config } from "scripts/libraries/Config.sol";
+import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
+
+import { AggregateVerifier } from "src/multiproof/AggregateVerifier.sol";
+import { IVerifier } from "interfaces/multiproof/IVerifier.sol";
+import { MockVerifier } from "src/multiproof/mocks/MockVerifier.sol";
+import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol";
+import { TEEVerifier } from "src/multiproof/tee/TEEVerifier.sol";
+
+import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol";
+import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol";
+import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol";
+
+contract DeployDevWithTDX is Script {
+    uint256 public constant INIT_BOND = 0.00001 ether;
+    address public constant DEFAULT_TDX_REGISTRATION_MANAGER = 0x93900CB7eCdB5994352b19DfD8a900Cd4fa437B7;
+
+    DeployConfig public constant cfg =
+        DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig"))))));
+
+    address public nitroEnclaveVerifierAddr;
+    address public tdxVerifierAddr;
+    address public tdxRegistrationManager;
+    address public teeProverRegistryProxy;
+    address public teeVerifier;
+    address public disputeGameFactory;
+    IAnchorStateRegistry public mockAnchorRegistry;
+    address public mockDelayedWETH;
+    address public aggregateVerifier;
+
+    function setUp() public {
+        DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" });
+        cfg.read(Config.deployConfigPath());
+    }
+
+    function run(address tdxVerifier) public {
+        run(tdxVerifier, DEFAULT_TDX_REGISTRATION_MANAGER);
+    }
+
+    function run(address tdxVerifier, address registrationManager) public {
+        nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier();
+        require(tdxVerifier != address(0), "tdxVerifier must be non-zero");
+        require(nitroEnclaveVerifierAddr != address(0), "nitroEnclaveVerifier must be set in config");
+        require(registrationManager != address(0), "registrationManager must be non-zero");
+        tdxVerifierAddr = tdxVerifier;
+        tdxRegistrationManager = registrationManager;
+
+        GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType()));
+
+        console.log("=== Deploying Dev Infrastructure (WITH TDX) ===");
+        console.log("Chain ID:", block.chainid);
+        console.log("Owner:", cfg.finalSystemOwner());
+        console.log("TEE Proposer:", cfg.teeProposer());
+        console.log("TEE Challenger:", cfg.teeChallenger());
+        console.log("Game Type:", cfg.multiproofGameType());
+        console.log("NitroEnclaveVerifier:", nitroEnclaveVerifierAddr);
+        console.log("TDXVerifier:", tdxVerifierAddr);
+        console.log("TDX Registration Manager:", tdxRegistrationManager);
+        console.log("");
+        console.log("NOTE: TDXVerifier owner must be the broadcaster/finalSystemOwner.");
+
+        vm.startBroadcast();
+
+        _deployInfrastructure(gameType);
+        _deployTDXContracts(gameType);
+        _deployAggregateVerifier(gameType);
+
+        vm.stopBroadcast();
+
+        _printSummary();
+        _writeOutput();
+    }
+
+    function _deployTDXContracts(GameType gameType) internal {
+        address owner = cfg.finalSystemOwner();
+        address registryImpl = address(
+            new TEEProverRegistry(
+                INitroEnclaveVerifier(nitroEnclaveVerifierAddr),
+                ITDXVerifier(tdxVerifierAddr),
+                IDisputeGameFactory(disputeGameFactory)
+            )
+        );
+
+        address[] memory initialProposers = new address[](2);
+        initialProposers[0] = cfg.teeProposer();
+        initialProposers[1] = cfg.teeChallenger();
+
+        Proxy registryProxy = new Proxy(msg.sender);
+        registryProxy.upgradeToAndCall(
+            registryImpl,
+            abi.encodeCall(TEEProverRegistry.initialize, (owner, tdxRegistrationManager, initialProposers, gameType))
+        );
+        registryProxy.changeAdmin(address(0xdead));
+        teeProverRegistryProxy = address(registryProxy);
+
+        ITDXVerifier(tdxVerifierAddr).setProofSubmitter(teeProverRegistryProxy);
+
+        teeVerifier = address(new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), mockAnchorRegistry));
+    }
+
+    function _deployInfrastructure(GameType gameType) internal {
+        address factoryImpl = address(new DisputeGameFactory());
+        MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.finalSystemOwner());
+
+        Proxy proxy = new Proxy(msg.sender);
+        proxy.upgradeTo(factoryImpl);
+        proxy.changeAdmin(address(proxyAdmin));
+        DisputeGameFactory(address(proxy)).initialize(cfg.finalSystemOwner());
+        disputeGameFactory = address(proxy);
+
+        MockAnchorStateRegistry asr = new MockAnchorStateRegistry();
+        mockAnchorRegistry = IAnchorStateRegistry(address(asr));
+        asr.initialize(
+            disputeGameFactory,
+            Hash.wrap(cfg.multiproofGenesisOutputRoot()),
+            cfg.multiproofGenesisBlockNumber(),
+            gameType
+        );
+    }
+
+    function _deployAggregateVerifier(GameType gameType) internal {
+        address zkVerifier = address(new MockVerifier(mockAnchorRegistry));
+        mockDelayedWETH = address(new MockDelayedWETH());
+
+        AggregateVerifier.ZkHashes memory zkHashes =
+            AggregateVerifier.ZkHashes({ rangeHash: cfg.zkRangeHash(), aggregateHash: cfg.zkAggregationHash() });
+
+        aggregateVerifier = address(
+            new AggregateVerifier(
+                gameType,
+                mockAnchorRegistry,
+                IDelayedWETH(payable(mockDelayedWETH)),
+                IVerifier(teeVerifier),
+                IVerifier(zkVerifier),
+                cfg.teeImageHash(),
+                zkHashes,
+                cfg.multiproofConfigHash(),
+                cfg.l2ChainID(),
+                cfg.multiproofBlockInterval(),
+                cfg.multiproofIntermediateBlockInterval()
+            )
+        );
+
+        DisputeGameFactory factory = DisputeGameFactory(disputeGameFactory);
+        factory.setImplementation(gameType, IDisputeGame(aggregateVerifier), "");
+        factory.setInitBond(gameType, INIT_BOND);
+    }
+
+    function _printSummary() internal view {
+        console.log("\n========================================");
+        console.log("      DEV DEPLOYMENT COMPLETE (TDX)");
+        console.log("========================================");
+        console.log("\nTDX Contracts:");
+        console.log("  NitroEnclaveVerifier:", nitroEnclaveVerifierAddr);
+        console.log("  TDXVerifier:", tdxVerifierAddr);
+        console.log("  TEEProverRegistry:", teeProverRegistryProxy);
+        console.log("  TDX Registration Manager:", tdxRegistrationManager);
+        console.log("  TEEVerifier:", teeVerifier);
+        console.log("\nInfrastructure:");
+        console.log("  DisputeGameFactory:", disputeGameFactory);
+        console.log("  AnchorStateRegistry (mock):", address(mockAnchorRegistry));
+        console.log("  DelayedWETH (mock):", mockDelayedWETH);
+        console.log("\nGame:");
+        console.log("  AggregateVerifier:", aggregateVerifier);
+        console.log("  Game Type:", cfg.multiproofGameType());
+        console.log("  TEE Image Hash:", vm.toString(cfg.teeImageHash()));
+        console.log("  Config Hash:", vm.toString(cfg.multiproofConfigHash()));
+        console.log("========================================");
+        console.log("\n>>> NEXT STEP: Register one Nitro signer and one TDX signer <<<");
+        console.log("\n  cast send", teeProverRegistryProxy);
+        console.log('    "registerSigner(bytes,bytes)" <NITRO_OUTPUT> <NITRO_PROOF_BYTES>');
+        console.log("    --private-key <OWNER_OR_MANAGER_KEY> --rpc-url <RPC>");
+        console.log("\n  cast send", teeProverRegistryProxy);
+        console.log('    "registerTDXSigner(bytes,bytes)" <TDX_OUTPUT> <PROOF_BYTES>');
+        console.log("    --private-key <OWNER_OR_MANAGER_KEY> --rpc-url <RPC>");
+        console.log("\n========================================\n");
+    }
+
+    function _writeOutput() internal {
+        string memory key = "deployment";
+        vm.serializeAddress(key, "NitroEnclaveVerifier", nitroEnclaveVerifierAddr);
+        vm.serializeAddress(key, "TDXVerifier", tdxVerifierAddr);
+        vm.serializeAddress(key, "TDXRegistrationManager", tdxRegistrationManager);
+        vm.serializeAddress(key, "TEEProverRegistry", teeProverRegistryProxy);
+        vm.serializeAddress(key, "TEEVerifier", teeVerifier);
+        vm.serializeAddress(key, "DisputeGameFactory", disputeGameFactory);
+        vm.serializeAddress(key, "AnchorStateRegistry", address(mockAnchorRegistry));
+        vm.serializeAddress(key, "DelayedWETH", mockDelayedWETH);
+        string memory json = vm.serializeAddress(key, "AggregateVerifier", aggregateVerifier);
+
+        string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-with-tdx.json");
+        vm.writeJson(json, outPath);
+        console.log("Deployment saved to:", outPath);
+    }
+}
diff --git a/scripts/multiproof/DeployTDXVerifier.s.sol b/scripts/multiproof/DeployTDXVerifier.s.sol
new file mode 100644
index 000000000..e5f955771
--- /dev/null
+++ b/scripts/multiproof/DeployTDXVerifier.s.sol
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+/**
+ * @title DeployTDXVerifier
+ * @notice Deploys the Solidity TDX policy verifier used by TEEProverRegistry.
+ *
+ * This script is separated from DeployDevWithTDX.s.sol because TDXVerifier imports
+ * verifier interfaces that require Solidity ^0.8.20, while the multiproof stack is
+ * pinned to Solidity 0.8.15.
+ *
+ * Usage:
+ *
+ *   forge script scripts/multiproof/DeployTDXVerifier.s.sol:DeployTDXVerifier \
+ *     --sig "run(address,address,bytes32,bytes32)" \
+ *     <OWNER> <RISC0_VERIFIER_ROUTER> <TDX_VERIFIER_ID> <INTEL_ROOT_CA_HASH> \
+ *     --rpc-url <RPC_URL> --broadcast --private-key <DEPLOYER_KEY>
+ *
+ * After running DeployDevWithTDX.s.sol, the TDX verifier's proofSubmitter is
+ * updated to the deployed TEEProverRegistry.
+ */
+
+import { Script } from "forge-std/Script.sol";
+import { console2 as console } from "forge-std/console2.sol";
+
+import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { TDXTcbStatus } from "interfaces/multiproof/tee/ITDXVerifier.sol";
+import { TDXVerifier } from "src/multiproof/tee/TDXVerifier.sol";
+
+contract DeployTDXVerifier is Script {
+    /// @notice Maximum TDX quote age accepted by TDXVerifier.
+    uint64 internal constant TDX_MAX_TIME_DIFF = 3600;
+
+    /// @param owner Owner for TDXVerifier.
+    /// @param risc0VerifierRouter Existing RISC Zero verifier router.
+    /// @param tdxVerifierId RISC Zero image ID for the TDX DCAP verifier guest.
+    /// @param intelRootCaHash Hash of the trusted Intel root CA consumed by the guest.
+    function run(address owner, address risc0VerifierRouter, bytes32 tdxVerifierId, bytes32 intelRootCaHash) public {
+        require(owner != address(0), "owner must be non-zero");
+        require(risc0VerifierRouter != address(0), "risc0VerifierRouter must be non-zero");
+        require(tdxVerifierId != bytes32(0), "tdxVerifierId must be non-zero");
+        require(intelRootCaHash != bytes32(0), "intelRootCaHash must be non-zero");
+
+        TDXTcbStatus[] memory allowedStatuses = new TDXTcbStatus[](2);
+        allowedStatuses[0] = TDXTcbStatus.UpToDate;
+        allowedStatuses[1] = TDXTcbStatus.SwHardeningNeeded;
+
+        ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({
+            verifierId: tdxVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter
+        });
+
+        console.log("=== Deploying TDXVerifier ===");
+        console.log("Owner:", owner);
+        console.log("RISC Zero Verifier Router:", risc0VerifierRouter);
+        console.log("TDX Verifier ID:", vm.toString(tdxVerifierId));
+        console.log("Intel Root CA Hash:", vm.toString(intelRootCaHash));
+        console.log("Max Time Diff:", TDX_MAX_TIME_DIFF);
+        console.log("");
+
+        vm.startBroadcast();
+
+        address tdxVerifier = address(
+            new TDXVerifier(
+                owner, TDX_MAX_TIME_DIFF, intelRootCaHash, owner, ZkCoProcessorType.RiscZero, zkConfig, allowedStatuses
+            )
+        );
+
+        vm.stopBroadcast();
+
+        console.log("TDXVerifier:", tdxVerifier);
+        console.log("");
+        console.log(">>> Use this address as the DeployDevWithTDX.s.sol argument <<<");
+
+        _writeOutput(tdxVerifier, risc0VerifierRouter, tdxVerifierId, intelRootCaHash);
+    }
+
+    function _writeOutput(
+        address tdxVerifier,
+        address risc0VerifierRouter,
+        bytes32 tdxVerifierId,
+        bytes32 intelRootCaHash
+    )
+        internal
+    {
+        string memory key = "deployment";
+        vm.serializeAddress(key, "TDXVerifier", tdxVerifier);
+        vm.serializeAddress(key, "RiscZeroVerifierRouter", risc0VerifierRouter);
+        vm.serializeBytes32(key, "TDXVerifierId", tdxVerifierId);
+        vm.serializeBytes32(key, "IntelRootCaHash", intelRootCaHash);
+        string memory json = vm.serializeUint(key, "MaxTimeDiff", TDX_MAX_TIME_DIFF);
+
+        string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-tdx-verifier.json");
+        vm.writeJson(json, outPath);
+        console.log("Deployment saved to:", outPath);
+    }
+}
diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md
index 01155f444..826664341 100644
--- a/scripts/multiproof/README.md
+++ b/scripts/multiproof/README.md
@@ -40,13 +40,13 @@ Ensure `finalSystemOwner` is set to the address you will deploy from (i.e. the a
 
 Other relevant fields:
 
-| Field | Description |
-|---|---|
-| `teeProposer` | Address to be registered as the TEE proposer |
-| `teeImageHash` | PCR0 hash used when registering the dev signer (use `bytes32(0x01...01)` for dev) |
-| `multiproofGameType` | Game type ID for the dispute game |
-| `multiproofGenesisOutputRoot` | Initial anchor output root |
-| `multiproofGenesisBlockNumber` | Initial anchor L2 block number |
+| Field                          | Description                                                                       |
+| ------------------------------ | --------------------------------------------------------------------------------- |
+| `teeProposer`                  | Address to be registered as the TEE proposer                                      |
+| `teeImageHash`                 | PCR0 hash used when registering the dev signer (use `bytes32(0x01...01)` for dev) |
+| `multiproofGameType`           | Game type ID for the dispute game                                                 |
+| `multiproofGenesisOutputRoot`  | Initial anchor output root                                                        |
+| `multiproofGenesisBlockNumber` | Initial anchor L2 block number                                                    |
 
 ### Step 2: Deploy contracts
 
@@ -96,9 +96,9 @@ This returns a raw byte array representing an uncompressed secp256k1 public key
 0x0cbe4A965B41DA6B2D5AF4d53c0C16a37d6f9F7D
 ```
 
-### Step 5: Register the dev signer
+### Step 5: Register the dev signers
 
-Call `addDevSigner` on the deployed `DevTEEProverRegistry` with the **signer address** derived in Step 4.
+Call `addDevSigner` for the Nitro signer and `addDevTDXSigner` for the TDX signer on the deployed `DevTEEProverRegistry`.
 
 > **Note:** PCR0 enforcement is handled by `AggregateVerifier` (which bakes `teeImageHash` into the
 > journal the enclave signs). The registry only tracks which signer addresses are valid.
@@ -106,10 +106,19 @@ Call `addDevSigner` on the deployed `DevTEEProverRegistry` with the **signer add
 ```bash
 # Replace:
 #   0x587d... with the TEEProverRegistry address from your deployment output
-#   0x080f... with the signer address derived in Step 4
+#   0x080f... with the Nitro signer address derived in Step 4
 cast send 0x587d410B205449fB889EC4a5b351D375C656d084 \
-  "addDevSigner(address)" \
+  "addDevSigner(address,bytes32)" \
   0x080f42420846c613158D7b4334257C78bE5A9B90 \
+  $TEE_IMAGE_HASH \
+  --rpc-url https://c3-chainproxy-eth-sepolia-full-dev.cbhq.net \
+  --ledger --mnemonic-derivation-path "m/44'/60'/1'/0/0"
+
+# Register a TDX dev signer for the same image hash.
+cast send 0x587d410B205449fB889EC4a5b351D375C656d084 \
+  "addDevTDXSigner(address,bytes32)" \
+  $TDX_SIGNER_ADDRESS \
+  $TEE_IMAGE_HASH \
   --rpc-url https://c3-chainproxy-eth-sepolia-full-dev.cbhq.net \
   --ledger --mnemonic-derivation-path "m/44'/60'/1'/0/0"
 ```
@@ -124,6 +133,134 @@ The deployer address (`finalSystemOwner`) is the owner of `DevTEEProverRegistry`
 
 ---
 
+## Path 3: TDX (Production-Path PoC)
+
+The TDX path follows the same split as Nitro: expensive attestation verification happens off-chain in a ZK guest,
+and Solidity verifies the proof plus the on-chain acceptance policy before registering the signer.
+
+| Contract               | Purpose                                                                                                                                                                                                                                       |
+| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `TDXVerifier`          | Verifies a RISC Zero or SP1 proof whose public values are an ABI-encoded `TDXVerifierJournal`, then checks trusted Intel root, TCB status policy, collateral expiry, quote freshness, signer derivation, and `REPORTDATA` public-key binding. |
+| `TEEProverRegistry`    | Registers Nitro signers through `registerSigner(bytes,bytes)` and TDX signers through `registerTDXSigner(bytes,bytes)`, tracking which TEE type each signer came from for `TEEVerifier`.                                                       |
+
+The ZK verifier guest is expected to perform the full Intel DCAP verification path:
+
+```text
+TD Quote signature
+PCK certificate chain
+TCB info signing chain and TCB status
+QE identity signing chain
+CRLs/revocation state
+TDREPORT field extraction
+```
+
+The Solidity verifier then enforces local policy over the proven journal. The PoC maps TDX measurements into the existing multiproof `TEE_IMAGE_HASH` field as:
+
+```text
+keccak256(MRTD || RTMR0 || RTMR1 || RTMR2 || RTMR3)
+```
+
+The attested public key must be supplied as an uncompressed 65-byte secp256k1 public key:
+
+```text
+0x04 || x || y
+```
+
+The quote's TDREPORT `REPORTDATA` must put `keccak256(x || y)` in the first 32 bytes. The last 32 bytes are returned by the verifier as app-specific binding data and emitted by the registry.
+
+`TEEVerifier` is still the proposal-proof verifier, but a TEE proposal proof now requires two signatures over the same journal: one from a Nitro-registered signer and one from a TDX-registered signer. The proof bytes are `proposer || signatureA || signatureB`; either signature order is accepted as long as both registered TEE types are present and both signers match the expected `TEE_IMAGE_HASH`.
+
+> **PoC boundary:** this repo now contains the production-shaped Solidity path and policy checks. The remaining off-chain piece is the actual RISC Zero/SP1 TDX DCAP guest that emits `TDXVerifierJournal` after verifying Intel collateral.
+
+### Step 1: Deploy `TDXVerifier`
+
+`TDXVerifier` is deployed separately because it depends on verifier interfaces that require Solidity `^0.8.20`, while the rest of the multiproof deployment stack is pinned to Solidity `0.8.15`.
+
+For Sepolia TDX testing, `scripts/multiproof/justfile` defaults to:
+
+```bash
+RISC0_VERIFIER_ROUTER=0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187
+TDX_VERIFIER_ID=0x1f1bc81fae82605af46a4c9a20f641922b7542befd9219644c7e6c59ccdeccab
+INTEL_ROOT_CA_HASH=0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009
+TDX_IMAGE_HASH=0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6
+TDX_SIGNER=0x6A1f38f20044e8e69EAC755144F14f973e7b8d6E
+TDX_REGISTRATION_MANAGER=0x93900CB7eCdB5994352b19DfD8a900Cd4fa437B7
+```
+
+`deploy-config/sepolia.json` uses the same `RISC0_VERIFIER_ROUTER` and `TDX_IMAGE_HASH`. The `TEEProverRegistry`
+constructor also requires a non-zero `tdxVerifier`; for config-driven deployments, set `tdxVerifier` in the deploy
+config to the deployed `TDXVerifier` address.
+
+```bash
+just --justfile scripts/multiproof/justfile deploy-tdx-verifier
+```
+
+To override any verifier input manually, pass all three verifier args:
+
+```bash
+forge script scripts/multiproof/DeployTDXVerifier.s.sol:DeployTDXVerifier \
+  --sig "run(address,address,bytes32,bytes32)" \
+  $OWNER \
+  $RISC0_VERIFIER_ROUTER \
+  $TDX_VERIFIER_ID \
+  $INTEL_ROOT_CA_HASH \
+  --rpc-url $L1_RPC_URL \
+  --broadcast \
+  --private-key $PRIVATE_KEY
+```
+
+The script saves output to `deployments/<chainId>-tdx-verifier.json`.
+
+### Step 2: Deploy the TDX multiproof test stack
+
+Set `DEPLOY_CONFIG_PATH` to the Sepolia deploy config and pass the `TDXVerifier` address from Step 1. The deploy config must also contain the `NitroEnclaveVerifier` address, because TEE proposal proofs now require both Nitro and TDX signatures. `finalSystemOwner` in the deploy config must be the account broadcasting this transaction because the script updates `TDXVerifier.proofSubmitter` to the deployed `TEEProverRegistry`.
+
+The TDX registry manager is set to `TDX_REGISTRATION_MANAGER`, allowing that address to call `registerTDXSigner(bytes,bytes)`. Register a Nitro signer through `registerSigner(bytes,bytes)` as well before submitting TEE proposal proofs.
+
+```bash
+just --justfile scripts/multiproof/justfile deploy-tdx-stack $TDX_VERIFIER
+```
+
+To override the manager manually, use:
+
+```bash
+forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \
+  --sig "run(address,address)" \
+  $TDX_VERIFIER \
+  $TDX_REGISTRATION_MANAGER \
+  --rpc-url $L1_RPC_URL \
+  --broadcast \
+  --private-key $PRIVATE_KEY
+```
+
+The script saves output to `deployments/<chainId>-dev-with-tdx.json`.
+
+### Step 3: Register Nitro and TDX signers
+
+Register a Nitro signer with a ZK-proven Nitro attestation:
+
+```bash
+cast send $TEE_PROVER_REGISTRY \
+  "registerSigner(bytes,bytes)" \
+  $NITRO_OUTPUT \
+  $NITRO_PROOF_BYTES \
+  --rpc-url $L1_RPC_URL \
+  --private-key $PRIVATE_KEY
+```
+
+Once you have the ABI-encoded `TDXVerifierJournal` output and matching RISC Zero proof bytes from the TDX DCAP guest, register the signer through the TDX-aware registry:
+
+```bash
+cast send $TEE_PROVER_REGISTRY \
+  "registerTDXSigner(bytes,bytes)" \
+  $TDX_OUTPUT \
+  $PROOF_BYTES \
+  --rpc-url $L1_RPC_URL \
+  --private-key $PRIVATE_KEY
+```
+
+---
+
 ## Pre-Seeding Games (Post-Deployment)
 
 After deploying via either path, you can pre-seed the `DisputeGameFactory` with a chain of `AggregateVerifier` games. This is useful for testing forward traversal at proposer restart — the proposer can walk the linked list of games to find where to resume.
@@ -184,9 +321,9 @@ forge script scripts/multiproof/SeedGames.s.sol \
 
 Optional env vars:
 
-| Variable | Default | Description |
-|---|---|---|
-| `GAME_COUNT` | 500 | Number of games to create |
+| Variable     | Default      | Description                   |
+| ------------ | ------------ | ----------------------------- |
+| `GAME_COUNT` | 500          | Number of games to create     |
 | `ROOTS_FILE` | `roots.json` | Path to the output roots JSON |
 
 ### Step 5: Verify on-chain
diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile
new file mode 100644
index 000000000..86559bb92
--- /dev/null
+++ b/scripts/multiproof/justfile
@@ -0,0 +1,84 @@
+set dotenv-load
+
+repo_root := justfile_directory() + "/../.."
+justfile_path := justfile_directory() + "/justfile"
+default_deploy_config := "deploy-config/sepolia.json"
+forge_account := "testnet-admin"
+l1_rpc_url := "https://ethereum-full-sepolia-k8s-dev.cbhq.net"
+
+sepolia_risc0_verifier_router := "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187"
+sepolia_tdx_verifier_id := "0x1f1bc81fae82605af46a4c9a20f641922b7542befd9219644c7e6c59ccdeccab"
+sepolia_intel_root_ca_hash := "0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009"
+sepolia_tdx_image_hash := "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6"
+sepolia_tdx_signer := "0x6A1f38f20044e8e69EAC755144F14f973e7b8d6E"
+sepolia_tdx_registration_manager := "0x93900CB7eCdB5994352b19DfD8a900Cd4fa437B7"
+
+# List available multiproof deployment recipes.
+default:
+  @just --justfile "{{justfile_path}}" --list
+
+# Deploy the TDXVerifier policy contract.
+#
+# Required env:
+#   L1_RPC_URL
+#
+# Args:
+#   risc0_router       RISC Zero verifier router
+#   tdx_verifier_id    RISC Zero image ID for the TDX DCAP verifier guest
+#   intel_root_ca_hash Trusted Intel root CA hash consumed by the guest
+deploy-tdx-verifier risc0_router=sepolia_risc0_verifier_router tdx_verifier_id=sepolia_tdx_verifier_id intel_root_ca_hash=sepolia_intel_root_ca_hash:
+  #!/usr/bin/env bash
+  set -euo pipefail
+  cd "{{repo_root}}"
+  owner="$(cast wallet address --account "{{forge_account}}")"
+  echo "Using {{forge_account}} as owner/deployer: $owner"
+  forge script scripts/multiproof/DeployTDXVerifier.s.sol:DeployTDXVerifier \
+    --sig "run(address,address,bytes32,bytes32)" \
+    "$owner" \
+    "{{risc0_router}}" \
+    "{{tdx_verifier_id}}" \
+    "{{intel_root_ca_hash}}" \
+    --rpc-url "{{l1_rpc_url}}" \
+    --broadcast \
+    --account "{{forge_account}}" \
+    --sender "$owner"
+
+# Print the Sepolia TDX constants used by these recipes.
+tdx-sepolia-config:
+  @echo "RISC0_VERIFIER_ROUTER={{sepolia_risc0_verifier_router}}"
+  @echo "TDX_VERIFIER_ID={{sepolia_tdx_verifier_id}}"
+  @echo "INTEL_ROOT_CA_HASH={{sepolia_intel_root_ca_hash}}"
+  @echo "TDX_IMAGE_HASH={{sepolia_tdx_image_hash}}"
+  @echo "TDX_SIGNER={{sepolia_tdx_signer}}"
+  @echo "TDX_REGISTRATION_MANAGER={{sepolia_tdx_registration_manager}}"
+
+# Deploy the TDX multiproof dev/test stack using deploy-config/sepolia.json.
+#
+# Required env:
+#   L1_RPC_URL
+#
+# Args:
+#   tdx_verifier TDXVerifier address from deploy-tdx-verifier
+#   tdx_manager  Manager that can call registerTDXSigner()
+deploy-tdx-stack tdx_verifier tdx_manager=sepolia_tdx_registration_manager deploy_config=default_deploy_config:
+  #!/usr/bin/env bash
+  set -euo pipefail
+  cd "{{repo_root}}"
+  owner="$(cast wallet address --account "{{forge_account}}")"
+  echo "Using {{forge_account}} as deployer: $owner"
+  DEPLOY_CONFIG_PATH="{{deploy_config}}" \
+  forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \
+    --sig "run(address,address)" \
+    "{{tdx_verifier}}" \
+    "{{tdx_manager}}" \
+    --rpc-url "{{l1_rpc_url}}" \
+    --broadcast \
+    --account "{{forge_account}}" \
+    --sender "$owner"
+
+# Print the two-step Sepolia command template.
+tdx-sepolia-help:
+  @echo 'export L1_RPC_URL=<sepolia-l1-rpc>'
+  @echo 'just deploy-tdx-verifier'
+  @echo 'just deploy-tdx-stack <tdx-verifier> [tdx-manager]'
+  @echo 'just tdx-sepolia-config'
diff --git a/snapshots/abi/DevTEEProverRegistry.json b/snapshots/abi/DevTEEProverRegistry.json
index 6d66717e0..0448f242a 100644
--- a/snapshots/abi/DevTEEProverRegistry.json
+++ b/snapshots/abi/DevTEEProverRegistry.json
@@ -6,6 +6,11 @@
         "name": "nitroVerifier",
         "type": "address"
       },
+      {
+        "internalType": "contract ITDXVerifier",
+        "name": "tdxVerifier",
+        "type": "address"
+      },
       {
         "internalType": "contract IDisputeGameFactory",
         "name": "factory",
@@ -54,6 +59,19 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "TDX_VERIFIER",
+    "outputs": [
+      {
+        "internalType": "contract ITDXVerifier",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -72,6 +90,24 @@
     "stateMutability": "nonpayable",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "imageHash",
+        "type": "bytes32"
+      }
+    ],
+    "name": "addDevTDXSigner",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -253,6 +289,24 @@
     "stateMutability": "nonpayable",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes",
+        "name": "output",
+        "type": "bytes"
+      },
+      {
+        "internalType": "bytes",
+        "name": "proofBytes",
+        "type": "bytes"
+      }
+    ],
+    "name": "registerTDXSigner",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [],
     "name": "renounceManagement",
@@ -317,6 +371,25 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "signerTEEType",
+    "outputs": [
+      {
+        "internalType": "enum TEEProverRegistry.TEEType",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -465,6 +538,31 @@
     "name": "SignerRegistered",
     "type": "event"
   },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "imageHash",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "reportDataSuffix",
+        "type": "bytes32"
+      }
+    ],
+    "name": "TDXSignerRegistered",
+    "type": "event"
+  },
   {
     "inputs": [],
     "name": "AttestationTooOld",
@@ -495,9 +593,40 @@
     "name": "InvalidPublicKey",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "InvalidTEEType",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "PCR0NotFound",
     "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      },
+      {
+        "internalType": "enum TEEProverRegistry.TEEType",
+        "name": "existingTEEType",
+        "type": "uint8"
+      },
+      {
+        "internalType": "enum TEEProverRegistry.TEEType",
+        "name": "newTEEType",
+        "type": "uint8"
+      }
+    ],
+    "name": "SignerTEETypeMismatch",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "TDXVerifierNotSet",
+    "type": "error"
   }
 ]
\ No newline at end of file
diff --git a/snapshots/abi/TDXVerifier.json b/snapshots/abi/TDXVerifier.json
new file mode 100644
index 000000000..bda2cd07b
--- /dev/null
+++ b/snapshots/abi/TDXVerifier.json
@@ -0,0 +1,808 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "uint64",
+        "name": "initialMaxTimeDiff",
+        "type": "uint64"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "initialRootCaHash",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "address",
+        "name": "initialProofSubmitter",
+        "type": "address"
+      },
+      {
+        "internalType": "enum ZkCoProcessorType",
+        "name": "zkCoprocessor",
+        "type": "uint8"
+      },
+      {
+        "components": [
+          {
+            "internalType": "bytes32",
+            "name": "verifierId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "aggregatorId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "address",
+            "name": "zkVerifier",
+            "type": "address"
+          }
+        ],
+        "internalType": "struct ZkCoProcessorConfig",
+        "name": "config",
+        "type": "tuple"
+      },
+      {
+        "internalType": "enum TDXTcbStatus[]",
+        "name": "initialAllowedTcbStatuses",
+        "type": "uint8[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum TDXTcbStatus",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "name": "allowedTcbStatuses",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "cancelOwnershipHandover",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "pendingOwner",
+        "type": "address"
+      }
+    ],
+    "name": "completeOwnershipHandover",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum ZkCoProcessorType",
+        "name": "zkCoprocessor",
+        "type": "uint8"
+      }
+    ],
+    "name": "getZkConfig",
+    "outputs": [
+      {
+        "components": [
+          {
+            "internalType": "bytes32",
+            "name": "verifierId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "aggregatorId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "address",
+            "name": "zkVerifier",
+            "type": "address"
+          }
+        ],
+        "internalType": "struct ZkCoProcessorConfig",
+        "name": "",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "maxTimeDiff",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "owner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "result",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "pendingOwner",
+        "type": "address"
+      }
+    ],
+    "name": "ownershipHandoverExpiresAt",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "result",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "proofSubmitter",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "renounceOwnership",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "requestOwnershipHandover",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "rootCaHash",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint64",
+        "name": "newMaxTimeDiff",
+        "type": "uint64"
+      }
+    ],
+    "name": "setMaxTimeDiff",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "newProofSubmitter",
+        "type": "address"
+      }
+    ],
+    "name": "setProofSubmitter",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "newRootCaHash",
+        "type": "bytes32"
+      }
+    ],
+    "name": "setRootCaHash",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum TDXTcbStatus",
+        "name": "status",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bool",
+        "name": "allowed",
+        "type": "bool"
+      }
+    ],
+    "name": "setTcbStatusAllowed",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum ZkCoProcessorType",
+        "name": "zkCoprocessor",
+        "type": "uint8"
+      },
+      {
+        "components": [
+          {
+            "internalType": "bytes32",
+            "name": "verifierId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "aggregatorId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "address",
+            "name": "zkVerifier",
+            "type": "address"
+          }
+        ],
+        "internalType": "struct ZkCoProcessorConfig",
+        "name": "config",
+        "type": "tuple"
+      }
+    ],
+    "name": "setZkConfiguration",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "transferOwnership",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes",
+        "name": "output",
+        "type": "bytes"
+      },
+      {
+        "internalType": "enum ZkCoProcessorType",
+        "name": "zkCoprocessor",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bytes",
+        "name": "proofBytes",
+        "type": "bytes"
+      }
+    ],
+    "name": "verify",
+    "outputs": [
+      {
+        "components": [
+          {
+            "internalType": "enum TDXVerificationResult",
+            "name": "result",
+            "type": "uint8"
+          },
+          {
+            "internalType": "enum TDXTcbStatus",
+            "name": "tcbStatus",
+            "type": "uint8"
+          },
+          {
+            "internalType": "uint64",
+            "name": "timestamp",
+            "type": "uint64"
+          },
+          {
+            "internalType": "uint64",
+            "name": "collateralExpiration",
+            "type": "uint64"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "rootCaHash",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "pckCertHash",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "tcbInfoHash",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "qeIdentityHash",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes",
+            "name": "publicKey",
+            "type": "bytes"
+          },
+          {
+            "internalType": "address",
+            "name": "signer",
+            "type": "address"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "imageHash",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "mrTdHash",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "reportDataPrefix",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "reportDataSuffix",
+            "type": "bytes32"
+          }
+        ],
+        "internalType": "struct TDXVerifierJournal",
+        "name": "journal",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "version",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum ZkCoProcessorType",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "name": "zkConfig",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "verifierId",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "aggregatorId",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "address",
+        "name": "zkVerifier",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "imageHash",
+        "type": "bytes32"
+      },
+      {
+        "indexed": true,
+        "internalType": "enum TDXTcbStatus",
+        "name": "tcbStatus",
+        "type": "uint8"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "pckCertHash",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "tcbInfoHash",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "qeIdentityHash",
+        "type": "bytes32"
+      }
+    ],
+    "name": "AttestationSubmitted",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint64",
+        "name": "maxTimeDiff",
+        "type": "uint64"
+      }
+    ],
+    "name": "MaxTimeDiffUpdated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "pendingOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipHandoverCanceled",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "pendingOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipHandoverRequested",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "oldOwner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipTransferred",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "proofSubmitter",
+        "type": "address"
+      }
+    ],
+    "name": "ProofSubmitterChanged",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "rootCaHash",
+        "type": "bytes32"
+      }
+    ],
+    "name": "RootCaHashUpdated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "enum TDXTcbStatus",
+        "name": "status",
+        "type": "uint8"
+      },
+      {
+        "indexed": false,
+        "internalType": "bool",
+        "name": "allowed",
+        "type": "bool"
+      }
+    ],
+    "name": "TcbStatusPolicyUpdated",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "enum ZkCoProcessorType",
+        "name": "zkCoprocessor",
+        "type": "uint8"
+      },
+      {
+        "components": [
+          {
+            "internalType": "bytes32",
+            "name": "verifierId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "aggregatorId",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "address",
+            "name": "zkVerifier",
+            "type": "address"
+          }
+        ],
+        "indexed": false,
+        "internalType": "struct ZkCoProcessorConfig",
+        "name": "config",
+        "type": "tuple"
+      }
+    ],
+    "name": "ZKConfigurationUpdated",
+    "type": "event"
+  },
+  {
+    "inputs": [],
+    "name": "AlreadyInitialized",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "CallerNotProofSubmitter",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint64",
+        "name": "collateralExpiration",
+        "type": "uint64"
+      }
+    ],
+    "name": "CollateralExpired",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "InvalidPublicKey",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint64",
+        "name": "timestampSeconds",
+        "type": "uint64"
+      },
+      {
+        "internalType": "uint256",
+        "name": "currentTimestamp",
+        "type": "uint256"
+      }
+    ],
+    "name": "InvalidTimestamp",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "NewOwnerIsZeroAddress",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "NoHandoverRequest",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "expected",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "actual",
+        "type": "bytes32"
+      }
+    ],
+    "name": "ReportDataMismatch",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "expected",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "actual",
+        "type": "bytes32"
+      }
+    ],
+    "name": "RootCaHashMismatch",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "expected",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "actual",
+        "type": "address"
+      }
+    ],
+    "name": "SignerMismatch",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum TDXVerificationResult",
+        "name": "result",
+        "type": "uint8"
+      }
+    ],
+    "name": "TDXVerificationFailed",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum TDXTcbStatus",
+        "name": "status",
+        "type": "uint8"
+      }
+    ],
+    "name": "TcbStatusNotAllowed",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "Unauthorized",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "UnknownZkCoprocessor",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "ZeroMaxTimeDiff",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "ZeroProofSubmitter",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "ZeroRootCaHash",
+    "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum ZkCoProcessorType",
+        "name": "zkCoprocessor",
+        "type": "uint8"
+      }
+    ],
+    "name": "ZkVerifierNotConfigured",
+    "type": "error"
+  }
+]
\ No newline at end of file
diff --git a/snapshots/abi/TEEProverRegistry.json b/snapshots/abi/TEEProverRegistry.json
index 510962c5d..ceff44a08 100644
--- a/snapshots/abi/TEEProverRegistry.json
+++ b/snapshots/abi/TEEProverRegistry.json
@@ -6,6 +6,11 @@
         "name": "nitroVerifier",
         "type": "address"
       },
+      {
+        "internalType": "contract ITDXVerifier",
+        "name": "tdxVerifier",
+        "type": "address"
+      },
       {
         "internalType": "contract IDisputeGameFactory",
         "name": "factory",
@@ -54,6 +59,19 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "TDX_VERIFIER",
+    "outputs": [
+      {
+        "internalType": "contract ITDXVerifier",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -235,6 +253,24 @@
     "stateMutability": "nonpayable",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes",
+        "name": "output",
+        "type": "bytes"
+      },
+      {
+        "internalType": "bytes",
+        "name": "proofBytes",
+        "type": "bytes"
+      }
+    ],
+    "name": "registerTDXSigner",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [],
     "name": "renounceManagement",
@@ -299,6 +335,25 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "signerTEEType",
+    "outputs": [
+      {
+        "internalType": "enum TEEProverRegistry.TEEType",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -447,6 +502,31 @@
     "name": "SignerRegistered",
     "type": "event"
   },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "imageHash",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "reportDataSuffix",
+        "type": "bytes32"
+      }
+    ],
+    "name": "TDXSignerRegistered",
+    "type": "event"
+  },
   {
     "inputs": [],
     "name": "AttestationTooOld",
@@ -477,9 +557,40 @@
     "name": "InvalidPublicKey",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "InvalidTEEType",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "PCR0NotFound",
     "type": "error"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      },
+      {
+        "internalType": "enum TEEProverRegistry.TEEType",
+        "name": "existingTEEType",
+        "type": "uint8"
+      },
+      {
+        "internalType": "enum TEEProverRegistry.TEEType",
+        "name": "newTEEType",
+        "type": "uint8"
+      }
+    ],
+    "name": "SignerTEETypeMismatch",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "TDXVerifierNotSet",
+    "type": "error"
   }
 ]
\ No newline at end of file
diff --git a/snapshots/abi/TEEVerifier.json b/snapshots/abi/TEEVerifier.json
index b491ef7a5..fff275ed7 100644
--- a/snapshots/abi/TEEVerifier.json
+++ b/snapshots/abi/TEEVerifier.json
@@ -116,6 +116,17 @@
     "name": "VerifierNullified",
     "type": "event"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "signer",
+        "type": "address"
+      }
+    ],
+    "name": "DuplicateSigner",
+    "type": "error"
+  },
   {
     "inputs": [
       {
@@ -164,6 +175,16 @@
     "name": "InvalidSigner",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "MissingNitroSignature",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "MissingTDXSignature",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "NotProperGame",
diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json
index 3bc67c56f..3887cc325 100644
--- a/snapshots/semver-lock.json
+++ b/snapshots/semver-lock.json
@@ -116,20 +116,24 @@
     "sourceCodeHash": "0xc0ff6e93b6e2b9111c11e81b5df8948ab71d02b9d2c4dfda982fcb615519f1f7"
   },
   "src/multiproof/AggregateVerifier.sol:AggregateVerifier": {
-    "initCodeHash": "0x10bb6a60f21de103d1da9ff310a38f571c53a8113d04a029087648debf3f0341",
-    "sourceCodeHash": "0xcd4c508223b9b461ec230bde03c1630fe68cd00b82ee84c39b93d9f3c0d71d37"
+    "initCodeHash": "0x9a77922f9f05c738e1a0884a1803a19302cd046e122af9ff38b8c3dc43e89409",
+    "sourceCodeHash": "0x9a48d272dd19ccd6df51999dcb6117308aea712f6d3731bdcc8b8d02b867c348"
   },
   "src/multiproof/tee/NitroEnclaveVerifier.sol:NitroEnclaveVerifier": {
     "initCodeHash": "0x100364f9b0c63a61538386ba91e73dcffba22d3f6dfe8efdbbf5ff347b6fce47",
     "sourceCodeHash": "0xbedb66738a0ab18600b2ed21de51d552a210c4cc71b2c971440e325791dc3ce7"
   },
+  "src/multiproof/tee/TDXVerifier.sol:TDXVerifier": {
+    "initCodeHash": "0xdfd5a00dc139565ec7102fd5ac05d91f923bfe1ef724e16a4cdfbcb60cd19c3c",
+    "sourceCodeHash": "0xb28e2632fc3591ecdb2d96cbc6b99a08854048a513f444d8282511293a0739e2"
+  },
   "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": {
-    "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0",
-    "sourceCodeHash": "0xf1ec1f02f540da659a204b26acf986fdce7d7d63bba67a87923f52453fb92ccb"
+    "initCodeHash": "0x5304faf5062088ee90dbd08eedbdacea39bdf48ffca689228e63d8f71f952bfb",
+    "sourceCodeHash": "0x37ed4874679b18cdf447a4d1890ecfeff648b0d762d8302827cd5e823b0af3ce"
   },
   "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": {
-    "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af",
-    "sourceCodeHash": "0xd5e01b4f4e69313e56e51f2f46b7bbe699ef8dc24b8a6385b8ebac4162e05353"
+    "initCodeHash": "0x6af4a7e162b2589076869a920a1f5b8b458d7f0c555068fdd2b82d44a51f3b0f",
+    "sourceCodeHash": "0xb0453b1fee0d1f2d1f53e22cbac4c43dd9e33ab33ac373ec54f81d433aeabbd5"
   },
   "src/multiproof/zk/ZKVerifier.sol:ZkVerifier": {
     "initCodeHash": "0xcdc5bd5fa8ffad76358b8f34a53b8c0cb36540176c31f82d52546373d0bd5245",
diff --git a/snapshots/storageLayout/DevTEEProverRegistry.json b/snapshots/storageLayout/DevTEEProverRegistry.json
index 93dcbfff3..30836afaa 100644
--- a/snapshots/storageLayout/DevTEEProverRegistry.json
+++ b/snapshots/storageLayout/DevTEEProverRegistry.json
@@ -75,5 +75,12 @@
     "offset": 0,
     "slot": "105",
     "type": "struct EnumerableSetLib.AddressSet"
+  },
+  {
+    "bytes": "32",
+    "label": "signerTEEType",
+    "offset": 0,
+    "slot": "106",
+    "type": "mapping(address => enum TEEProverRegistry.TEEType)"
   }
 ]
\ No newline at end of file
diff --git a/snapshots/storageLayout/TDXVerifier.json b/snapshots/storageLayout/TDXVerifier.json
new file mode 100644
index 000000000..2f13d341b
--- /dev/null
+++ b/snapshots/storageLayout/TDXVerifier.json
@@ -0,0 +1,37 @@
+[
+  {
+    "bytes": "8",
+    "label": "maxTimeDiff",
+    "offset": 0,
+    "slot": "0",
+    "type": "uint64"
+  },
+  {
+    "bytes": "20",
+    "label": "proofSubmitter",
+    "offset": 8,
+    "slot": "0",
+    "type": "address"
+  },
+  {
+    "bytes": "32",
+    "label": "rootCaHash",
+    "offset": 0,
+    "slot": "1",
+    "type": "bytes32"
+  },
+  {
+    "bytes": "32",
+    "label": "zkConfig",
+    "offset": 0,
+    "slot": "2",
+    "type": "mapping(enum ZkCoProcessorType => struct ZkCoProcessorConfig)"
+  },
+  {
+    "bytes": "32",
+    "label": "allowedTcbStatuses",
+    "offset": 0,
+    "slot": "3",
+    "type": "mapping(enum TDXTcbStatus => bool)"
+  }
+]
\ No newline at end of file
diff --git a/snapshots/storageLayout/TEEProverRegistry.json b/snapshots/storageLayout/TEEProverRegistry.json
index 93dcbfff3..30836afaa 100644
--- a/snapshots/storageLayout/TEEProverRegistry.json
+++ b/snapshots/storageLayout/TEEProverRegistry.json
@@ -75,5 +75,12 @@
     "offset": 0,
     "slot": "105",
     "type": "struct EnumerableSetLib.AddressSet"
+  },
+  {
+    "bytes": "32",
+    "label": "signerTEEType",
+    "offset": 0,
+    "slot": "106",
+    "type": "mapping(address => enum TEEProverRegistry.TEEType)"
   }
 ]
\ No newline at end of file
diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol
index 4c99f1d84..0c63db975 100644
--- a/src/multiproof/AggregateVerifier.sol
+++ b/src/multiproof/AggregateVerifier.sol
@@ -65,6 +65,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
 
     /// @notice The minimum number of proofs required to resolve the game.
     uint256 public constant PROOF_THRESHOLD = 1;
+
+    /// @notice TEE proof payload size: nitro signature(65) + tdx signature(65).
+    uint256 internal constant TEE_PROOF_BYTES_LENGTH = 130;
     ////////////////////////////////////////////////////////////////
     //                         Immutables                         //
     ////////////////////////////////////////////////////////////////
@@ -877,7 +880,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
     }
 
     /// @notice Verifies a TEE proof for the current game.
-    /// @param proofBytes The proof: signature(65).
+    /// @param proofBytes The proof: nitro signature(65) + tdx signature(65).
     function _verifyTeeProof(
         bytes calldata proofBytes,
         address proposer,
@@ -891,6 +894,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
         internal
         view
     {
+        if (proofBytes.length != TEE_PROOF_BYTES_LENGTH) revert InvalidProof();
+
         bytes32 journal = keccak256(
             abi.encodePacked(
                 proposer,
@@ -1036,8 +1041,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
     }
 
     /// @notice Semantic version.
-    /// @custom:semver 0.1.0
+    /// @custom:semver 0.2.0
     function version() public pure virtual returns (string memory) {
-        return "0.1.0";
+        return "0.2.0";
     }
 }
diff --git a/src/multiproof/mocks/MockDevTEEProverRegistry.sol b/src/multiproof/mocks/MockDevTEEProverRegistry.sol
index c01b124f1..e21c04b7c 100644
--- a/src/multiproof/mocks/MockDevTEEProverRegistry.sol
+++ b/src/multiproof/mocks/MockDevTEEProverRegistry.sol
@@ -2,8 +2,8 @@
 pragma solidity 0.8.15;
 
 import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
-import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol";
 
 import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol";
 
@@ -12,25 +12,29 @@ import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol";
 /// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification.
 ///      DO NOT deploy this contract to production networks.
 contract DevTEEProverRegistry is TEEProverRegistry {
-    using EnumerableSetLib for EnumerableSetLib.AddressSet;
-
     constructor(
         INitroEnclaveVerifier nitroVerifier,
+        ITDXVerifier tdxVerifier,
         IDisputeGameFactory factory
     )
-        TEEProverRegistry(nitroVerifier, factory)
+        TEEProverRegistry(nitroVerifier, tdxVerifier, factory)
     { }
 
-    /// @notice Registers a signer for testing (bypasses attestation verification).
+    /// @notice Registers a Nitro signer for testing (bypasses attestation verification).
     /// @dev Only callable by owner. For development/testing use only.
     ///      The imageHash parameter is stored so isValidSigner() can validate against
     ///      the current AggregateVerifier's TEE_IMAGE_HASH.
     /// @param signer The address of the signer to register.
     /// @param imageHash The TEE image hash to associate with this signer.
     function addDevSigner(address signer, bytes32 imageHash) external onlyOwner {
-        isRegisteredSigner[signer] = true;
-        signerImageHash[signer] = imageHash;
-        _registeredSigners.add(signer);
-        emit SignerRegistered(signer);
+        _registerSigner(signer, imageHash, TEEType.NITRO);
+    }
+
+    /// @notice Registers a TDX signer for testing (bypasses attestation verification).
+    /// @dev Only callable by owner. For development/testing use only.
+    /// @param signer The address of the signer to register.
+    /// @param imageHash The TEE image hash to associate with this signer.
+    function addDevTDXSigner(address signer, bytes32 imageHash) external onlyOwner {
+        _registerSigner(signer, imageHash, TEEType.TDX);
     }
 }
diff --git a/src/multiproof/tee/TDXVerifier.sol b/src/multiproof/tee/TDXVerifier.sol
new file mode 100644
index 000000000..d8175830a
--- /dev/null
+++ b/src/multiproof/tee/TDXVerifier.sol
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { Ownable } from "@solady/auth/Ownable.sol";
+import { ISP1Verifier } from "lib/sp1-contracts/contracts/src/ISP1Verifier.sol";
+import { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol";
+
+import { ISemver } from "interfaces/universal/ISemver.sol";
+import {
+    ITDXVerifier,
+    TDXTcbStatus,
+    TDXVerificationResult,
+    TDXVerifierJournal,
+    ZkCoProcessorType,
+    ZkCoProcessorConfig
+} from "interfaces/multiproof/tee/ITDXVerifier.sol";
+
+/// @title TDXVerifier
+/// @notice Production-shape Intel TDX DCAP verifier for multiproof signer registration.
+/// @dev The heavy TDX work is expected to happen in a ZK guest: quote signature verification, PCK chain
+///      validation, TCB info validation, QE identity validation, CRL checks, and extraction of TDREPORT fields.
+///      This contract verifies the ZK proof and enforces on-chain policy over the verified journal.
+contract TDXVerifier is Ownable, ITDXVerifier, ISemver {
+    /// @notice Conversion factor from milliseconds to seconds.
+    uint256 private constant MS_PER_SECOND = 1000;
+
+    /// @notice Maximum accepted age of a TDX quote, in seconds.
+    uint64 public maxTimeDiff;
+
+    /// @notice Address authorized to submit TDX proofs, expected to be the TDX-aware registry.
+    address public proofSubmitter;
+
+    /// @notice Hash of the trusted Intel root CA used by the ZK verifier guest.
+    bytes32 public rootCaHash;
+
+    /// @notice Configuration mapping for each supported ZK coprocessor type.
+    mapping(ZkCoProcessorType => ZkCoProcessorConfig) public zkConfig;
+
+    /// @inheritdoc ITDXVerifier
+    mapping(TDXTcbStatus => bool) public allowedTcbStatuses;
+
+    /// @notice Emitted when the trusted Intel root CA hash changes.
+    event RootCaHashUpdated(bytes32 indexed rootCaHash);
+
+    /// @notice Emitted when the proof submitter changes.
+    event ProofSubmitterChanged(address indexed proofSubmitter);
+
+    /// @notice Emitted when the quote timestamp tolerance changes.
+    event MaxTimeDiffUpdated(uint64 maxTimeDiff);
+
+    /// @notice Emitted when a TCB status policy changes.
+    event TcbStatusPolicyUpdated(TDXTcbStatus indexed status, bool allowed);
+
+    /// @notice Emitted when ZK configuration changes.
+    event ZKConfigurationUpdated(ZkCoProcessorType indexed zkCoprocessor, ZkCoProcessorConfig config);
+
+    /// @notice Emitted after a TDX attestation journal is accepted.
+    event AttestationSubmitted(
+        address indexed signer,
+        bytes32 indexed imageHash,
+        TDXTcbStatus indexed tcbStatus,
+        bytes32 pckCertHash,
+        bytes32 tcbInfoHash,
+        bytes32 qeIdentityHash
+    );
+
+    /// @notice Thrown when a zero maxTimeDiff is provided.
+    error ZeroMaxTimeDiff();
+
+    /// @notice Thrown when a zero address is provided for the proof submitter.
+    error ZeroProofSubmitter();
+
+    /// @notice Thrown when a zero root CA hash is provided.
+    error ZeroRootCaHash();
+
+    /// @notice Thrown when the caller is not the configured proof submitter.
+    error CallerNotProofSubmitter();
+
+    /// @notice Thrown when the ZK coprocessor type is unknown.
+    error UnknownZkCoprocessor();
+
+    /// @notice Thrown when the configured ZK verifier address is zero.
+    error ZkVerifierNotConfigured(ZkCoProcessorType zkCoprocessor);
+
+    /// @notice Thrown when the TDX verifier guest did not report success.
+    error TDXVerificationFailed(TDXVerificationResult result);
+
+    /// @notice Thrown when the journal root does not match the trusted Intel root.
+    error RootCaHashMismatch(bytes32 expected, bytes32 actual);
+
+    /// @notice Thrown when the journal's TCB status is not allowed.
+    error TcbStatusNotAllowed(TDXTcbStatus status);
+
+    /// @notice Thrown when collateral consumed by the ZK guest is expired.
+    error CollateralExpired(uint64 collateralExpiration);
+
+    /// @notice Thrown when the quote timestamp is outside policy.
+    error InvalidTimestamp(uint64 timestampSeconds, uint256 currentTimestamp);
+
+    /// @notice Thrown when the public key is not an uncompressed secp256k1 public key.
+    error InvalidPublicKey();
+
+    /// @notice Thrown when the journal signer does not match the supplied public key.
+    error SignerMismatch(address expected, address actual);
+
+    /// @notice Thrown when TDREPORT.REPORTDATA does not bind the supplied public key.
+    error ReportDataMismatch(bytes32 expected, bytes32 actual);
+
+    constructor(
+        address owner,
+        uint64 initialMaxTimeDiff,
+        bytes32 initialRootCaHash,
+        address initialProofSubmitter,
+        ZkCoProcessorType zkCoprocessor,
+        ZkCoProcessorConfig memory config,
+        TDXTcbStatus[] memory initialAllowedTcbStatuses
+    ) {
+        _initializeOwner(owner);
+        _setMaxTimeDiff(initialMaxTimeDiff);
+        _setRootCaHash(initialRootCaHash);
+        _setProofSubmitter(initialProofSubmitter);
+        _setZkConfiguration(zkCoprocessor, config);
+
+        for (uint256 i = 0; i < initialAllowedTcbStatuses.length; i++) {
+            _setTcbStatusAllowed(initialAllowedTcbStatuses[i], true);
+        }
+    }
+
+    /// @inheritdoc ITDXVerifier
+    function getZkConfig(ZkCoProcessorType zkCoprocessor) external view returns (ZkCoProcessorConfig memory) {
+        return zkConfig[zkCoprocessor];
+    }
+
+    /// @notice Sets the trusted Intel root CA hash.
+    function setRootCaHash(bytes32 newRootCaHash) external onlyOwner {
+        _setRootCaHash(newRootCaHash);
+    }
+
+    /// @notice Sets the proof submitter, expected to be the TDX-aware registry.
+    function setProofSubmitter(address newProofSubmitter) external onlyOwner {
+        _setProofSubmitter(newProofSubmitter);
+    }
+
+    /// @notice Sets maximum allowed quote age, in seconds.
+    function setMaxTimeDiff(uint64 newMaxTimeDiff) external onlyOwner {
+        _setMaxTimeDiff(newMaxTimeDiff);
+    }
+
+    /// @notice Sets whether a TDX TCB status is accepted.
+    function setTcbStatusAllowed(TDXTcbStatus status, bool allowed) external onlyOwner {
+        _setTcbStatusAllowed(status, allowed);
+    }
+
+    /// @notice Configures a ZK verifier/program for a coprocessor.
+    function setZkConfiguration(ZkCoProcessorType zkCoprocessor, ZkCoProcessorConfig memory config) external onlyOwner {
+        _setZkConfiguration(zkCoprocessor, config);
+    }
+
+    /// @inheritdoc ITDXVerifier
+    function verify(
+        bytes calldata output,
+        ZkCoProcessorType zkCoprocessor,
+        bytes calldata proofBytes
+    )
+        external
+        returns (TDXVerifierJournal memory journal)
+    {
+        if (msg.sender != proofSubmitter) revert CallerNotProofSubmitter();
+
+        _verifyZk(zkCoprocessor, output, proofBytes);
+        journal = abi.decode(output, (TDXVerifierJournal));
+        _verifyJournal(journal);
+
+        emit AttestationSubmitted(
+            journal.signer,
+            journal.imageHash,
+            journal.tcbStatus,
+            journal.pckCertHash,
+            journal.tcbInfoHash,
+            journal.qeIdentityHash
+        );
+    }
+
+    /// @notice Semantic version.
+    /// @custom:semver 0.2.0
+    function version() public pure virtual returns (string memory) {
+        return "0.2.0";
+    }
+
+    function _verifyJournal(TDXVerifierJournal memory journal) internal view {
+        if (journal.result != TDXVerificationResult.Success) revert TDXVerificationFailed(journal.result);
+        bytes32 expectedRootCaHash = rootCaHash;
+        if (journal.rootCaHash != expectedRootCaHash) {
+            revert RootCaHashMismatch(expectedRootCaHash, journal.rootCaHash);
+        }
+        if (!allowedTcbStatuses[journal.tcbStatus]) revert TcbStatusNotAllowed(journal.tcbStatus);
+        if (journal.collateralExpiration <= block.timestamp) revert CollateralExpired(journal.collateralExpiration);
+
+        uint64 timestamp = journal.timestamp / uint64(MS_PER_SECOND);
+        if (timestamp + maxTimeDiff <= block.timestamp || timestamp >= block.timestamp) {
+            revert InvalidTimestamp(timestamp, block.timestamp);
+        }
+
+        bytes32 publicKeyHash = _derivePublicKeyHash(journal.publicKey);
+        address signer = address(uint160(uint256(publicKeyHash)));
+        if (journal.signer != signer) revert SignerMismatch(signer, journal.signer);
+        if (journal.reportDataPrefix != publicKeyHash) {
+            revert ReportDataMismatch(publicKeyHash, journal.reportDataPrefix);
+        }
+    }
+
+    function _verifyZk(
+        ZkCoProcessorType zkCoprocessor,
+        bytes calldata output,
+        bytes calldata proofBytes
+    )
+        internal
+        view
+    {
+        ZkCoProcessorConfig memory config = zkConfig[zkCoprocessor];
+        if (config.zkVerifier == address(0)) revert ZkVerifierNotConfigured(zkCoprocessor);
+
+        if (zkCoprocessor == ZkCoProcessorType.RiscZero) {
+            IRiscZeroVerifier(config.zkVerifier).verify(proofBytes, config.verifierId, sha256(output));
+        } else if (zkCoprocessor == ZkCoProcessorType.Succinct) {
+            ISP1Verifier(config.zkVerifier).verifyProof(config.verifierId, output, proofBytes);
+        } else {
+            revert UnknownZkCoprocessor();
+        }
+    }
+
+    function _derivePublicKeyHash(bytes memory publicKey) internal pure returns (bytes32 publicKeyHash) {
+        if (publicKey.length != 65 || publicKey[0] != 0x04) revert InvalidPublicKey();
+        // Skip the 32-byte length word and the 0x04 uncompressed prefix; hash the 64-byte X||Y.
+        assembly {
+            publicKeyHash := keccak256(add(publicKey, 0x21), 64)
+        }
+    }
+
+    function _setRootCaHash(bytes32 newRootCaHash) internal {
+        if (newRootCaHash == bytes32(0)) revert ZeroRootCaHash();
+        rootCaHash = newRootCaHash;
+        emit RootCaHashUpdated(newRootCaHash);
+    }
+
+    function _setProofSubmitter(address newProofSubmitter) internal {
+        if (newProofSubmitter == address(0)) revert ZeroProofSubmitter();
+        proofSubmitter = newProofSubmitter;
+        emit ProofSubmitterChanged(newProofSubmitter);
+    }
+
+    function _setMaxTimeDiff(uint64 newMaxTimeDiff) internal {
+        if (newMaxTimeDiff == 0) revert ZeroMaxTimeDiff();
+        maxTimeDiff = newMaxTimeDiff;
+        emit MaxTimeDiffUpdated(newMaxTimeDiff);
+    }
+
+    function _setTcbStatusAllowed(TDXTcbStatus status, bool allowed) internal {
+        allowedTcbStatuses[status] = allowed;
+        emit TcbStatusPolicyUpdated(status, allowed);
+    }
+
+    function _setZkConfiguration(ZkCoProcessorType zkCoprocessor, ZkCoProcessorConfig memory config) internal {
+        zkConfig[zkCoprocessor] = config;
+        emit ZKConfigurationUpdated(zkCoprocessor, config);
+    }
+}
diff --git a/src/multiproof/tee/TEEProverRegistry.sol b/src/multiproof/tee/TEEProverRegistry.sol
index ec6652b44..81b255c8d 100644
--- a/src/multiproof/tee/TEEProverRegistry.sol
+++ b/src/multiproof/tee/TEEProverRegistry.sol
@@ -9,6 +9,7 @@ import {
     Pcr,
     Bytes48
 } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { ITDXVerifier, TDXVerifierJournal } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 import { OwnableManagedUpgradeable } from "lib/op-enclave/contracts/src/OwnableManagedUpgradeable.sol";
 import { ISemver } from "interfaces/universal/ISemver.sol";
 import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol";
@@ -16,15 +17,22 @@ import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"
 import { GameType } from "src/dispute/lib/Types.sol";
 
 /// @title TEEProverRegistry
-/// @notice Manages TEE signer registration via ZK-verified AWS Nitro attestation.
-/// @dev Signers are registered by providing a ZK proof of a valid AWS Nitro attestation document,
-///      verified through an external NitroEnclaveVerifier contract (Risc0).
+/// @notice Manages TEE signer registration via ZK-verified Nitro or TDX attestation.
+/// @dev Nitro signers are registered by providing a ZK proof of a valid AWS Nitro attestation document,
+///      verified through an external NitroEnclaveVerifier contract (Risc0). TDX signers are registered
+///      through the TDXVerifier configured on the implementation.
 ///      Registration is PCR0-agnostic: any enclave with a valid attestation can register,
 ///      enabling pre-registration before hardforks. PCR0 enforcement happens at proof-submission
 ///      time in TEEVerifier, which checks signerImageHash against the AggregateVerifier's
-///      TEE_IMAGE_HASH.
+///      TEE_IMAGE_HASH and requires one Nitro signer plus one TDX signer.
 contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     using EnumerableSetLib for EnumerableSetLib.AddressSet;
+    enum TEEType {
+        NONE,
+        NITRO,
+        TDX
+    }
+
     /// @notice Maximum age of an attestation document (60 minutes), in seconds.
     uint256 public constant MAX_AGE = 60 minutes;
 
@@ -36,6 +44,9 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     /// @notice The external NitroEnclaveVerifier contract used for ZK attestation verification.
     INitroEnclaveVerifier public immutable NITRO_VERIFIER;
 
+    /// @notice The external TDXVerifier contract used for ZK TDX quote verification.
+    ITDXVerifier public immutable TDX_VERIFIER;
+
     /// @notice The DisputeGameFactory used to look up the current AggregateVerifier and its TEE_IMAGE_HASH.
     IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY;
 
@@ -62,9 +73,15 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     ///      Enables O(1) on-chain enumeration via `getRegisteredSigners()`.
     EnumerableSetLib.AddressSet internal _registeredSigners;
 
+    /// @notice Mapping of signer address to the attestation type used to register it.
+    mapping(address => TEEType) public signerTEEType;
+
     /// @notice Emitted when a signer is registered.
     event SignerRegistered(address indexed signer);
 
+    /// @notice Emitted when a TDX signer is registered.
+    event TDXSignerRegistered(address indexed signer, bytes32 indexed imageHash, bytes32 reportDataSuffix);
+
     /// @notice Emitted when a signer is deregistered.
     event SignerDeregistered(address indexed signer);
 
@@ -95,9 +112,20 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     /// @notice Thrown when setting a game type whose AggregateVerifier has no TEE_IMAGE_HASH.
     error InvalidGameType();
 
-    constructor(INitroEnclaveVerifier nitroVerifier, IDisputeGameFactory factory) {
+    /// @notice Thrown when the TDX verifier is not configured.
+    error TDXVerifierNotSet();
+
+    /// @notice Thrown when attempting to register a signer with no TEE type.
+    error InvalidTEEType();
+
+    /// @notice Thrown when a signer is already registered under another TEE type.
+    error SignerTEETypeMismatch(address signer, TEEType existingTEEType, TEEType newTEEType);
+
+    constructor(INitroEnclaveVerifier nitroVerifier, ITDXVerifier tdxVerifier, IDisputeGameFactory factory) {
         if (address(factory) == address(0)) revert DisputeGameFactoryNotSet();
+        if (address(tdxVerifier) == address(0)) revert TDXVerifierNotSet();
         NITRO_VERIFIER = nitroVerifier;
+        TDX_VERIFIER = tdxVerifier;
         DISPUTE_GAME_FACTORY = factory;
         initialize({
             initialOwner: address(0xdEaD),
@@ -119,14 +147,8 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     /// @dev Validates that the new game type has an AggregateVerifier with a non-zero TEE_IMAGE_HASH.
     /// @param gameType_ The new game type ID.
     function setGameType(GameType gameType_) external onlyOwner {
-        // Validate the new game type points to a valid AggregateVerifier with a TEE_IMAGE_HASH
-        GameType oldGameType = gameType;
+        if (_getExpectedImageHash(gameType_) == bytes32(0)) revert InvalidGameType();
         gameType = gameType_;
-        bytes32 imageHash = _getExpectedImageHash();
-        if (imageHash == bytes32(0)) {
-            gameType = oldGameType;
-            revert InvalidGameType();
-        }
         emit GameTypeUpdated(gameType_);
     }
 
@@ -151,26 +173,30 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
         // the attestation is generated and when it is submitted to this contract.
         if (journal.timestamp / MS_PER_SECOND + MAX_AGE <= block.timestamp) revert AttestationTooOld();
 
-        // Extract the attestation's PCR0 and store it for TEEVerifier to check at
-        // proof-submission time. No comparison against the current TEE_IMAGE_HASH
-        // here — the registry accepts any valid attestation.
         bytes32 pcr0Hash = _extractPCR0Hash(journal.pcrs);
 
-        // The publicKey is encoded in ANSI X9.62 format: 0x04 || x || y (65 bytes).
-        // We skip the first byte (0x04 prefix) when hashing to derive the address.
+        // publicKey is ANSI X9.62 uncompressed: 0x04 || x || y (65 bytes). Skip the prefix and hash
+        // only the 64-byte x||y to derive the signer address.
         bytes memory pubKey = journal.publicKey;
         if (pubKey.length != 65) revert InvalidPublicKey();
         bytes32 publicKeyHash;
         assembly {
-            // Length is hardcoded to 64 to skip the 0x04 prefix and hash only the x and y coordinates
             publicKeyHash := keccak256(add(pubKey, 0x21), 64)
         }
         address enclaveAddress = address(uint160(uint256(publicKeyHash)));
 
-        isRegisteredSigner[enclaveAddress] = true;
-        signerImageHash[enclaveAddress] = pcr0Hash;
-        _registeredSigners.add(enclaveAddress);
-        emit SignerRegistered(enclaveAddress);
+        _registerSigner(enclaveAddress, pcr0Hash, TEEType.NITRO);
+    }
+
+    /// @notice Registers a signer using a ZK proof of Intel TDX DCAP quote verification.
+    /// @param output ABI-encoded TDXVerifierJournal public values from the ZK verifier guest.
+    /// @param proofBytes ZK proof bytes.
+    function registerTDXSigner(bytes calldata output, bytes calldata proofBytes) external onlyOwnerOrManager {
+        TDXVerifierJournal memory journal = TDX_VERIFIER.verify(output, ZkCoProcessorType.RiscZero, proofBytes);
+
+        _registerSigner(journal.signer, journal.imageHash, TEEType.TDX);
+
+        emit TDXSignerRegistered(journal.signer, journal.imageHash, journal.reportDataSuffix);
     }
 
     /// @notice Deregisters a signer.
@@ -178,6 +204,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     function deregisterSigner(address signer) external onlyOwnerOrManager {
         delete isRegisteredSigner[signer];
         delete signerImageHash[signer];
+        delete signerTEEType[signer];
         _registeredSigners.remove(signer);
         emit SignerDeregistered(signer);
     }
@@ -189,7 +216,8 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     /// @param signer The address to check.
     /// @return True if the signer is registered with the current image hash, false otherwise.
     function isValidSigner(address signer) external view returns (bool) {
-        return isRegisteredSigner[signer] && signerImageHash[signer] == _getExpectedImageHash();
+        if (!isRegisteredSigner[signer]) return false;
+        return signerImageHash[signer] == _getExpectedImageHash(gameType);
     }
 
     /// @notice Returns all currently registered signer addresses.
@@ -203,7 +231,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     /// @notice Returns the expected TEE image hash from the current AggregateVerifier.
     /// @return The TEE_IMAGE_HASH from the AggregateVerifier registered in the factory.
     function getExpectedImageHash() external view returns (bytes32) {
-        return _getExpectedImageHash();
+        return _getExpectedImageHash(gameType);
     }
 
     /// @notice Initializes the contract with owner, manager, proposers, and game type.
@@ -233,14 +261,29 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver {
     }
 
     /// @notice Semantic version.
-    /// @custom:semver 0.5.0
+    /// @custom:semver 0.7.0
     function version() public pure virtual returns (string memory) {
-        return "0.5.0";
+        return "0.7.0";
+    }
+
+    /// @dev Registers a signer and stores the image hash enforced by TEEVerifier at proof-submission time.
+    function _registerSigner(address signer, bytes32 imageHash, TEEType teeType) internal {
+        if (teeType == TEEType.NONE) revert InvalidTEEType();
+        TEEType existingTEEType = signerTEEType[signer];
+        if (existingTEEType != TEEType.NONE && existingTEEType != teeType) {
+            revert SignerTEETypeMismatch(signer, existingTEEType, teeType);
+        }
+
+        isRegisteredSigner[signer] = true;
+        signerImageHash[signer] = imageHash;
+        signerTEEType[signer] = teeType;
+        _registeredSigners.add(signer);
+        emit SignerRegistered(signer);
     }
 
-    /// @dev Reads TEE_IMAGE_HASH from the AggregateVerifier registered in the factory.
-    function _getExpectedImageHash() internal view returns (bytes32) {
-        address impl = address(DISPUTE_GAME_FACTORY.gameImpls(gameType));
+    /// @dev Reads TEE_IMAGE_HASH from the AggregateVerifier registered in the factory for `gameType_`.
+    function _getExpectedImageHash(GameType gameType_) internal view returns (bytes32) {
+        address impl = address(DISPUTE_GAME_FACTORY.gameImpls(gameType_));
         // AggregateVerifier.TEE_IMAGE_HASH() selector
         (bool success, bytes memory data) = impl.staticcall(abi.encodeWithSignature("TEE_IMAGE_HASH()"));
         if (!success || data.length != 32) revert ImageHashReadFailed();
diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol
index 73878af80..6fa772ee6 100644
--- a/src/multiproof/tee/TEEVerifier.sol
+++ b/src/multiproof/tee/TEEVerifier.sol
@@ -10,19 +10,25 @@ import { TEEProverRegistry } from "./TEEProverRegistry.sol";
 import { Verifier } from "../Verifier.sol";
 
 /// @title TEEVerifier
-/// @notice Stateless TEE proof verifier that validates signatures against registered signers.
+/// @notice Stateless TEE proof verifier that validates Nitro and TDX signatures against registered signers.
 /// @dev This contract is designed to be used as the TEE_VERIFIER in the AggregateVerifier.
-///      It verifies that proofs are signed by enclave addresses registered in TEEProverRegistry
-///      via AWS Nitro attestation. PCR0 (enclave image hash) enforcement is handled by
+///      It verifies that proofs are signed by a Nitro signer and a TDX signer registered in
+///      TEEProverRegistry. PCR0 / TDX image hash enforcement is handled by
 ///      AggregateVerifier, which bakes TEE_IMAGE_HASH into the journal that the enclave signs.
 ///      The contract is intentionally stateless - all state related to output proposals and
 ///      L1 origin verification is managed by the calling contract (e.g., AggregateVerifier).
 contract TEEVerifier is Verifier, ISemver {
     /// @notice The TEEProverRegistry contract that manages valid TEE signers.
-    /// @dev Signers are registered via AWS Nitro attestation in TEEProverRegistry.
+    /// @dev Signers are registered via Nitro or TDX attestation in TEEProverRegistry.
     TEEProverRegistry public immutable TEE_PROVER_REGISTRY;
 
-    /// @notice Thrown when the recovered signer is not a valid registered signer.
+    /// @notice Size of an ECDSA signature in bytes.
+    uint256 internal constant SIGNATURE_SIZE = 65;
+
+    /// @notice Size of a TEE proof: proposer(20) + nitro signature(65) + tdx signature(65).
+    uint256 internal constant TEE_PROOF_SIZE = 20 + SIGNATURE_SIZE * 2;
+
+    /// @notice Thrown when a recovered signer is not a valid registered signer.
     error InvalidSigner(address signer);
 
     /// @notice Thrown when the signer's registered image hash does not match the claimed imageId.
@@ -37,6 +43,15 @@ contract TEEVerifier is Verifier, ISemver {
     /// @notice Thrown when the proposer is not a valid registered proposer.
     error InvalidProposer(address proposer);
 
+    /// @notice Thrown when both signatures recover to the same signer.
+    error DuplicateSigner(address signer);
+
+    /// @notice Thrown when neither signature came from a Nitro-registered signer.
+    error MissingNitroSignature();
+
+    /// @notice Thrown when neither signature came from a TDX-registered signer.
+    error MissingTDXSignature();
+
     /// @notice Constructs the TEEVerifier contract.
     /// @param teeProverRegistry The TEEProverRegistry contract address.
     constructor(
@@ -49,9 +64,10 @@ contract TEEVerifier is Verifier, ISemver {
     }
 
     /// @notice Verifies a TEE proof for a state transition.
-    /// @param proofBytes The proof: proposer(20) + signature(65) = 85 bytes.
+    /// @param proofBytes The proof: proposer(20) + two signatures(65 each) = 150 bytes.
+    ///        One signature must recover to a Nitro signer and the other to a TDX signer.
     /// @param imageId The claimed TEE image hash (from the calling AggregateVerifier's TEE_IMAGE_HASH).
-    ///        Validated against the signer's registered image hash to prevent cross-game-type attacks.
+    ///        Validated against each signer's registered image hash to prevent cross-game-type attacks.
     /// @param journal The keccak256 hash of the proof's public inputs.
     /// @return valid Whether the proof is valid.
     function verify(
@@ -65,42 +81,62 @@ contract TEEVerifier is Verifier, ISemver {
         notNullified
         returns (bool)
     {
-        if (proofBytes.length < 85) revert InvalidProofFormat();
+        if (proofBytes.length != TEE_PROOF_SIZE) revert InvalidProofFormat();
 
         address proposer = address(bytes20(proofBytes[0:20]));
-        bytes calldata signature = proofBytes[20:85];
-
-        // Recover the signer from the signature
-        // The signature should be over the journal hash directly (not eth-signed-message prefixed)
-        (address signer, ECDSA.RecoverError err) = ECDSA.tryRecover(journal, signature);
-
-        if (err != ECDSA.RecoverError.NoError) {
-            revert InvalidSignature();
-        }
-
         if (!TEE_PROVER_REGISTRY.isValidProposer(proposer)) {
             revert InvalidProposer(proposer);
         }
 
-        // Check that the signer is registered
-        if (!TEE_PROVER_REGISTRY.isRegisteredSigner(signer)) {
-            revert InvalidSigner(signer);
+        bytes calldata firstSignature = proofBytes[20:20 + SIGNATURE_SIZE];
+        bytes calldata secondSignature = proofBytes[20 + SIGNATURE_SIZE:TEE_PROOF_SIZE];
+
+        address firstSigner = _recoverSigner(journal, firstSignature);
+        address secondSigner = _recoverSigner(journal, secondSignature);
+
+        TEEProverRegistry.TEEType firstTEEType = _validateSigner(firstSigner, imageId);
+        TEEProverRegistry.TEEType secondTEEType = _validateSigner(secondSigner, imageId);
+
+        if (firstSigner == secondSigner) revert DuplicateSigner(firstSigner);
+
+        if (firstTEEType != TEEProverRegistry.TEEType.NITRO && secondTEEType != TEEProverRegistry.TEEType.NITRO) {
+            revert MissingNitroSignature();
+        }
+        if (firstTEEType != TEEProverRegistry.TEEType.TDX && secondTEEType != TEEProverRegistry.TEEType.TDX) {
+            revert MissingTDXSignature();
         }
 
-        // Check that the signer's registered image hash matches the calling AggregateVerifier's imageId.
-        // This prevents a signer registered under one enclave image from being used in a game
+        return true;
+    }
+
+    function _recoverSigner(bytes32 journal, bytes calldata signature) internal pure returns (address signer) {
+        // The signature should be over the journal hash directly (not eth-signed-message prefixed).
+        ECDSA.RecoverError err;
+        (signer, err) = ECDSA.tryRecover(journal, signature);
+        if (err != ECDSA.RecoverError.NoError) revert InvalidSignature();
+    }
+
+    function _validateSigner(address signer, bytes32 imageId)
+        internal
+        view
+        returns (TEEProverRegistry.TEEType teeType)
+    {
+        // A registered signer always has a non-NONE TEE type, so this single read also
+        // serves as the registration check (saves an SLOAD versus calling isRegisteredSigner).
+        teeType = TEE_PROVER_REGISTRY.signerTEEType(signer);
+        if (teeType == TEEProverRegistry.TEEType.NONE) revert InvalidSigner(signer);
+
+        // Prevents a signer registered under one enclave image from being used in a game
         // that expects a different image (e.g., after an upgrade or across game types).
         bytes32 registeredImageHash = TEE_PROVER_REGISTRY.signerImageHash(signer);
         if (registeredImageHash != imageId) {
             revert ImageIdMismatch(registeredImageHash, imageId);
         }
-
-        return true;
     }
 
     /// @notice Semantic version.
-    /// @custom:semver 0.2.0
+    /// @custom:semver 0.3.0
     function version() public pure virtual returns (string memory) {
-        return "0.2.0";
+        return "0.3.0";
     }
 }
diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol
index c8bf67fd1..92f5b9028 100644
--- a/test/multiproof/AggregateVerifier.t.sol
+++ b/test/multiproof/AggregateVerifier.t.sol
@@ -268,7 +268,7 @@ contract AggregateVerifierTest is BaseTest {
         Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber)));
 
         bytes memory proofBytes =
-            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw());
+            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(130));
 
         vm.expectRevert(
             abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number)
@@ -290,7 +290,7 @@ contract AggregateVerifierTest is BaseTest {
         Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber)));
 
         bytes memory proofBytes =
-            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw());
+            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(130));
 
         vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number));
         _createAggregateVerifierGame(
@@ -326,7 +326,7 @@ contract AggregateVerifierTest is BaseTest {
         Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber)));
 
         bytes memory proofBytes =
-            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw());
+            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(130));
 
         _createAggregateVerifierGame(
             TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes
@@ -352,7 +352,7 @@ contract AggregateVerifierTest is BaseTest {
         );
 
         bytes memory proofBytes =
-            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, rootClaim.raw());
+            abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, new bytes(130));
 
         _createAggregateVerifierGame(
             TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes
diff --git a/test/multiproof/BaseTest.t.sol b/test/multiproof/BaseTest.t.sol
index 55c4d311d..7fd9e0b0d 100644
--- a/test/multiproof/BaseTest.t.sol
+++ b/test/multiproof/BaseTest.t.sol
@@ -218,10 +218,33 @@ contract BaseTest is Test {
         bytes32 l1OriginHash = blockhash(block.number - 1);
         // Use the previous block number as l1OriginNumber
         uint256 l1OriginNumber = block.number - 1;
-        // Add some padding/signature data (65 bytes minimum for a signature)
-        bytes memory signature = abi.encodePacked(salt, bytes32(0), bytes32(0), uint8(27));
+        return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, _generateProofBody(salt, proofType));
+    }
+
+    function _generateProposalProof(
+        bytes memory salt,
+        AggregateVerifier.ProofType proofType
+    )
+        internal
+        pure
+        returns (bytes memory)
+    {
+        return abi.encodePacked(uint8(proofType), _generateProofBody(salt, proofType));
+    }
 
-        return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, signature);
+    function _generateProofBody(
+        bytes memory salt,
+        AggregateVerifier.ProofType proofType
+    )
+        internal
+        pure
+        returns (bytes memory)
+    {
+        if (proofType == AggregateVerifier.ProofType.TEE) {
+            bytes32 saltHash = keccak256(salt);
+            return abi.encodePacked(saltHash, bytes32(0), uint8(27), saltHash, bytes32(uint256(1)), uint8(28));
+        }
+        return abi.encodePacked(salt, bytes32(0), bytes32(0), uint8(27));
     }
 
     function _generateIntermediateRootsExceptLast(uint256 l2BlockNumber) internal pure returns (bytes memory) {
diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol
index b36cf03e4..29399e507 100644
--- a/test/multiproof/Challenge.t.sol
+++ b/test/multiproof/Challenge.t.sol
@@ -80,7 +80,7 @@ contract ChallengeTest is BaseTest {
         );
 
         Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2")));
-        bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
+        bytes memory teeProof2 = _generateProposalProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
 
         vm.expectRevert(AggregateVerifier.InvalidProofType.selector);
         game1.challenge(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw());
@@ -170,7 +170,7 @@ contract ChallengeTest is BaseTest {
         );
 
         Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2")));
-        bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
+        bytes memory teeProof2 = _generateProposalProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
 
         game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw());
 
@@ -284,7 +284,7 @@ contract ChallengeTest is BaseTest {
             _createAggregateVerifierGame(TEE_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), teeProofB);
 
         Claim rootNullifyB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-nullify-b")));
-        bytes memory teeNullifyB = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE);
+        bytes memory teeNullifyB = _generateProposalProof("tee-nullify-b", AggregateVerifier.ProofType.TEE);
         uint256 lastIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
         gameB.nullify(teeNullifyB, lastIdx, rootNullifyB.raw());
         assertTrue(teeVerifier.nullified());
diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol
index 5bc8c4aa9..3a28acc8c 100644
--- a/test/multiproof/Nullify.t.sol
+++ b/test/multiproof/Nullify.t.sol
@@ -20,7 +20,7 @@ contract NullifyTest is BaseTest {
         );
 
         Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2")));
-        bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
+        bytes memory teeProof2 = _generateProposalProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
 
         game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw());
 
@@ -87,7 +87,7 @@ contract NullifyTest is BaseTest {
         assertEq(game.expectedResolution().raw(), block.timestamp + 1 days);
 
         Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2")));
-        bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
+        bytes memory teeProof2 = _generateProposalProof("tee-proof-2", AggregateVerifier.ProofType.TEE);
         game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw());
 
         assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS));
@@ -195,7 +195,7 @@ contract NullifyTest is BaseTest {
         assertEq(gameA.proofCount(), 1);
 
         Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "nullify-b")));
-        bytes memory teeProofNullify = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE);
+        bytes memory teeProofNullify = _generateProposalProof("tee-nullify-b", AggregateVerifier.ProofType.TEE);
         uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1;
         gameB.nullify(teeProofNullify, lastIntermediateIdx, rootClaimNullify.raw());
 
diff --git a/test/multiproof/TDXVerifier.t.sol b/test/multiproof/TDXVerifier.t.sol
new file mode 100644
index 000000000..291b34614
--- /dev/null
+++ b/test/multiproof/TDXVerifier.t.sol
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { Test } from "forge-std/Test.sol";
+
+import { TDXTcbStatus, TDXVerificationResult, TDXVerifierJournal } from "interfaces/multiproof/tee/ITDXVerifier.sol";
+import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+
+import { TDXVerifier } from "src/multiproof/tee/TDXVerifier.sol";
+
+contract TDXVerifierTest is Test {
+    TDXVerifier internal verifier;
+
+    address internal owner;
+    address internal proofSubmitter;
+    address internal mockRiscZeroVerifier;
+    address internal mockSP1Verifier;
+
+    bytes32 internal constant ROOT_CA_HASH = keccak256("intel-root-ca");
+    bytes32 internal constant WRONG_ROOT_CA_HASH = keccak256("wrong-root-ca");
+    bytes32 internal constant VERIFIER_ID = keccak256("tdx-verifier-id");
+    bytes32 internal constant AGGREGATOR_ID = keccak256("tdx-aggregator-id");
+    bytes32 internal constant IMAGE_HASH = keccak256("tdx-image");
+    bytes32 internal constant MRTD_HASH = keccak256("mrtd");
+    bytes32 internal constant REPORT_DATA_SUFFIX = keccak256("multiproof-tdx-poc");
+
+    uint64 internal constant MAX_TIME_DIFF = 3600;
+    uint256 internal constant NOW = 1_700_000_000;
+
+    function setUp() public {
+        vm.warp(NOW);
+
+        owner = address(this);
+        proofSubmitter = address(this);
+        mockRiscZeroVerifier = makeAddr("mock-risc-zero");
+        mockSP1Verifier = makeAddr("mock-sp1");
+
+        TDXTcbStatus[] memory allowedStatuses = new TDXTcbStatus[](2);
+        allowedStatuses[0] = TDXTcbStatus.UpToDate;
+        allowedStatuses[1] = TDXTcbStatus.SwHardeningNeeded;
+
+        verifier = new TDXVerifier(
+            owner,
+            MAX_TIME_DIFF,
+            ROOT_CA_HASH,
+            proofSubmitter,
+            ZkCoProcessorType.Succinct,
+            ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier }),
+            allowedStatuses
+        );
+    }
+
+    function testVerifySucceedsWithSP1ProofAndAllowedJournal() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        TDXVerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+
+        assertEq(result.signer, journal.signer);
+        assertEq(result.imageHash, IMAGE_HASH);
+        assertEq(uint256(result.tcbStatus), uint256(TDXTcbStatus.UpToDate));
+    }
+
+    function testVerifySucceedsWithRiscZeroProof() public {
+        ZkCoProcessorConfig memory config = ZkCoProcessorConfig({
+            verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier
+        });
+        verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config);
+
+        TDXVerifierJournal memory journal = _successJournal();
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"5678";
+        _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes);
+
+        TDXVerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes);
+
+        assertEq(result.signer, journal.signer);
+        assertEq(result.imageHash, IMAGE_HASH);
+    }
+
+    function testVerifyRevertsIfNotProofSubmitter() public {
+        bytes memory output = abi.encode(_successJournal());
+
+        vm.prank(makeAddr("not-submitter"));
+        vm.expectRevert(TDXVerifier.CallerNotProofSubmitter.selector);
+        verifier.verify(output, ZkCoProcessorType.Succinct, "");
+    }
+
+    function testVerifyRevertsIfZkVerifierNotConfigured() public {
+        bytes memory output = abi.encode(_successJournal());
+
+        vm.expectRevert(
+            abi.encodeWithSelector(TDXVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.RiscZero)
+        );
+        verifier.verify(output, ZkCoProcessorType.RiscZero, "");
+    }
+
+    function testVerifyRevertsForUnknownCoprocessor() public {
+        ZkCoProcessorConfig memory config =
+            ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier });
+        verifier.setZkConfiguration(ZkCoProcessorType.Unknown, config);
+
+        bytes memory output = abi.encode(_successJournal());
+
+        vm.expectRevert(TDXVerifier.UnknownZkCoprocessor.selector);
+        verifier.verify(output, ZkCoProcessorType.Unknown, "");
+    }
+
+    function testVerifyRevertsWhenGuestReportsFailure() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.result = TDXVerificationResult.PckCertChainInvalid;
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(abi.encodeWithSelector(TDXVerifier.TDXVerificationFailed.selector, journal.result));
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testVerifyRevertsWhenRootCaHashMismatches() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.rootCaHash = WRONG_ROOT_CA_HASH;
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(
+            abi.encodeWithSelector(TDXVerifier.RootCaHashMismatch.selector, ROOT_CA_HASH, WRONG_ROOT_CA_HASH)
+        );
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testVerifyRevertsWhenTcbStatusIsNotAllowed() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.tcbStatus = TDXTcbStatus.ConfigurationNeeded;
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(abi.encodeWithSelector(TDXVerifier.TcbStatusNotAllowed.selector, journal.tcbStatus));
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testOwnerCanAllowAdditionalTcbStatus() public {
+        verifier.setTcbStatusAllowed(TDXTcbStatus.ConfigurationNeeded, true);
+
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.tcbStatus = TDXTcbStatus.ConfigurationNeeded;
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        TDXVerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+
+        assertEq(uint256(result.tcbStatus), uint256(TDXTcbStatus.ConfigurationNeeded));
+    }
+
+    function testVerifyRevertsWhenCollateralExpired() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.collateralExpiration = uint64(block.timestamp);
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(abi.encodeWithSelector(TDXVerifier.CollateralExpired.selector, journal.collateralExpiration));
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testVerifyRevertsWhenTimestampTooOld() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.timestamp = uint64(block.timestamp - MAX_TIME_DIFF) * 1000;
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(
+            abi.encodeWithSelector(
+                TDXVerifier.InvalidTimestamp.selector, uint64(block.timestamp - MAX_TIME_DIFF), block.timestamp
+            )
+        );
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testVerifyRevertsWhenTimestampIsFromFuture() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        journal.timestamp = uint64(block.timestamp) * 1000;
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(
+            abi.encodeWithSelector(TDXVerifier.InvalidTimestamp.selector, uint64(block.timestamp), block.timestamp)
+        );
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testVerifyRevertsWhenReportDataDoesNotBindPublicKey() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        bytes32 expected = journal.reportDataPrefix;
+        journal.reportDataPrefix = keccak256("wrong-report-data");
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(
+            abi.encodeWithSelector(TDXVerifier.ReportDataMismatch.selector, expected, journal.reportDataPrefix)
+        );
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function testVerifyRevertsWhenSignerDoesNotMatchPublicKey() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        address expected = journal.signer;
+        journal.signer = makeAddr("wrong-signer");
+        bytes memory output = abi.encode(journal);
+        bytes memory proofBytes = hex"1234";
+        _mockSP1Verify(VERIFIER_ID, output, proofBytes);
+
+        vm.expectRevert(abi.encodeWithSelector(TDXVerifier.SignerMismatch.selector, expected, journal.signer));
+        verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes);
+    }
+
+    function _successJournal() internal view returns (TDXVerifierJournal memory journal) {
+        bytes memory publicKey = _publicKey();
+        bytes32 publicKeyHash;
+        assembly {
+            publicKeyHash := keccak256(add(publicKey, 0x21), 64)
+        }
+
+        journal = TDXVerifierJournal({
+            result: TDXVerificationResult.Success,
+            tcbStatus: TDXTcbStatus.UpToDate,
+            timestamp: uint64(block.timestamp - 1) * 1000,
+            collateralExpiration: uint64(block.timestamp + 1 days),
+            rootCaHash: ROOT_CA_HASH,
+            pckCertHash: keccak256("pck-cert"),
+            tcbInfoHash: keccak256("tcb-info"),
+            qeIdentityHash: keccak256("qe-identity"),
+            publicKey: publicKey,
+            signer: address(uint160(uint256(publicKeyHash))),
+            imageHash: IMAGE_HASH,
+            mrTdHash: MRTD_HASH,
+            reportDataPrefix: publicKeyHash,
+            reportDataSuffix: REPORT_DATA_SUFFIX
+        });
+    }
+
+    function _publicKey() internal pure returns (bytes memory publicKey) {
+        publicKey = new bytes(65);
+        publicKey[0] = 0x04;
+        for (uint256 i = 1; i < publicKey.length; i++) {
+            publicKey[i] = bytes1(uint8(i));
+        }
+    }
+
+    function _mockRiscZeroVerify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal {
+        vm.mockCall(
+            mockRiscZeroVerifier,
+            abi.encodeWithSelector(
+                bytes4(keccak256("verify(bytes,bytes32,bytes32)")), proofBytes, programId, sha256(output)
+            ),
+            ""
+        );
+    }
+
+    function _mockSP1Verify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal {
+        vm.mockCall(
+            mockSP1Verifier,
+            abi.encodeWithSelector(
+                bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")), programId, output, proofBytes
+            ),
+            ""
+        );
+    }
+}
diff --git a/test/multiproof/TEEProverRegistry.t.sol b/test/multiproof/TEEProverRegistry.t.sol
index b4a64a178..ebab8ebac 100644
--- a/test/multiproof/TEEProverRegistry.t.sol
+++ b/test/multiproof/TEEProverRegistry.t.sol
@@ -7,6 +7,7 @@ import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/trans
 import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
 
 import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
 import { GameType } from "src/dispute/lib/Types.sol";
 
@@ -75,8 +76,9 @@ contract TEEProverRegistryTest is Test {
         mockFactory.setImpl(TEST_GAME_TYPE, address(mockVerifier));
 
         // Deploy implementation (using DevTEEProverRegistry for test flexibility)
-        DevTEEProverRegistry impl =
-            new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(mockFactory)));
+        DevTEEProverRegistry impl = new DevTEEProverRegistry(
+            INitroEnclaveVerifier(address(0)), ITDXVerifier(address(1)), IDisputeGameFactory(address(mockFactory))
+        );
 
         // Deploy proxy admin
         proxyAdmin = new ProxyAdmin(address(this));
@@ -98,15 +100,16 @@ contract TEEProverRegistryTest is Test {
     function testInitialization() public view {
         assertEq(teeProverRegistry.owner(), owner);
         assertEq(teeProverRegistry.manager(), manager);
-        assertEq(teeProverRegistry.version(), "0.5.0");
+        assertEq(teeProverRegistry.version(), "0.7.0");
     }
 
     function testInitializationWithProposers() public {
         address proposer1 = makeAddr("proposer1");
         address proposer2 = makeAddr("proposer2");
         address proposer3 = makeAddr("proposer3");
-        DevTEEProverRegistry impl2 =
-            new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(1)));
+        DevTEEProverRegistry impl2 = new DevTEEProverRegistry(
+            INitroEnclaveVerifier(address(0)), ITDXVerifier(address(1)), IDisputeGameFactory(address(1))
+        );
         ProxyAdmin proxyAdmin2 = new ProxyAdmin(address(this));
         address[] memory proposers = new address[](3);
         proposers[0] = proposer1;
@@ -213,6 +216,35 @@ contract TEEProverRegistryTest is Test {
         teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH);
 
         assertTrue(teeProverRegistry.isValidSigner(signer));
+        assertEq(uint8(teeProverRegistry.signerTEEType(signer)), uint8(TEEProverRegistry.TEEType.NITRO));
+    }
+
+    function testAddDevTDXSigner() public {
+        address signer = makeAddr("dev-tdx-signer");
+
+        vm.prank(owner);
+        teeProverRegistry.addDevTDXSigner(signer, TEST_IMAGE_HASH);
+
+        assertTrue(teeProverRegistry.isValidSigner(signer));
+        assertEq(uint8(teeProverRegistry.signerTEEType(signer)), uint8(TEEProverRegistry.TEEType.TDX));
+    }
+
+    function testRegisteringSameSignerWithDifferentTEETypeFails() public {
+        address signer = makeAddr("dev-signer");
+
+        vm.prank(owner);
+        teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH);
+
+        vm.prank(owner);
+        vm.expectRevert(
+            abi.encodeWithSelector(
+                TEEProverRegistry.SignerTEETypeMismatch.selector,
+                signer,
+                TEEProverRegistry.TEEType.NITRO,
+                TEEProverRegistry.TEEType.TDX
+            )
+        );
+        teeProverRegistry.addDevTDXSigner(signer, TEST_IMAGE_HASH);
     }
 
     // ============ MAX_AGE Tests ============
diff --git a/test/multiproof/TEEProverRegistryTDX.t.sol b/test/multiproof/TEEProverRegistryTDX.t.sol
new file mode 100644
index 000000000..0df45c5b6
--- /dev/null
+++ b/test/multiproof/TEEProverRegistryTDX.t.sol
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.15;
+
+import { Test } from "forge-std/Test.sol";
+
+import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol";
+import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
+import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import {
+    ITDXVerifier,
+    TDXTcbStatus,
+    TDXVerificationResult,
+    TDXVerifierJournal
+} from "interfaces/multiproof/tee/ITDXVerifier.sol";
+import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { GameType } from "src/dispute/lib/Types.sol";
+
+import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol";
+
+/// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH.
+contract MockAggregateVerifierForTDXRegistry {
+    bytes32 public TEE_IMAGE_HASH;
+
+    constructor(bytes32 imageHash) {
+        TEE_IMAGE_HASH = imageHash;
+    }
+}
+
+/// @notice Mock DisputeGameFactory that returns a fixed game implementation.
+contract MockDisputeGameFactoryForTDXRegistry {
+    mapping(uint32 => address) internal _impls;
+
+    function setImpl(uint32 gameType, address impl) external {
+        _impls[gameType] = impl;
+    }
+
+    function gameImpls(GameType gameType) external view returns (IDisputeGame) {
+        return IDisputeGame(_impls[GameType.unwrap(gameType)]);
+    }
+}
+
+contract MockTDXVerifierForRegistry is ITDXVerifier {
+    TDXVerifierJournal internal _journal;
+
+    function setJournal(TDXVerifierJournal memory journal) external {
+        _journal = journal;
+    }
+
+    function verify(
+        bytes calldata,
+        ZkCoProcessorType,
+        bytes calldata
+    )
+        external
+        view
+        returns (TDXVerifierJournal memory)
+    {
+        return _journal;
+    }
+
+    function getZkConfig(ZkCoProcessorType) external pure returns (ZkCoProcessorConfig memory) {
+        return ZkCoProcessorConfig({ verifierId: bytes32(0), aggregatorId: bytes32(0), zkVerifier: address(0) });
+    }
+
+    function allowedTcbStatuses(TDXTcbStatus) external pure returns (bool) {
+        return true;
+    }
+
+    function setProofSubmitter(address) external { }
+}
+
+contract TEEProverRegistryTDXTest is Test {
+    bytes32 internal constant IMAGE_HASH = keccak256("tdx-image");
+    bytes32 internal constant REPORT_DATA_SUFFIX = keccak256("multiproof-tdx-poc");
+
+    function testRegisterTDXSignerStoresImageHash() public {
+        TDXVerifierJournal memory journal = _successJournal();
+        MockTDXVerifierForRegistry verifier = new MockTDXVerifierForRegistry();
+        verifier.setJournal(journal);
+
+        MockDisputeGameFactoryForTDXRegistry factory = new MockDisputeGameFactoryForTDXRegistry();
+        factory.setImpl(0, address(new MockAggregateVerifierForTDXRegistry(IMAGE_HASH)));
+
+        TEEProverRegistry registry = new TEEProverRegistry(
+            INitroEnclaveVerifier(address(0)), ITDXVerifier(address(verifier)), IDisputeGameFactory(address(factory))
+        );
+
+        vm.prank(address(0xdEaD));
+        registry.registerTDXSigner("", "");
+
+        assertTrue(registry.isRegisteredSigner(journal.signer));
+        assertEq(registry.signerImageHash(journal.signer), IMAGE_HASH);
+        assertEq(uint8(registry.signerTEEType(journal.signer)), uint8(TEEProverRegistry.TEEType.TDX));
+        assertTrue(registry.isValidSigner(journal.signer));
+    }
+
+    function testConstructorRevertsIfTDXVerifierNotSet() public {
+        MockDisputeGameFactoryForTDXRegistry factory = new MockDisputeGameFactoryForTDXRegistry();
+        factory.setImpl(0, address(new MockAggregateVerifierForTDXRegistry(IMAGE_HASH)));
+
+        vm.expectRevert(TEEProverRegistry.TDXVerifierNotSet.selector);
+        new TEEProverRegistry(
+            INitroEnclaveVerifier(address(0)), ITDXVerifier(address(0)), IDisputeGameFactory(address(factory))
+        );
+    }
+
+    function _successJournal() internal pure returns (TDXVerifierJournal memory journal) {
+        address signer = address(0x1234);
+
+        journal = TDXVerifierJournal({
+            result: TDXVerificationResult.Success,
+            tcbStatus: TDXTcbStatus.UpToDate,
+            timestamp: 0,
+            collateralExpiration: 0,
+            rootCaHash: bytes32(0),
+            pckCertHash: bytes32(0),
+            tcbInfoHash: bytes32(0),
+            qeIdentityHash: bytes32(0),
+            publicKey: "",
+            signer: signer,
+            imageHash: IMAGE_HASH,
+            mrTdHash: bytes32(0),
+            reportDataPrefix: bytes32(0),
+            reportDataSuffix: REPORT_DATA_SUFFIX
+        });
+    }
+}
diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol
index 89302645a..83b397f09 100644
--- a/test/multiproof/TEEVerifier.t.sol
+++ b/test/multiproof/TEEVerifier.t.sol
@@ -7,6 +7,7 @@ import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/trans
 import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
 
 import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol";
+import { ITDXVerifier } from "interfaces/multiproof/tee/ITDXVerifier.sol";
 import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
 import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol";
 import { GameType } from "src/dispute/lib/Types.sol";
@@ -45,9 +46,12 @@ contract TEEVerifierTest is Test {
     ProxyAdmin public proxyAdmin;
     MockAnchorStateRegistry public anchorStateRegistry;
 
-    // Test signer - we'll derive address from private key
-    uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
-    address internal signerAddress;
+    uint256 internal constant NITRO_SIGNER_PRIVATE_KEY =
+        0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
+    uint256 internal constant TDX_SIGNER_PRIVATE_KEY =
+        0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
+    address internal nitroSignerAddress;
+    address internal tdxSignerAddress;
 
     bytes32 internal constant IMAGE_ID = keccak256("test-image-id");
     uint32 internal constant TEST_GAME_TYPE = 621;
@@ -58,8 +62,8 @@ contract TEEVerifierTest is Test {
     function setUp() public {
         owner = address(this);
 
-        // Derive signer address from private key
-        signerAddress = vm.addr(SIGNER_PRIVATE_KEY);
+        nitroSignerAddress = vm.addr(NITRO_SIGNER_PRIVATE_KEY);
+        tdxSignerAddress = vm.addr(TDX_SIGNER_PRIVATE_KEY);
 
         // Deploy mock factory and verifier
         MockAggregateVerifierForVerifier mockVerifier = new MockAggregateVerifierForVerifier(IMAGE_ID);
@@ -67,8 +71,9 @@ contract TEEVerifierTest is Test {
         mockFactory.setImpl(TEST_GAME_TYPE, address(mockVerifier));
 
         // Deploy implementation (NitroEnclaveVerifier not needed for dev signer tests)
-        DevTEEProverRegistry impl =
-            new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(mockFactory)));
+        DevTEEProverRegistry impl = new DevTEEProverRegistry(
+            INitroEnclaveVerifier(address(0)), ITDXVerifier(address(1)), IDisputeGameFactory(address(mockFactory))
+        );
 
         // Deploy proxy admin
         proxyAdmin = new ProxyAdmin(address(this));
@@ -84,8 +89,9 @@ contract TEEVerifierTest is Test {
 
         teeProverRegistry = DevTEEProverRegistry(address(proxy));
 
-        // Register the signer with the correct image hash
-        teeProverRegistry.addDevSigner(signerAddress, IMAGE_ID);
+        // Register one Nitro signer and one TDX signer with the correct image hash.
+        teeProverRegistry.addDevSigner(nitroSignerAddress, IMAGE_ID);
+        teeProverRegistry.addDevTDXSigner(tdxSignerAddress, IMAGE_ID);
 
         // Set the proposer as valid
         teeProverRegistry.setProposer(PROPOSER, true);
@@ -97,45 +103,29 @@ contract TEEVerifierTest is Test {
         );
     }
 
-    function testVerifyValidSignature() public view {
-        // Create a journal hash
+    function testVerifyValidSignatures() public view {
         bytes32 journal = keccak256("test-journal");
-
-        // Sign the journal with the signer's private key
-        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal);
-        bytes memory signature = abi.encodePacked(r, s, v);
-
-        // Construct proof: proposer(20) + signature(65) = 85 bytes
-        bytes memory proofBytes = abi.encodePacked(PROPOSER, signature);
-
-        // Verify should return true regardless of imageId (enforced via journal hash, not registry)
+        bytes memory proofBytes = _proofBytes(journal);
         bool result = verifier.verify(proofBytes, IMAGE_ID, journal);
         assertTrue(result);
     }
 
     function testVerifyFailsWithInvalidSignature() public {
         bytes32 journal = keccak256("test-journal");
-
-        // Create an invalid signature (all zeros except v)
         bytes memory invalidSignature = new bytes(65);
         invalidSignature[64] = bytes1(uint8(27)); // Set v to 27
 
-        bytes memory proofBytes = abi.encodePacked(PROPOSER, invalidSignature);
+        bytes memory proofBytes = _buildProof(PROPOSER, invalidSignature, _signature(TDX_SIGNER_PRIVATE_KEY, journal));
 
         vm.expectRevert(TEEVerifier.InvalidSignature.selector);
         verifier.verify(proofBytes, IMAGE_ID, journal);
     }
 
     function testVerifyFailsWithInvalidProposer() public {
-        // Create a journal hash
         bytes32 journal = keccak256("test-journal");
-
-        // Sign the journal with the signer's private key
-        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal);
-        bytes memory signature = abi.encodePacked(r, s, v);
-
-        // Construct proof: proposer(20) + signature(65) = 85 bytes
-        bytes memory proofBytes = abi.encodePacked(address(0), signature);
+        bytes memory proofBytes = _buildProof(
+            address(0), _signature(NITRO_SIGNER_PRIVATE_KEY, journal), _signature(TDX_SIGNER_PRIVATE_KEY, journal)
+        );
 
         vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidProposer.selector, address(0)));
         verifier.verify(proofBytes, IMAGE_ID, journal);
@@ -148,10 +138,8 @@ contract TEEVerifierTest is Test {
 
         bytes32 journal = keccak256("test-journal");
 
-        (uint8 v, bytes32 r, bytes32 s) = vm.sign(unregisteredKey, journal);
-        bytes memory signature = abi.encodePacked(r, s, v);
-
-        bytes memory proofBytes = abi.encodePacked(PROPOSER, signature);
+        bytes memory proofBytes =
+            _buildProof(PROPOSER, _signature(unregisteredKey, journal), _signature(TDX_SIGNER_PRIVATE_KEY, journal));
 
         vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner));
         verifier.verify(proofBytes, IMAGE_ID, journal);
@@ -159,11 +147,7 @@ contract TEEVerifierTest is Test {
 
     function testVerifyFailsWithImageIdMismatch() public {
         bytes32 journal = keccak256("test-journal");
-
-        (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal);
-        bytes memory signature = abi.encodePacked(r, s, v);
-
-        bytes memory proofBytes = abi.encodePacked(PROPOSER, signature);
+        bytes memory proofBytes = _proofBytes(journal);
 
         // Different imageId should fail — signer was registered with IMAGE_ID
         bytes32 wrongImageId = keccak256("different-image");
@@ -171,10 +155,47 @@ contract TEEVerifierTest is Test {
         verifier.verify(proofBytes, wrongImageId, journal);
     }
 
+    function testVerifyFailsWithDuplicateSigner() public {
+        bytes32 journal = keccak256("test-journal");
+        bytes memory nitroSignature = _signature(NITRO_SIGNER_PRIVATE_KEY, journal);
+        bytes memory proofBytes = _buildProof(PROPOSER, nitroSignature, nitroSignature);
+
+        vm.expectRevert(abi.encodeWithSelector(TEEVerifier.DuplicateSigner.selector, nitroSignerAddress));
+        verifier.verify(proofBytes, IMAGE_ID, journal);
+    }
+
+    function testVerifyFailsWithoutNitroSignature() public {
+        bytes32 journal = keccak256("test-journal");
+        uint256 secondTDXKey = 0x3234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
+
+        vm.prank(owner);
+        teeProverRegistry.addDevTDXSigner(vm.addr(secondTDXKey), IMAGE_ID);
+
+        bytes memory proofBytes =
+            _buildProof(PROPOSER, _signature(TDX_SIGNER_PRIVATE_KEY, journal), _signature(secondTDXKey, journal));
+
+        vm.expectRevert(TEEVerifier.MissingNitroSignature.selector);
+        verifier.verify(proofBytes, IMAGE_ID, journal);
+    }
+
+    function testVerifyFailsWithoutTDXSignature() public {
+        bytes32 journal = keccak256("test-journal");
+        uint256 secondNitroKey = 0x4234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
+
+        vm.prank(owner);
+        teeProverRegistry.addDevSigner(vm.addr(secondNitroKey), IMAGE_ID);
+
+        bytes memory proofBytes =
+            _buildProof(PROPOSER, _signature(NITRO_SIGNER_PRIVATE_KEY, journal), _signature(secondNitroKey, journal));
+
+        vm.expectRevert(TEEVerifier.MissingTDXSignature.selector);
+        verifier.verify(proofBytes, IMAGE_ID, journal);
+    }
+
     function testVerifyFailsWithInvalidProofFormat() public {
         bytes32 journal = keccak256("test-journal");
 
-        // Proof too short (less than 85 bytes)
+        // Proof too short (less than proposer + two signatures).
         bytes memory shortProof = new bytes(50);
 
         vm.expectRevert(TEEVerifier.InvalidProofFormat.selector);
@@ -184,4 +205,27 @@ contract TEEVerifierTest is Test {
     function testConstants() public view {
         assertEq(address(verifier.TEE_PROVER_REGISTRY()), address(teeProverRegistry));
     }
+
+    function _proofBytes(bytes32 journal) internal view returns (bytes memory) {
+        return _buildProof(
+            PROPOSER, _signature(NITRO_SIGNER_PRIVATE_KEY, journal), _signature(TDX_SIGNER_PRIVATE_KEY, journal)
+        );
+    }
+
+    function _signature(uint256 privateKey, bytes32 journal) internal pure returns (bytes memory) {
+        (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, journal);
+        return abi.encodePacked(r, s, v);
+    }
+
+    function _buildProof(
+        address proposer,
+        bytes memory firstSignature,
+        bytes memory secondSignature
+    )
+        internal
+        pure
+        returns (bytes memory)
+    {
+        return abi.encodePacked(proposer, firstSignature, secondSignature);
+    }
 }
diff --git a/test/opcm/DeployImplementations.t.sol b/test/opcm/DeployImplementations.t.sol
index 00fd553f5..c1fc8de93 100644
--- a/test/opcm/DeployImplementations.t.sol
+++ b/test/opcm/DeployImplementations.t.sol
@@ -176,6 +176,7 @@ contract DeployImplementations_Test is Test, FeatureFlags {
             bytes32(0), // multiproofConfigHash
             621, // multiproofGameType
             address(0), // nitroEnclaveVerifier
+            address(1), // tdxVerifier
             8453, // l2ChainID
             100, // multiproofBlockInterval
             10, // multiproofIntermediateBlockInterval
@@ -312,6 +313,11 @@ contract DeployImplementations_Test is Test, FeatureFlags {
         vm.expectRevert("DeployImplementations: mipsVersion not set");
         deployImplementations.run(input);
 
+        input = defaultInput();
+        input.tdxVerifier = address(0);
+        vm.expectRevert("DeployImplementations: tdxVerifier not set");
+        deployImplementations.run(input);
+
         input = defaultInput();
         input.superchainConfigProxy = ISuperchainConfig(address(0));
         vm.expectRevert("DeployImplementations: superchainConfigProxy not set");
@@ -399,6 +405,7 @@ contract DeployImplementations_Test is Test, FeatureFlags {
             bytes32(0), // multiproofConfigHash
             621, // multiproofGameType
             address(0), // nitroEnclaveVerifier
+            address(1), // tdxVerifier
             8453, // l2ChainID
             100, // multiproofBlockInterval
             10, // multiproofIntermediateBlockInterval
diff --git a/test/opcm/DeployOPChain.t.sol b/test/opcm/DeployOPChain.t.sol
index 42f4353f9..9a6a75592 100644
--- a/test/opcm/DeployOPChain.t.sol
+++ b/test/opcm/DeployOPChain.t.sol
@@ -89,6 +89,7 @@ contract DeployOPChain_TestBase is Test, FeatureFlags {
                 multiproofConfigHash: bytes32(0),
                 multiproofGameType: 621,
                 nitroEnclaveVerifier: address(0),
+                tdxVerifier: address(1),
                 l2ChainID: 8453,
                 multiproofBlockInterval: 100,
                 multiproofIntermediateBlockInterval: 10,
