From 70b7dc7c7777a9b5b8b7e8b6362015c394a5456b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Tue, 28 Apr 2026 21:14:01 -0400 Subject: [PATCH 01/20] feat: add TDX verifier and TDX-aware TEE prover registry Adds production-shape Intel TDX DCAP signer-registration path for multiproof. The heavy attestation work happens off-chain in a ZK guest (RISC Zero or SP1); Solidity verifies the proof and enforces on-chain policy (trusted Intel root, TCB status, collateral expiry, quote freshness, signer derivation, REPORTDATA public-key binding) before registering the signer via TDXTEEProverRegistry. --- interfaces/multiproof/tee/ITDXVerifier.sol | 89 ++ scripts/multiproof/README.md | 61 +- snapshots/abi/TDXTEEProverRegistry.json | 556 ++++++++++++ snapshots/abi/TDXVerifier.json | 808 ++++++++++++++++++ snapshots/semver-lock.json | 10 +- .../storageLayout/TDXTEEProverRegistry.json | 79 ++ snapshots/storageLayout/TDXVerifier.json | 37 + src/multiproof/tee/TDXTEEProverRegistry.sol | 66 ++ src/multiproof/tee/TDXVerifier.sol | 263 ++++++ test/multiproof/TDXTEEProverRegistry.t.sol | 114 +++ test/multiproof/TDXVerifier.t.sol | 277 ++++++ 11 files changed, 2349 insertions(+), 11 deletions(-) create mode 100644 interfaces/multiproof/tee/ITDXVerifier.sol create mode 100644 snapshots/abi/TDXTEEProverRegistry.json create mode 100644 snapshots/abi/TDXVerifier.json create mode 100644 snapshots/storageLayout/TDXTEEProverRegistry.json create mode 100644 snapshots/storageLayout/TDXVerifier.json create mode 100644 src/multiproof/tee/TDXTEEProverRegistry.sol create mode 100644 src/multiproof/tee/TDXVerifier.sol create mode 100644 test/multiproof/TDXTEEProverRegistry.t.sol create mode 100644 test/multiproof/TDXVerifier.t.sol diff --git a/interfaces/multiproof/tee/ITDXVerifier.sol b/interfaces/multiproof/tee/ITDXVerifier.sol new file mode 100644 index 000000000..d0eb7f3ad --- /dev/null +++ b/interfaces/multiproof/tee/ITDXVerifier.sol @@ -0,0 +1,89 @@ +// 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); +} diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 01155f444..de93d01ed 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 @@ -124,6 +124,47 @@ 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. | +| `TDXTEEProverRegistry` | Extends `TEEProverRegistry` with `registerTDXSigner(bytes output, ZkCoProcessorType zkCoprocessor, bytes proofBytes)`, storing the signer address and TDX image hash in the same registry fields used by `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. TDX only changes signer registration: once a TDX signer is registered, proposal proofs use the existing `proposer + signature` proof bytes and the same `signerImageHash` check as Nitro. + +> **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. + +--- + ## 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 +225,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/snapshots/abi/TDXTEEProverRegistry.json b/snapshots/abi/TDXTEEProverRegistry.json new file mode 100644 index 000000000..51e55f31a --- /dev/null +++ b/snapshots/abi/TDXTEEProverRegistry.json @@ -0,0 +1,556 @@ +[ + { + "inputs": [ + { + "internalType": "contract INitroEnclaveVerifier", + "name": "nitroVerifier", + "type": "address" + }, + { + "internalType": "contract ITDXVerifier", + "name": "tdxVerifier", + "type": "address" + }, + { + "internalType": "contract IDisputeGameFactory", + "name": "factory", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DISPUTE_GAME_FACTORY", + "outputs": [ + { + "internalType": "contract IDisputeGameFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_AGE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NITRO_VERIFIER", + "outputs": [ + { + "internalType": "contract INitroEnclaveVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TDX_VERIFIER", + "outputs": [ + { + "internalType": "contract ITDXVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "deregisterSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "gameType", + "outputs": [ + { + "internalType": "GameType", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getExpectedImageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRegisteredSigners", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "initialManager", + "type": "address" + }, + { + "internalType": "address[]", + "name": "initialProposers", + "type": "address[]" + }, + { + "internalType": "GameType", + "name": "gameType_", + "type": "uint32" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isRegisteredSigner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isValidProposer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "isValidSigner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "manager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "registerSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "enum ZkCoProcessorType", + "name": "zkCoprocessor", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "registerTDXSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceManagement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "GameType", + "name": "gameType_", + "type": "uint32" + } + ], + "name": "setGameType", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "setProposer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "signerImageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "transferManagement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + } + ], + "name": "GameTypeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "ManagementTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "ProposerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "SignerDeregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "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", + "type": "error" + }, + { + "inputs": [], + "name": "AttestationVerificationFailed", + "type": "error" + }, + { + "inputs": [], + "name": "DisputeGameFactoryNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "ImageHashReadFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGameType", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPublicKey", + "type": "error" + }, + { + "inputs": [], + "name": "PCR0NotFound", + "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/semver-lock.json b/snapshots/semver-lock.json index c7ce7ce69..caec4886c 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -239,6 +239,14 @@ "initCodeHash": "0x100364f9b0c63a61538386ba91e73dcffba22d3f6dfe8efdbbf5ff347b6fce47", "sourceCodeHash": "0xed5d92ee1f32f2fde5e89ba278f3142c8113c774f6ce7bd831781a8abe7bddf3" }, + "src/multiproof/tee/TDXTEEProverRegistry.sol:TDXTEEProverRegistry": { + "initCodeHash": "0x266f220204f2c62f341b700bdb1e9bbda8c6489ced1e14d17b4ffe7dd33b1d8b", + "sourceCodeHash": "0x90bf694159e3f47e8f42aeaaa856947bb493acd5f64bf14fe277718dc62b4463" + }, + "src/multiproof/tee/TDXVerifier.sol:TDXVerifier": { + "initCodeHash": "0x0f511b2f67bd82ff976935ca0a178c3fa9d9cf9b89c6e67b3fd7f1006f5e5147", + "sourceCodeHash": "0x7cdf2abefaefa3126960103aa810699973c1c47147114f8a92e63c427b9b1656" + }, "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": { "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0", "sourceCodeHash": "0xf1ec1f02f540da659a204b26acf986fdce7d7d63bba67a87923f52453fb92ccb" @@ -299,4 +307,4 @@ "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } -} +} \ No newline at end of file diff --git a/snapshots/storageLayout/TDXTEEProverRegistry.json b/snapshots/storageLayout/TDXTEEProverRegistry.json new file mode 100644 index 000000000..93dcbfff3 --- /dev/null +++ b/snapshots/storageLayout/TDXTEEProverRegistry.json @@ -0,0 +1,79 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "address" + }, + { + "bytes": "20", + "label": "_manager", + "offset": 0, + "slot": "52", + "type": "address" + }, + { + "bytes": "1536", + "label": "__gap", + "offset": 0, + "slot": "53", + "type": "uint256[48]" + }, + { + "bytes": "4", + "label": "gameType", + "offset": 0, + "slot": "101", + "type": "GameType" + }, + { + "bytes": "32", + "label": "isRegisteredSigner", + "offset": 0, + "slot": "102", + "type": "mapping(address => bool)" + }, + { + "bytes": "32", + "label": "signerImageHash", + "offset": 0, + "slot": "103", + "type": "mapping(address => bytes32)" + }, + { + "bytes": "32", + "label": "isValidProposer", + "offset": 0, + "slot": "104", + "type": "mapping(address => bool)" + }, + { + "bytes": "32", + "label": "_registeredSigners", + "offset": 0, + "slot": "105", + "type": "struct EnumerableSetLib.AddressSet" + } +] \ No newline at end of file diff --git a/snapshots/storageLayout/TDXVerifier.json b/snapshots/storageLayout/TDXVerifier.json new file mode 100644 index 000000000..1a05d86c7 --- /dev/null +++ b/snapshots/storageLayout/TDXVerifier.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "8", + "label": "maxTimeDiff", + "offset": 0, + "slot": "0", + "type": "uint64" + }, + { + "bytes": "32", + "label": "rootCaHash", + "offset": 0, + "slot": "1", + "type": "bytes32" + }, + { + "bytes": "20", + "label": "proofSubmitter", + "offset": 0, + "slot": "2", + "type": "address" + }, + { + "bytes": "32", + "label": "zkConfig", + "offset": 0, + "slot": "3", + "type": "mapping(enum ZkCoProcessorType => struct ZkCoProcessorConfig)" + }, + { + "bytes": "32", + "label": "allowedTcbStatuses", + "offset": 0, + "slot": "4", + "type": "mapping(enum TDXTcbStatus => bool)" + } +] \ No newline at end of file diff --git a/src/multiproof/tee/TDXTEEProverRegistry.sol b/src/multiproof/tee/TDXTEEProverRegistry.sol new file mode 100644 index 000000000..a4b05b63c --- /dev/null +++ b/src/multiproof/tee/TDXTEEProverRegistry.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol"; + +import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; +import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { ITDXVerifier, TDXVerifierJournal, ZkCoProcessorType } from "interfaces/multiproof/tee/ITDXVerifier.sol"; + +import { TEEProverRegistry } from "./TEEProverRegistry.sol"; + +/// @title TDXTEEProverRegistry +/// @notice TEE prover registry extension that can register signers from Intel TDX quotes. +/// @dev Nitro registration remains available through TEEProverRegistry.registerSigner(). This contract adds a +/// separate TDX path so existing Nitro deployments and verifier integrations do not need to change. +contract TDXTEEProverRegistry is TEEProverRegistry { + using EnumerableSetLib for EnumerableSetLib.AddressSet; + + /// @notice The TDX verifier used to validate ZK-proven TD quote verification journals. + ITDXVerifier public immutable TDX_VERIFIER; + + /// @notice Emitted when a TDX signer is registered. + event TDXSignerRegistered(address indexed signer, bytes32 indexed imageHash, bytes32 reportDataSuffix); + + /// @notice Thrown when the TDX verifier is not configured. + error TDXVerifierNotSet(); + + constructor( + INitroEnclaveVerifier nitroVerifier, + ITDXVerifier tdxVerifier, + IDisputeGameFactory factory + ) + TEEProverRegistry(nitroVerifier, factory) + { + if (address(tdxVerifier) == address(0)) revert TDXVerifierNotSet(); + TDX_VERIFIER = tdxVerifier; + } + + /// @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 zkCoprocessor ZK proving system used to generate the proof. + /// @param proofBytes ZK proof bytes. + function registerTDXSigner( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + onlyOwnerOrManager + { + TDXVerifierJournal memory journal = TDX_VERIFIER.verify(output, zkCoprocessor, proofBytes); + + isRegisteredSigner[journal.signer] = true; + signerImageHash[journal.signer] = journal.imageHash; + _registeredSigners.add(journal.signer); + + emit SignerRegistered(journal.signer); + emit TDXSignerRegistered(journal.signer, journal.imageHash, journal.reportDataSuffix); + } + + /// @notice Semantic version. + /// @custom:semver 0.1.0 + function version() public pure override returns (string memory) { + return "0.1.0"; + } +} diff --git a/src/multiproof/tee/TDXVerifier.sol b/src/multiproof/tee/TDXVerifier.sol new file mode 100644 index 000000000..14ed8ea9c --- /dev/null +++ b/src/multiproof/tee/TDXVerifier.sol @@ -0,0 +1,263 @@ +// 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 Hash of the trusted Intel root CA used by the ZK verifier guest. + bytes32 public rootCaHash; + + /// @notice Address authorized to submit TDX proofs, expected to be the TDX-aware registry. + address public proofSubmitter; + + /// @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); + if (journal.rootCaHash != rootCaHash) revert RootCaHashMismatch(rootCaHash, 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(); + 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/test/multiproof/TDXTEEProverRegistry.t.sol b/test/multiproof/TDXTEEProverRegistry.t.sol new file mode 100644 index 000000000..6e40c87a9 --- /dev/null +++ b/test/multiproof/TDXTEEProverRegistry.t.sol @@ -0,0 +1,114 @@ +// 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 { TDXTEEProverRegistry } from "src/multiproof/tee/TDXTEEProverRegistry.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; + } +} + +contract TDXTEEProverRegistryTest 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))); + + TDXTEEProverRegistry registry = new TDXTEEProverRegistry( + INitroEnclaveVerifier(address(0)), ITDXVerifier(address(verifier)), IDisputeGameFactory(address(factory)) + ); + + vm.prank(address(0xdEaD)); + registry.registerTDXSigner("", ZkCoProcessorType.Succinct, ""); + + assertTrue(registry.isRegisteredSigner(journal.signer)); + assertEq(registry.signerImageHash(journal.signer), IMAGE_HASH); + assertTrue(registry.isValidSigner(journal.signer)); + } + + 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/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 + ), + "" + ); + } +} From 65bc3628c3c189e393be93b40a012ff42e269365 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 08:14:43 -0400 Subject: [PATCH 02/20] remove zkCoprocessor input --- src/multiproof/tee/TDXTEEProverRegistry.sol | 12 ++---------- test/multiproof/TDXTEEProverRegistry.t.sol | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/multiproof/tee/TDXTEEProverRegistry.sol b/src/multiproof/tee/TDXTEEProverRegistry.sol index a4b05b63c..5efff43b5 100644 --- a/src/multiproof/tee/TDXTEEProverRegistry.sol +++ b/src/multiproof/tee/TDXTEEProverRegistry.sol @@ -38,17 +38,9 @@ contract TDXTEEProverRegistry is TEEProverRegistry { /// @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 zkCoprocessor ZK proving system used to generate the proof. /// @param proofBytes ZK proof bytes. - function registerTDXSigner( - bytes calldata output, - ZkCoProcessorType zkCoprocessor, - bytes calldata proofBytes - ) - external - onlyOwnerOrManager - { - TDXVerifierJournal memory journal = TDX_VERIFIER.verify(output, zkCoprocessor, proofBytes); + function registerTDXSigner(bytes calldata output, bytes calldata proofBytes) external onlyOwnerOrManager { + TDXVerifierJournal memory journal = TDX_VERIFIER.verify(output, ZkCoProcessorType.RiscZero, proofBytes); isRegisteredSigner[journal.signer] = true; signerImageHash[journal.signer] = journal.imageHash; diff --git a/test/multiproof/TDXTEEProverRegistry.t.sol b/test/multiproof/TDXTEEProverRegistry.t.sol index 6e40c87a9..1158c04e2 100644 --- a/test/multiproof/TDXTEEProverRegistry.t.sol +++ b/test/multiproof/TDXTEEProverRegistry.t.sol @@ -84,7 +84,7 @@ contract TDXTEEProverRegistryTest is Test { ); vm.prank(address(0xdEaD)); - registry.registerTDXSigner("", ZkCoProcessorType.Succinct, ""); + registry.registerTDXSigner("", ""); assertTrue(registry.isRegisteredSigner(journal.signer)); assertEq(registry.signerImageHash(journal.signer), IMAGE_HASH); From 1220336db8ec0e75331923b67615a54c9f311eef Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 13:31:50 -0400 Subject: [PATCH 03/20] test deployment and fix tests --- deploy-config/hardhat.json | 1 + deploy-config/sepolia.json | 7 +- scripts/deploy/Deploy.s.sol | 1 + scripts/deploy/DeployConfig.s.sol | 2 + scripts/deploy/DeployImplementations.s.sol | 7 +- scripts/multiproof/DeployDevNoNitro.s.sol | 13 +- scripts/multiproof/DeployDevWithNitro.s.sol | 11 +- scripts/multiproof/DeployDevWithTDX.s.sol | 217 +++++++ scripts/multiproof/DeployTDXVerifier.s.sol | 94 +++ scripts/multiproof/README.md | 78 ++- scripts/multiproof/justfile | 84 +++ snapshots/abi/DevTEEProverRegistry.json | 66 +++ snapshots/abi/TDXTEEProverRegistry.json | 556 ------------------ snapshots/abi/TEEProverRegistry.json | 66 +++ snapshots/semver-lock.json | 8 +- .../storageLayout/TDXTEEProverRegistry.json | 79 --- .../mocks/MockDevTEEProverRegistry.sol | 12 +- src/multiproof/tee/TDXTEEProverRegistry.sol | 58 -- src/multiproof/tee/TEEProverRegistry.sol | 49 +- test/multiproof/TEEProverRegistry.t.sol | 13 +- ...istry.t.sol => TEEProverRegistryTDX.t.sol} | 16 +- test/multiproof/TEEVerifier.t.sol | 6 +- test/opcm/DeployImplementations.t.sol | 7 + test/opcm/DeployOPChain.t.sol | 1 + 24 files changed, 718 insertions(+), 734 deletions(-) create mode 100644 scripts/multiproof/DeployDevWithTDX.s.sol create mode 100644 scripts/multiproof/DeployTDXVerifier.s.sol create mode 100644 scripts/multiproof/justfile delete mode 100644 snapshots/abi/TDXTEEProverRegistry.json delete mode 100644 snapshots/storageLayout/TDXTEEProverRegistry.json delete mode 100644 src/multiproof/tee/TDXTEEProverRegistry.sol rename test/multiproof/{TDXTEEProverRegistry.t.sol => TEEProverRegistryTDX.t.sol} (85%) diff --git a/deploy-config/hardhat.json b/deploy-config/hardhat.json index dd0bd375e..1c6ac22b4 100644 --- a/deploy-config/hardhat.json +++ b/deploy-config/hardhat.json @@ -79,6 +79,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 2e898f91f..c3f0767ee 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, @@ -63,7 +63,7 @@ "gasPayingTokenSymbol": "", "nativeAssetLiquidityAmount": null, "liquidityControllerOwner": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", - "teeImageHash": "0xd04c8147410930fade36986c9357fd9a7591231e38e6e87e51183bd5909f509c", + "teeImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360", "multiproofGameType": 621, "teeProposer": "0xdb84125f2f4229c81c579f41bc129c71b174eb58", @@ -71,10 +71,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/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index 3dabdee77..4ab3bf10e 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -243,6 +243,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 2b5822ddd..b7524192d 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -99,6 +99,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; @@ -216,6 +217,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 35c224091..2a304bed7 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -46,6 +46,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 { @@ -66,6 +67,7 @@ contract DeployImplementations is Script { bytes32 multiproofConfigHash; uint256 multiproofGameType; address nitroEnclaveVerifier; + address tdxVerifier; uint256 l2ChainID; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; @@ -632,7 +634,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; @@ -697,6 +701,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..92385240f 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -72,6 +72,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 +90,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 +101,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 +113,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 +133,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 +206,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); @@ -219,6 +229,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..92ad2b17a 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:"); @@ -216,6 +224,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..d7ecf6f0c --- /dev/null +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -0,0 +1,217 @@ +// 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"; + +interface IProofSubmitterSetter { + function setProofSubmitter(address newProofSubmitter) external; +} + +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 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 { + require(tdxVerifier != address(0), "tdxVerifier must be non-zero"); + 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("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(address(0)), + 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); + + IProofSubmitterSetter(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(" 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 TDX signer with a ZK-proven TDX journal <<<"); + console.log("\n cast send", teeProverRegistryProxy); + console.log(' "registerTDXSigner(bytes,bytes)" '); + console.log(" --private-key --rpc-url "); + console.log("\n========================================\n"); + } + + function _writeOutput() internal { + string memory key = "deployment"; + 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..0b1ff6fcd --- /dev/null +++ b/scripts/multiproof/DeployTDXVerifier.s.sol @@ -0,0 +1,94 @@ +// 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)" \ + * \ + * --rpc-url --broadcast --private-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 public constant TDX_MAX_TIME_DIFF = 3600; + + address public tdxVerifier; + + /// @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(""); + console.log("NOTE: proofSubmitter is set to owner as a temporary placeholder."); + console.log(" DeployDevWithTDX.s.sol updates it to TEEProverRegistry."); + console.log(""); + + vm.startBroadcast(); + + 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(risc0VerifierRouter, tdxVerifierId, intelRootCaHash); + } + + function _writeOutput(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 de93d01ed..9e1fe9565 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -132,7 +132,7 @@ and Solidity verifies the proof plus the on-chain acceptance policy before regis | 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. | -| `TDXTEEProverRegistry` | Extends `TEEProverRegistry` with `registerTDXSigner(bytes output, ZkCoProcessorType zkCoprocessor, bytes proofBytes)`, storing the signer address and TDX image hash in the same registry fields used by `TEEVerifier`. | +| `TEEProverRegistry` | Registers Nitro signers through `registerSigner(bytes,bytes)` and TDX signers through `registerTDXSigner(bytes,bytes)`, storing both in the same registry fields used by `TEEVerifier`. | The ZK verifier guest is expected to perform the full Intel DCAP verification path: @@ -163,6 +163,82 @@ The quote's TDREPORT `REPORTDATA` must put `keccak256(x || y)` in the first 32 b > **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/-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. `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)`. + +```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/-dev-with-tdx.json`. + +### Step 3: Register a TDX signer + +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) 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=' + @echo 'just deploy-tdx-verifier' + @echo 'just deploy-tdx-stack [tdx-manager]' + @echo 'just tdx-sepolia-config' diff --git a/snapshots/abi/DevTEEProverRegistry.json b/snapshots/abi/DevTEEProverRegistry.json index 6d66717e0..ff1329861 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": [ { @@ -253,6 +271,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", @@ -465,6 +501,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", @@ -499,5 +560,10 @@ "inputs": [], "name": "PCR0NotFound", "type": "error" + }, + { + "inputs": [], + "name": "TDXVerifierNotSet", + "type": "error" } ] \ No newline at end of file diff --git a/snapshots/abi/TDXTEEProverRegistry.json b/snapshots/abi/TDXTEEProverRegistry.json deleted file mode 100644 index 51e55f31a..000000000 --- a/snapshots/abi/TDXTEEProverRegistry.json +++ /dev/null @@ -1,556 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract INitroEnclaveVerifier", - "name": "nitroVerifier", - "type": "address" - }, - { - "internalType": "contract ITDXVerifier", - "name": "tdxVerifier", - "type": "address" - }, - { - "internalType": "contract IDisputeGameFactory", - "name": "factory", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "DISPUTE_GAME_FACTORY", - "outputs": [ - { - "internalType": "contract IDisputeGameFactory", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MAX_AGE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "NITRO_VERIFIER", - "outputs": [ - { - "internalType": "contract INitroEnclaveVerifier", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "TDX_VERIFIER", - "outputs": [ - { - "internalType": "contract ITDXVerifier", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "signer", - "type": "address" - } - ], - "name": "deregisterSigner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "gameType", - "outputs": [ - { - "internalType": "GameType", - "name": "", - "type": "uint32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getExpectedImageHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getRegisteredSigners", - "outputs": [ - { - "internalType": "address[]", - "name": "", - "type": "address[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "initialOwner", - "type": "address" - }, - { - "internalType": "address", - "name": "initialManager", - "type": "address" - }, - { - "internalType": "address[]", - "name": "initialProposers", - "type": "address[]" - }, - { - "internalType": "GameType", - "name": "gameType_", - "type": "uint32" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isRegisteredSigner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isValidProposer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "signer", - "type": "address" - } - ], - "name": "isValidSigner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "manager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "output", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "proofBytes", - "type": "bytes" - } - ], - "name": "registerSigner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "output", - "type": "bytes" - }, - { - "internalType": "enum ZkCoProcessorType", - "name": "zkCoprocessor", - "type": "uint8" - }, - { - "internalType": "bytes", - "name": "proofBytes", - "type": "bytes" - } - ], - "name": "registerTDXSigner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceManagement", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "GameType", - "name": "gameType_", - "type": "uint32" - } - ], - "name": "setGameType", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "proposer", - "type": "address" - }, - { - "internalType": "bool", - "name": "isValid", - "type": "bool" - } - ], - "name": "setProposer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "signerImageHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newManager", - "type": "address" - } - ], - "name": "transferManagement", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "GameType", - "name": "gameType", - "type": "uint32" - } - ], - "name": "GameTypeUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousManager", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newManager", - "type": "address" - } - ], - "name": "ManagementTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "proposer", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "isValid", - "type": "bool" - } - ], - "name": "ProposerSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "signer", - "type": "address" - } - ], - "name": "SignerDeregistered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "signer", - "type": "address" - } - ], - "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", - "type": "error" - }, - { - "inputs": [], - "name": "AttestationVerificationFailed", - "type": "error" - }, - { - "inputs": [], - "name": "DisputeGameFactoryNotSet", - "type": "error" - }, - { - "inputs": [], - "name": "ImageHashReadFailed", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidGameType", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidPublicKey", - "type": "error" - }, - { - "inputs": [], - "name": "PCR0NotFound", - "type": "error" - }, - { - "inputs": [], - "name": "TDXVerifierNotSet", - "type": "error" - } -] \ No newline at end of file diff --git a/snapshots/abi/TEEProverRegistry.json b/snapshots/abi/TEEProverRegistry.json index 510962c5d..9bec65dec 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", @@ -447,6 +483,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", @@ -481,5 +542,10 @@ "inputs": [], "name": "PCR0NotFound", "type": "error" + }, + { + "inputs": [], + "name": "TDXVerifierNotSet", + "type": "error" } ] \ No newline at end of file diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 77715d027..c29210d10 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -151,17 +151,13 @@ "initCodeHash": "0x100364f9b0c63a61538386ba91e73dcffba22d3f6dfe8efdbbf5ff347b6fce47", "sourceCodeHash": "0xbedb66738a0ab18600b2ed21de51d552a210c4cc71b2c971440e325791dc3ce7" }, - "src/multiproof/tee/TDXTEEProverRegistry.sol:TDXTEEProverRegistry": { - "initCodeHash": "0x266f220204f2c62f341b700bdb1e9bbda8c6489ced1e14d17b4ffe7dd33b1d8b", - "sourceCodeHash": "0x90bf694159e3f47e8f42aeaaa856947bb493acd5f64bf14fe277718dc62b4463" - }, "src/multiproof/tee/TDXVerifier.sol:TDXVerifier": { "initCodeHash": "0x0f511b2f67bd82ff976935ca0a178c3fa9d9cf9b89c6e67b3fd7f1006f5e5147", "sourceCodeHash": "0x7cdf2abefaefa3126960103aa810699973c1c47147114f8a92e63c427b9b1656" }, "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": { - "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0", - "sourceCodeHash": "0xf1ec1f02f540da659a204b26acf986fdce7d7d63bba67a87923f52453fb92ccb" + "initCodeHash": "0xf050a9503675e4351de148eb311d3446f04d59bd2083710617e045dd8b64bb26", + "sourceCodeHash": "0x64e7860f2b0d3b07aff5e1da82928a88ea4da94bf2fcb93a342f0f02844ece73" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af", diff --git a/snapshots/storageLayout/TDXTEEProverRegistry.json b/snapshots/storageLayout/TDXTEEProverRegistry.json deleted file mode 100644 index 93dcbfff3..000000000 --- a/snapshots/storageLayout/TDXTEEProverRegistry.json +++ /dev/null @@ -1,79 +0,0 @@ -[ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, - { - "bytes": "1600", - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "uint256[50]" - }, - { - "bytes": "20", - "label": "_owner", - "offset": 0, - "slot": "51", - "type": "address" - }, - { - "bytes": "20", - "label": "_manager", - "offset": 0, - "slot": "52", - "type": "address" - }, - { - "bytes": "1536", - "label": "__gap", - "offset": 0, - "slot": "53", - "type": "uint256[48]" - }, - { - "bytes": "4", - "label": "gameType", - "offset": 0, - "slot": "101", - "type": "GameType" - }, - { - "bytes": "32", - "label": "isRegisteredSigner", - "offset": 0, - "slot": "102", - "type": "mapping(address => bool)" - }, - { - "bytes": "32", - "label": "signerImageHash", - "offset": 0, - "slot": "103", - "type": "mapping(address => bytes32)" - }, - { - "bytes": "32", - "label": "isValidProposer", - "offset": 0, - "slot": "104", - "type": "mapping(address => bool)" - }, - { - "bytes": "32", - "label": "_registeredSigners", - "offset": 0, - "slot": "105", - "type": "struct EnumerableSetLib.AddressSet" - } -] \ No newline at end of file diff --git a/src/multiproof/mocks/MockDevTEEProverRegistry.sol b/src/multiproof/mocks/MockDevTEEProverRegistry.sol index c01b124f1..02cf538a5 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,13 +12,12 @@ 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). @@ -28,9 +27,6 @@ contract DevTEEProverRegistry is TEEProverRegistry { /// @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); } } diff --git a/src/multiproof/tee/TDXTEEProverRegistry.sol b/src/multiproof/tee/TDXTEEProverRegistry.sol deleted file mode 100644 index 5efff43b5..000000000 --- a/src/multiproof/tee/TDXTEEProverRegistry.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol"; - -import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { INitroEnclaveVerifier } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; -import { ITDXVerifier, TDXVerifierJournal, ZkCoProcessorType } from "interfaces/multiproof/tee/ITDXVerifier.sol"; - -import { TEEProverRegistry } from "./TEEProverRegistry.sol"; - -/// @title TDXTEEProverRegistry -/// @notice TEE prover registry extension that can register signers from Intel TDX quotes. -/// @dev Nitro registration remains available through TEEProverRegistry.registerSigner(). This contract adds a -/// separate TDX path so existing Nitro deployments and verifier integrations do not need to change. -contract TDXTEEProverRegistry is TEEProverRegistry { - using EnumerableSetLib for EnumerableSetLib.AddressSet; - - /// @notice The TDX verifier used to validate ZK-proven TD quote verification journals. - ITDXVerifier public immutable TDX_VERIFIER; - - /// @notice Emitted when a TDX signer is registered. - event TDXSignerRegistered(address indexed signer, bytes32 indexed imageHash, bytes32 reportDataSuffix); - - /// @notice Thrown when the TDX verifier is not configured. - error TDXVerifierNotSet(); - - constructor( - INitroEnclaveVerifier nitroVerifier, - ITDXVerifier tdxVerifier, - IDisputeGameFactory factory - ) - TEEProverRegistry(nitroVerifier, factory) - { - if (address(tdxVerifier) == address(0)) revert TDXVerifierNotSet(); - TDX_VERIFIER = tdxVerifier; - } - - /// @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); - - isRegisteredSigner[journal.signer] = true; - signerImageHash[journal.signer] = journal.imageHash; - _registeredSigners.add(journal.signer); - - emit SignerRegistered(journal.signer); - emit TDXSignerRegistered(journal.signer, journal.imageHash, journal.reportDataSuffix); - } - - /// @notice Semantic version. - /// @custom:semver 0.1.0 - function version() public pure override returns (string memory) { - return "0.1.0"; - } -} diff --git a/src/multiproof/tee/TEEProverRegistry.sol b/src/multiproof/tee/TEEProverRegistry.sol index ec6652b44..fbce8f4bd 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,9 +17,10 @@ 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 @@ -36,6 +38,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; @@ -65,6 +70,9 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @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 +103,14 @@ 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(); + + 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), @@ -167,10 +180,18 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { } address enclaveAddress = address(uint160(uint256(publicKeyHash))); - isRegisteredSigner[enclaveAddress] = true; - signerImageHash[enclaveAddress] = pcr0Hash; - _registeredSigners.add(enclaveAddress); - emit SignerRegistered(enclaveAddress); + _registerSigner(enclaveAddress, pcr0Hash); + } + + /// @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); + + emit TDXSignerRegistered(journal.signer, journal.imageHash, journal.reportDataSuffix); } /// @notice Deregisters a signer. @@ -233,9 +254,17 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { } /// @notice Semantic version. - /// @custom:semver 0.5.0 + /// @custom:semver 0.6.0 function version() public pure virtual returns (string memory) { - return "0.5.0"; + return "0.6.0"; + } + + /// @dev Registers a signer and stores the image hash enforced by TEEVerifier at proof-submission time. + function _registerSigner(address signer, bytes32 imageHash) internal { + isRegisteredSigner[signer] = true; + signerImageHash[signer] = imageHash; + _registeredSigners.add(signer); + emit SignerRegistered(signer); } /// @dev Reads TEE_IMAGE_HASH from the AggregateVerifier registered in the factory. diff --git a/test/multiproof/TEEProverRegistry.t.sol b/test/multiproof/TEEProverRegistry.t.sol index b4a64a178..ecaa2c06b 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.6.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; diff --git a/test/multiproof/TDXTEEProverRegistry.t.sol b/test/multiproof/TEEProverRegistryTDX.t.sol similarity index 85% rename from test/multiproof/TDXTEEProverRegistry.t.sol rename to test/multiproof/TEEProverRegistryTDX.t.sol index 1158c04e2..43bd99f7f 100644 --- a/test/multiproof/TDXTEEProverRegistry.t.sol +++ b/test/multiproof/TEEProverRegistryTDX.t.sol @@ -15,7 +15,7 @@ import { import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; import { GameType } from "src/dispute/lib/Types.sol"; -import { TDXTEEProverRegistry } from "src/multiproof/tee/TDXTEEProverRegistry.sol"; +import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol"; /// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. contract MockAggregateVerifierForTDXRegistry { @@ -67,7 +67,7 @@ contract MockTDXVerifierForRegistry is ITDXVerifier { } } -contract TDXTEEProverRegistryTest is Test { +contract TEEProverRegistryTDXTest is Test { bytes32 internal constant IMAGE_HASH = keccak256("tdx-image"); bytes32 internal constant REPORT_DATA_SUFFIX = keccak256("multiproof-tdx-poc"); @@ -79,7 +79,7 @@ contract TDXTEEProverRegistryTest is Test { MockDisputeGameFactoryForTDXRegistry factory = new MockDisputeGameFactoryForTDXRegistry(); factory.setImpl(0, address(new MockAggregateVerifierForTDXRegistry(IMAGE_HASH))); - TDXTEEProverRegistry registry = new TDXTEEProverRegistry( + TEEProverRegistry registry = new TEEProverRegistry( INitroEnclaveVerifier(address(0)), ITDXVerifier(address(verifier)), IDisputeGameFactory(address(factory)) ); @@ -91,6 +91,16 @@ contract TDXTEEProverRegistryTest is Test { 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); diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 89302645a..19faa27f1 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"; @@ -67,8 +68,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)); diff --git a/test/opcm/DeployImplementations.t.sol b/test/opcm/DeployImplementations.t.sol index cb2f103d2..18b6326f9 100644 --- a/test/opcm/DeployImplementations.t.sol +++ b/test/opcm/DeployImplementations.t.sol @@ -177,6 +177,7 @@ contract DeployImplementations_Test is Test, FeatureFlags { bytes32(0), // multiproofConfigHash 621, // multiproofGameType address(0), // nitroEnclaveVerifier + address(1), // tdxVerifier 8453, // l2ChainID 100, // multiproofBlockInterval 10, // multiproofIntermediateBlockInterval @@ -313,6 +314,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"); @@ -400,6 +406,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 2da623316..9c1c7166d 100644 --- a/test/opcm/DeployOPChain.t.sol +++ b/test/opcm/DeployOPChain.t.sol @@ -90,6 +90,7 @@ contract DeployOPChain_TestBase is Test, FeatureFlags { multiproofConfigHash: bytes32(0), multiproofGameType: 621, nitroEnclaveVerifier: address(0), + tdxVerifier: address(1), l2ChainID: 8453, multiproofBlockInterval: 100, multiproofIntermediateBlockInterval: 10, From 55718a96fc540816a0e92d05e52343b941fd3107 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 13:55:33 -0400 Subject: [PATCH 04/20] update AggregateVerifier --- scripts/multiproof/DeployDevNoNitro.s.sol | 20 ++- scripts/multiproof/DeployDevWithNitro.s.sol | 7 +- scripts/multiproof/DeployDevWithTDX.s.sol | 13 +- scripts/multiproof/README.md | 38 ++++-- snapshots/abi/DevTEEProverRegistry.json | 63 +++++++++ snapshots/abi/TEEProverRegistry.json | 45 +++++++ snapshots/abi/TEEVerifier.json | 21 +++ snapshots/semver-lock.json | 20 ++- .../storageLayout/DevTEEProverRegistry.json | 7 + .../storageLayout/TEEProverRegistry.json | 7 + src/multiproof/AggregateVerifier.sol | 11 +- .../mocks/MockDevTEEProverRegistry.sol | 12 +- src/multiproof/tee/TEEProverRegistry.sol | 35 ++++- src/multiproof/tee/TEEVerifier.sol | 78 +++++++++--- test/multiproof/AggregateVerifier.t.sol | 8 +- test/multiproof/BaseTest.t.sol | 29 ++++- test/multiproof/Challenge.t.sol | 6 +- test/multiproof/Nullify.t.sol | 6 +- test/multiproof/TEEProverRegistry.t.sol | 31 ++++- test/multiproof/TEEProverRegistryTDX.t.sol | 1 + test/multiproof/TEEVerifier.t.sol | 120 ++++++++++++------ 21 files changed, 471 insertions(+), 107 deletions(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 92385240f..50b383af1 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -26,11 +26,15 @@ 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 \ - * --private-key $OWNER_KEY --rpc-url $RPC_URL + * cast send $TEE_PROVER_REGISTRY \ + * "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. * @@ -218,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)" '); + console.log(' "addDevSigner(address,bytes32)" '); + console.log(" ", vm.toString(cfg.teeImageHash())); + console.log(" --private-key --rpc-url "); + console.log("\ncast send", teeProverRegistryProxy); + console.log(' "addDevTDXSigner(address,bytes32)" '); console.log(" ", vm.toString(cfg.teeImageHash())); console.log(" --private-key --rpc-url "); console.log("\n========================================\n"); diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 92ad2b17a..5a3cae27f 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -212,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)" '); + console.log(' "registerSigner(bytes,bytes)" '); + console.log(" --private-key --rpc-url "); + console.log("\n cast send", teeProverRegistryProxy); + console.log(' "registerTDXSigner(bytes,bytes)" '); console.log(" --private-key --rpc-url "); console.log("\n========================================\n"); } diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index d7ecf6f0c..592bee867 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -49,6 +49,7 @@ contract DeployDevWithTDX is Script { 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; @@ -61,6 +62,7 @@ contract DeployDevWithTDX is Script { function setUp() public { DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); cfg.read(Config.deployConfigPath()); + nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier(); } function run(address tdxVerifier) public { @@ -69,6 +71,7 @@ contract DeployDevWithTDX is Script { function run(address tdxVerifier, address registrationManager) public { 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; @@ -81,6 +84,7 @@ contract DeployDevWithTDX is Script { 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(""); @@ -102,7 +106,7 @@ contract DeployDevWithTDX is Script { address owner = cfg.finalSystemOwner(); address registryImpl = address( new TEEProverRegistry( - INitroEnclaveVerifier(address(0)), + INitroEnclaveVerifier(nitroEnclaveVerifierAddr), ITDXVerifier(tdxVerifierAddr), IDisputeGameFactory(disputeGameFactory) ) @@ -178,6 +182,7 @@ contract DeployDevWithTDX is Script { 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); @@ -192,7 +197,10 @@ contract DeployDevWithTDX 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 TDX signer with a ZK-proven TDX journal <<<"); + console.log("\n>>> NEXT STEP: Register one Nitro signer and one TDX signer <<<"); + console.log("\n cast send", teeProverRegistryProxy); + console.log(' "registerSigner(bytes,bytes)" '); + console.log(" --private-key --rpc-url "); console.log("\n cast send", teeProverRegistryProxy); console.log(' "registerTDXSigner(bytes,bytes)" '); console.log(" --private-key --rpc-url "); @@ -201,6 +209,7 @@ contract DeployDevWithTDX is Script { 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); diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 9e1fe9565..826664341 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -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" ``` @@ -132,7 +141,7 @@ and Solidity verifies the proof plus the on-chain acceptance policy before regis | 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)`, storing both in the same registry fields used by `TEEVerifier`. | +| `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: @@ -159,7 +168,7 @@ The attested public key must be supplied as an uncompressed 65-byte secp256k1 pu 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. TDX only changes signer registration: once a TDX signer is registered, proposal proofs use the existing `proposer + signature` proof bytes and the same `signerImageHash` check as Nitro. +`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. @@ -204,9 +213,9 @@ The script saves output to `deployments/-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. `finalSystemOwner` in the deploy config must be the account broadcasting this transaction because the script updates `TDXVerifier.proofSubmitter` to the deployed `TEEProverRegistry`. +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)`. +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 @@ -226,7 +235,18 @@ forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \ The script saves output to `deployments/-dev-with-tdx.json`. -### Step 3: Register a TDX signer +### 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: diff --git a/snapshots/abi/DevTEEProverRegistry.json b/snapshots/abi/DevTEEProverRegistry.json index ff1329861..0448f242a 100644 --- a/snapshots/abi/DevTEEProverRegistry.json +++ b/snapshots/abi/DevTEEProverRegistry.json @@ -90,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": [ { @@ -353,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": [ { @@ -556,11 +593,37 @@ "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", diff --git a/snapshots/abi/TEEProverRegistry.json b/snapshots/abi/TEEProverRegistry.json index 9bec65dec..ceff44a08 100644 --- a/snapshots/abi/TEEProverRegistry.json +++ b/snapshots/abi/TEEProverRegistry.json @@ -335,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": [ { @@ -538,11 +557,37 @@ "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", 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 c29210d10..4e75dca07 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -144,8 +144,12 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x10bb6a60f21de103d1da9ff310a38f571c53a8113d04a029087648debf3f0341", - "sourceCodeHash": "0xcd4c508223b9b461ec230bde03c1630fe68cd00b82ee84c39b93d9f3c0d71d37" + "initCodeHash": "0xea8f199813975e1307fa964b70e30832cf5ea1e848ffa78368cc565cc9e919d1", + "sourceCodeHash": "0x9a48d272dd19ccd6df51999dcb6117308aea712f6d3731bdcc8b8d02b867c348" + }, + "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { + "initCodeHash": "0xa92e0ce6d4dddea92719bad6f1e964c3abf838ec892a12fc16c4a5b9fbfb9af1", + "sourceCodeHash": "0x9a48d272dd19ccd6df51999dcb6117308aea712f6d3731bdcc8b8d02b867c348" }, "src/multiproof/tee/NitroEnclaveVerifier.sol:NitroEnclaveVerifier": { "initCodeHash": "0x100364f9b0c63a61538386ba91e73dcffba22d3f6dfe8efdbbf5ff347b6fce47", @@ -156,12 +160,16 @@ "sourceCodeHash": "0x7cdf2abefaefa3126960103aa810699973c1c47147114f8a92e63c427b9b1656" }, "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": { - "initCodeHash": "0xf050a9503675e4351de148eb311d3446f04d59bd2083710617e045dd8b64bb26", - "sourceCodeHash": "0x64e7860f2b0d3b07aff5e1da82928a88ea4da94bf2fcb93a342f0f02844ece73" + "initCodeHash": "0xf96d42f3dc474f4f8d29af0e7fbb59b13035a61bc53903ddd14814ae0b3f2f73", + "sourceCodeHash": "0x12bf06d225633d1983746ea06e72469388a3db63fd7d8a10144b4267f13f0b5d" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { - "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af", - "sourceCodeHash": "0xd5e01b4f4e69313e56e51f2f46b7bbe699ef8dc24b8a6385b8ebac4162e05353" + "initCodeHash": "0x37883165bb1924631d830772a811f172059f529fd44ff34694d2511ff52c29c9", + "sourceCodeHash": "0x549df133a04af10305dcc5e70818ac49ac47546c79c957151184f9a21223d6c1" + }, + "src/multiproof/tee/TEEVerifier.sol:TEEVerifier:dispute": { + "initCodeHash": "0x37883165bb1924631d830772a811f172059f529fd44ff34694d2511ff52c29c9", + "sourceCodeHash": "0x549df133a04af10305dcc5e70818ac49ac47546c79c957151184f9a21223d6c1" }, "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/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 02cf538a5..e21c04b7c 100644 --- a/src/multiproof/mocks/MockDevTEEProverRegistry.sol +++ b/src/multiproof/mocks/MockDevTEEProverRegistry.sol @@ -20,13 +20,21 @@ contract DevTEEProverRegistry is TEEProverRegistry { 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 { - _registerSigner(signer, imageHash); + _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/TEEProverRegistry.sol b/src/multiproof/tee/TEEProverRegistry.sol index fbce8f4bd..1079ba7df 100644 --- a/src/multiproof/tee/TEEProverRegistry.sol +++ b/src/multiproof/tee/TEEProverRegistry.sol @@ -24,9 +24,15 @@ import { GameType } from "src/dispute/lib/Types.sol"; /// 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; @@ -67,6 +73,9 @@ 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); @@ -106,6 +115,12 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @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(); @@ -180,7 +195,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { } address enclaveAddress = address(uint160(uint256(publicKeyHash))); - _registerSigner(enclaveAddress, pcr0Hash); + _registerSigner(enclaveAddress, pcr0Hash, TEEType.NITRO); } /// @notice Registers a signer using a ZK proof of Intel TDX DCAP quote verification. @@ -189,7 +204,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { 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); + _registerSigner(journal.signer, journal.imageHash, TEEType.TDX); emit TDXSignerRegistered(journal.signer, journal.imageHash, journal.reportDataSuffix); } @@ -199,6 +214,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); } @@ -254,15 +270,22 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { } /// @notice Semantic version. - /// @custom:semver 0.6.0 + /// @custom:semver 0.7.0 function version() public pure virtual returns (string memory) { - return "0.6.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) internal { + 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); } diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 73878af80..3a13b9758 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,23 +81,49 @@ 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]; + bytes calldata firstSignature = proofBytes[20:85]; + bytes calldata secondSignature = proofBytes[85:150]; - // 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); + address firstSigner = _recoverSigner(journal, firstSignature); + address secondSigner = _recoverSigner(journal, secondSignature); - if (err != ECDSA.RecoverError.NoError) { - revert InvalidSignature(); - } + if (firstSigner == secondSigner) revert DuplicateSigner(firstSigner); if (!TEE_PROVER_REGISTRY.isValidProposer(proposer)) { revert InvalidProposer(proposer); } + TEEProverRegistry.TEEType firstTEEType = _validateSigner(firstSigner, imageId); + TEEProverRegistry.TEEType secondTEEType = _validateSigner(secondSigner, imageId); + + if (firstTEEType != TEEProverRegistry.TEEType.NITRO && secondTEEType != TEEProverRegistry.TEEType.NITRO) { + revert MissingNitroSignature(); + } + if (firstTEEType != TEEProverRegistry.TEEType.TDX && secondTEEType != TEEProverRegistry.TEEType.TDX) { + revert MissingTDXSignature(); + } + + 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) + { // Check that the signer is registered if (!TEE_PROVER_REGISTRY.isRegisteredSigner(signer)) { revert InvalidSigner(signer); @@ -95,12 +137,12 @@ contract TEEVerifier is Verifier, ISemver { revert ImageIdMismatch(registeredImageHash, imageId); } - return true; + teeType = TEE_PROVER_REGISTRY.signerTEEType(signer); } /// @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/TEEProverRegistry.t.sol b/test/multiproof/TEEProverRegistry.t.sol index ecaa2c06b..ebab8ebac 100644 --- a/test/multiproof/TEEProverRegistry.t.sol +++ b/test/multiproof/TEEProverRegistry.t.sol @@ -100,7 +100,7 @@ contract TEEProverRegistryTest is Test { function testInitialization() public view { assertEq(teeProverRegistry.owner(), owner); assertEq(teeProverRegistry.manager(), manager); - assertEq(teeProverRegistry.version(), "0.6.0"); + assertEq(teeProverRegistry.version(), "0.7.0"); } function testInitializationWithProposers() public { @@ -216,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 index 43bd99f7f..725daf990 100644 --- a/test/multiproof/TEEProverRegistryTDX.t.sol +++ b/test/multiproof/TEEProverRegistryTDX.t.sol @@ -88,6 +88,7 @@ contract TEEProverRegistryTDXTest is Test { 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)); } diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 19faa27f1..83b397f09 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -46,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; @@ -59,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); @@ -86,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); @@ -99,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); @@ -150,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); @@ -161,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"); @@ -173,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); @@ -186,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); + } } From 0cd4fc87c63c1ef4f0a9c95825231c6d81f1f09b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 14:08:47 -0400 Subject: [PATCH 05/20] fmt --- scripts/multiproof/DeployDevNoNitro.s.sol | 16 ++++++++-------- snapshots/semver-lock.json | 12 ++---------- src/multiproof/tee/TEEVerifier.sol | 5 +---- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 50b383af1..d142b952c 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -26,15 +26,15 @@ pragma solidity 0.8.15; * SIGNER REGISTRATION (SIMPLIFIED) * ───────────────────────────────────────────────────────────────────────────────── * - * After deployment, register one dev Nitro signer and one dev TDX signer: + * After deployment, register one dev Nitro signer and one dev TDX signer: * - * cast send $TEE_PROVER_REGISTRY \ - * "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 + * cast send $TEE_PROVER_REGISTRY \ + * "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. * diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 4e75dca07..26635aedc 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -144,11 +144,7 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xea8f199813975e1307fa964b70e30832cf5ea1e848ffa78368cc565cc9e919d1", - "sourceCodeHash": "0x9a48d272dd19ccd6df51999dcb6117308aea712f6d3731bdcc8b8d02b867c348" - }, - "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0xa92e0ce6d4dddea92719bad6f1e964c3abf838ec892a12fc16c4a5b9fbfb9af1", + "initCodeHash": "0x9a77922f9f05c738e1a0884a1803a19302cd046e122af9ff38b8c3dc43e89409", "sourceCodeHash": "0x9a48d272dd19ccd6df51999dcb6117308aea712f6d3731bdcc8b8d02b867c348" }, "src/multiproof/tee/NitroEnclaveVerifier.sol:NitroEnclaveVerifier": { @@ -165,11 +161,7 @@ }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { "initCodeHash": "0x37883165bb1924631d830772a811f172059f529fd44ff34694d2511ff52c29c9", - "sourceCodeHash": "0x549df133a04af10305dcc5e70818ac49ac47546c79c957151184f9a21223d6c1" - }, - "src/multiproof/tee/TEEVerifier.sol:TEEVerifier:dispute": { - "initCodeHash": "0x37883165bb1924631d830772a811f172059f529fd44ff34694d2511ff52c29c9", - "sourceCodeHash": "0x549df133a04af10305dcc5e70818ac49ac47546c79c957151184f9a21223d6c1" + "sourceCodeHash": "0x506aba49b3b5b29e565a151c828b8f24662ed5794418bdaa2be149b085b05a7e" }, "src/multiproof/zk/ZKVerifier.sol:ZkVerifier": { "initCodeHash": "0xcdc5bd5fa8ffad76358b8f34a53b8c0cb36540176c31f82d52546373d0bd5245", diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 3a13b9758..0fb0e50b5 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -116,10 +116,7 @@ contract TEEVerifier is Verifier, ISemver { if (err != ECDSA.RecoverError.NoError) revert InvalidSignature(); } - function _validateSigner( - address signer, - bytes32 imageId - ) + function _validateSigner(address signer, bytes32 imageId) internal view returns (TEEProverRegistry.TEEType teeType) From c1a2a0aed34ac65e592ecc6c7d14ce790c4af06c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 14:30:23 -0400 Subject: [PATCH 06/20] clean DeployDevWithTDX script --- interfaces/multiproof/tee/ITDXVerifier.sol | 3 +++ scripts/multiproof/DeployDevWithTDX.s.sol | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interfaces/multiproof/tee/ITDXVerifier.sol b/interfaces/multiproof/tee/ITDXVerifier.sol index d0eb7f3ad..24fc2a6ed 100644 --- a/interfaces/multiproof/tee/ITDXVerifier.sol +++ b/interfaces/multiproof/tee/ITDXVerifier.sol @@ -86,4 +86,7 @@ interface ITDXVerifier { /// @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/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index 592bee867..bc9776b49 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -38,10 +38,6 @@ import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol"; import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; -interface IProofSubmitterSetter { - function setProofSubmitter(address newProofSubmitter) external; -} - contract DeployDevWithTDX is Script { uint256 public constant INIT_BOND = 0.00001 ether; address public constant DEFAULT_TDX_REGISTRATION_MANAGER = 0x93900CB7eCdB5994352b19DfD8a900Cd4fa437B7; @@ -62,7 +58,6 @@ contract DeployDevWithTDX is Script { function setUp() public { DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); cfg.read(Config.deployConfigPath()); - nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier(); } function run(address tdxVerifier) public { @@ -70,6 +65,7 @@ contract DeployDevWithTDX is Script { } 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"); @@ -124,7 +120,7 @@ contract DeployDevWithTDX is Script { registryProxy.changeAdmin(address(0xdead)); teeProverRegistryProxy = address(registryProxy); - IProofSubmitterSetter(tdxVerifierAddr).setProofSubmitter(teeProverRegistryProxy); + ITDXVerifier(tdxVerifierAddr).setProofSubmitter(teeProverRegistryProxy); teeVerifier = address(new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), mockAnchorRegistry)); } From cd1af349bb54e53035bbe613a59fb27d9e3dc1c2 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 14:34:44 -0400 Subject: [PATCH 07/20] clean DeployTDXVerifier script --- scripts/multiproof/DeployTDXVerifier.s.sol | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/scripts/multiproof/DeployTDXVerifier.s.sol b/scripts/multiproof/DeployTDXVerifier.s.sol index 0b1ff6fcd..7deadf4a0 100644 --- a/scripts/multiproof/DeployTDXVerifier.s.sol +++ b/scripts/multiproof/DeployTDXVerifier.s.sol @@ -29,9 +29,7 @@ import { TDXVerifier } from "src/multiproof/tee/TDXVerifier.sol"; contract DeployTDXVerifier is Script { /// @notice Maximum TDX quote age accepted by TDXVerifier. - uint64 public constant TDX_MAX_TIME_DIFF = 3600; - - address public tdxVerifier; + uint64 internal constant TDX_MAX_TIME_DIFF = 3600; /// @param owner Owner for TDXVerifier. /// @param risc0VerifierRouter Existing RISC Zero verifier router. @@ -58,13 +56,10 @@ contract DeployTDXVerifier is Script { console.log("Intel Root CA Hash:", vm.toString(intelRootCaHash)); console.log("Max Time Diff:", TDX_MAX_TIME_DIFF); console.log(""); - console.log("NOTE: proofSubmitter is set to owner as a temporary placeholder."); - console.log(" DeployDevWithTDX.s.sol updates it to TEEProverRegistry."); - console.log(""); vm.startBroadcast(); - tdxVerifier = address( + address tdxVerifier = address( new TDXVerifier( owner, TDX_MAX_TIME_DIFF, intelRootCaHash, owner, ZkCoProcessorType.RiscZero, zkConfig, allowedStatuses ) @@ -76,10 +71,10 @@ contract DeployTDXVerifier is Script { console.log(""); console.log(">>> Use this address as the DeployDevWithTDX.s.sol argument <<<"); - _writeOutput(risc0VerifierRouter, tdxVerifierId, intelRootCaHash); + _writeOutput(tdxVerifier, risc0VerifierRouter, tdxVerifierId, intelRootCaHash); } - function _writeOutput(address risc0VerifierRouter, bytes32 tdxVerifierId, bytes32 intelRootCaHash) internal { + 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); From 74d593aa133a5739270ef97007637b0759583822 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 14:41:40 -0400 Subject: [PATCH 08/20] clean TDXVerifier --- src/multiproof/tee/TDXVerifier.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/multiproof/tee/TDXVerifier.sol b/src/multiproof/tee/TDXVerifier.sol index 14ed8ea9c..bd3ec4cd8 100644 --- a/src/multiproof/tee/TDXVerifier.sol +++ b/src/multiproof/tee/TDXVerifier.sol @@ -27,12 +27,12 @@ contract TDXVerifier is Ownable, ITDXVerifier, ISemver { /// @notice Maximum accepted age of a TDX quote, in seconds. uint64 public maxTimeDiff; - /// @notice Hash of the trusted Intel root CA used by the ZK verifier guest. - bytes32 public rootCaHash; - /// @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; @@ -189,7 +189,8 @@ contract TDXVerifier is Ownable, ITDXVerifier, ISemver { function _verifyJournal(TDXVerifierJournal memory journal) internal view { if (journal.result != TDXVerificationResult.Success) revert TDXVerificationFailed(journal.result); - if (journal.rootCaHash != rootCaHash) revert RootCaHashMismatch(rootCaHash, journal.rootCaHash); + 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); @@ -228,6 +229,7 @@ contract TDXVerifier is Ownable, ITDXVerifier, ISemver { 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) } From 4766367d3fe07a61b6eb43046e9f49e5eaf10b29 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 14:48:02 -0400 Subject: [PATCH 09/20] clean TEEProverRegistry --- src/multiproof/tee/TEEProverRegistry.sol | 27 ++++++++-------------- test/multiproof/TEEProverRegistryTDX.t.sol | 2 ++ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/multiproof/tee/TEEProverRegistry.sol b/src/multiproof/tee/TEEProverRegistry.sol index 1079ba7df..81b255c8d 100644 --- a/src/multiproof/tee/TEEProverRegistry.sol +++ b/src/multiproof/tee/TEEProverRegistry.sol @@ -147,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_); } @@ -179,18 +173,14 @@ 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))); @@ -226,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. @@ -240,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. @@ -290,9 +281,9 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { 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/test/multiproof/TEEProverRegistryTDX.t.sol b/test/multiproof/TEEProverRegistryTDX.t.sol index 725daf990..0df45c5b6 100644 --- a/test/multiproof/TEEProverRegistryTDX.t.sol +++ b/test/multiproof/TEEProverRegistryTDX.t.sol @@ -65,6 +65,8 @@ contract MockTDXVerifierForRegistry is ITDXVerifier { function allowedTcbStatuses(TDXTcbStatus) external pure returns (bool) { return true; } + + function setProofSubmitter(address) external { } } contract TEEProverRegistryTDXTest is Test { From f120667eb1cdc981f74005d31fd0b76cd7292c4a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 14:52:21 -0400 Subject: [PATCH 10/20] clean TEEVerifier --- src/multiproof/tee/TEEVerifier.sol | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/multiproof/tee/TEEVerifier.sol b/src/multiproof/tee/TEEVerifier.sol index 0fb0e50b5..6fa772ee6 100644 --- a/src/multiproof/tee/TEEVerifier.sol +++ b/src/multiproof/tee/TEEVerifier.sol @@ -84,21 +84,21 @@ contract TEEVerifier is Verifier, ISemver { if (proofBytes.length != TEE_PROOF_SIZE) revert InvalidProofFormat(); address proposer = address(bytes20(proofBytes[0:20])); - bytes calldata firstSignature = proofBytes[20:85]; - bytes calldata secondSignature = proofBytes[85:150]; - - address firstSigner = _recoverSigner(journal, firstSignature); - address secondSigner = _recoverSigner(journal, secondSignature); - - if (firstSigner == secondSigner) revert DuplicateSigner(firstSigner); - if (!TEE_PROVER_REGISTRY.isValidProposer(proposer)) { revert InvalidProposer(proposer); } + 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(); } @@ -121,20 +121,17 @@ contract TEEVerifier is Verifier, ISemver { view returns (TEEProverRegistry.TEEType teeType) { - // Check that the signer is registered - if (!TEE_PROVER_REGISTRY.isRegisteredSigner(signer)) { - revert InvalidSigner(signer); - } + // 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); - // 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 + // 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); } - - teeType = TEE_PROVER_REGISTRY.signerTEEType(signer); } /// @notice Semantic version. From 042548548d2254b30fe9cc657b7186ecb0e14256 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 3 May 2026 15:12:43 -0400 Subject: [PATCH 11/20] clean TDXVerifier --- scripts/multiproof/DeployTDXVerifier.s.sol | 9 ++++++++- snapshots/semver-lock.json | 12 ++++++------ snapshots/storageLayout/TDXVerifier.json | 18 +++++++++--------- src/multiproof/tee/TDXVerifier.sol | 4 +++- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/scripts/multiproof/DeployTDXVerifier.s.sol b/scripts/multiproof/DeployTDXVerifier.s.sol index 7deadf4a0..e5f955771 100644 --- a/scripts/multiproof/DeployTDXVerifier.s.sol +++ b/scripts/multiproof/DeployTDXVerifier.s.sol @@ -74,7 +74,14 @@ contract DeployTDXVerifier is Script { _writeOutput(tdxVerifier, risc0VerifierRouter, tdxVerifierId, intelRootCaHash); } - function _writeOutput(address tdxVerifier, address risc0VerifierRouter, bytes32 tdxVerifierId, bytes32 intelRootCaHash) internal { + 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); diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 26635aedc..c1a707102 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -152,16 +152,16 @@ "sourceCodeHash": "0xbedb66738a0ab18600b2ed21de51d552a210c4cc71b2c971440e325791dc3ce7" }, "src/multiproof/tee/TDXVerifier.sol:TDXVerifier": { - "initCodeHash": "0x0f511b2f67bd82ff976935ca0a178c3fa9d9cf9b89c6e67b3fd7f1006f5e5147", - "sourceCodeHash": "0x7cdf2abefaefa3126960103aa810699973c1c47147114f8a92e63c427b9b1656" + "initCodeHash": "0xdfd5a00dc139565ec7102fd5ac05d91f923bfe1ef724e16a4cdfbcb60cd19c3c", + "sourceCodeHash": "0xb28e2632fc3591ecdb2d96cbc6b99a08854048a513f444d8282511293a0739e2" }, "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": { - "initCodeHash": "0xf96d42f3dc474f4f8d29af0e7fbb59b13035a61bc53903ddd14814ae0b3f2f73", - "sourceCodeHash": "0x12bf06d225633d1983746ea06e72469388a3db63fd7d8a10144b4267f13f0b5d" + "initCodeHash": "0x5304faf5062088ee90dbd08eedbdacea39bdf48ffca689228e63d8f71f952bfb", + "sourceCodeHash": "0x37ed4874679b18cdf447a4d1890ecfeff648b0d762d8302827cd5e823b0af3ce" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { - "initCodeHash": "0x37883165bb1924631d830772a811f172059f529fd44ff34694d2511ff52c29c9", - "sourceCodeHash": "0x506aba49b3b5b29e565a151c828b8f24662ed5794418bdaa2be149b085b05a7e" + "initCodeHash": "0x6af4a7e162b2589076869a920a1f5b8b458d7f0c555068fdd2b82d44a51f3b0f", + "sourceCodeHash": "0xb0453b1fee0d1f2d1f53e22cbac4c43dd9e33ab33ac373ec54f81d433aeabbd5" }, "src/multiproof/zk/ZKVerifier.sol:ZkVerifier": { "initCodeHash": "0xcdc5bd5fa8ffad76358b8f34a53b8c0cb36540176c31f82d52546373d0bd5245", diff --git a/snapshots/storageLayout/TDXVerifier.json b/snapshots/storageLayout/TDXVerifier.json index 1a05d86c7..2f13d341b 100644 --- a/snapshots/storageLayout/TDXVerifier.json +++ b/snapshots/storageLayout/TDXVerifier.json @@ -6,6 +6,13 @@ "slot": "0", "type": "uint64" }, + { + "bytes": "20", + "label": "proofSubmitter", + "offset": 8, + "slot": "0", + "type": "address" + }, { "bytes": "32", "label": "rootCaHash", @@ -13,25 +20,18 @@ "slot": "1", "type": "bytes32" }, - { - "bytes": "20", - "label": "proofSubmitter", - "offset": 0, - "slot": "2", - "type": "address" - }, { "bytes": "32", "label": "zkConfig", "offset": 0, - "slot": "3", + "slot": "2", "type": "mapping(enum ZkCoProcessorType => struct ZkCoProcessorConfig)" }, { "bytes": "32", "label": "allowedTcbStatuses", "offset": 0, - "slot": "4", + "slot": "3", "type": "mapping(enum TDXTcbStatus => bool)" } ] \ No newline at end of file diff --git a/src/multiproof/tee/TDXVerifier.sol b/src/multiproof/tee/TDXVerifier.sol index bd3ec4cd8..d8175830a 100644 --- a/src/multiproof/tee/TDXVerifier.sol +++ b/src/multiproof/tee/TDXVerifier.sol @@ -190,7 +190,9 @@ contract TDXVerifier is Ownable, ITDXVerifier, ISemver { 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 (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); From 85bd21a8741a92897b0363560b3144284cdf3be7 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Tue, 5 May 2026 18:55:32 -0400 Subject: [PATCH 12/20] fix imports --- interfaces/L1/proofs/tee/ITDXVerifier.sol | 2 +- scripts/multiproof/DeployDevWithTDX.s.sol | 26 +++++++++++----------- scripts/multiproof/DeployTDXVerifier.s.sol | 6 ++--- src/L1/proofs/tee/TDXVerifier.sol | 2 +- test/L1/proofs/TDXVerifier.t.sol | 6 ++--- test/L1/proofs/TEEProverRegistryTDX.t.sol | 14 ++++++------ 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/interfaces/L1/proofs/tee/ITDXVerifier.sol b/interfaces/L1/proofs/tee/ITDXVerifier.sol index 24fc2a6ed..de7ae0f05 100644 --- a/interfaces/L1/proofs/tee/ITDXVerifier.sol +++ b/interfaces/L1/proofs/tee/ITDXVerifier.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ZkCoProcessorType, ZkCoProcessorConfig } from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; +import { ZkCoProcessorType, ZkCoProcessorConfig } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; /// @notice Statuses that a TDX quote/collateral verifier may emit. /// @dev Unknown is index 0 so uninitialized values fail closed. diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index bc9776b49..f38846a83 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -12,27 +12,27 @@ pragma solidity 0.8.15; * 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 { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { ITDXVerifier } from "interfaces/L1/proofs/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 { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; +import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; +import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; +import { DisputeGameFactory } from "src/L1/proofs/DisputeGameFactory.sol"; +import { GameType, Hash } from "src/libraries/bridge/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 { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; +import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; +import { MockVerifier } from "test/mocks/MockVerifier.sol"; +import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; +import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { MinimalProxyAdmin } from "./mocks/MinimalProxyAdmin.sol"; import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; diff --git a/scripts/multiproof/DeployTDXVerifier.s.sol b/scripts/multiproof/DeployTDXVerifier.s.sol index e5f955771..f083a527f 100644 --- a/scripts/multiproof/DeployTDXVerifier.s.sol +++ b/scripts/multiproof/DeployTDXVerifier.s.sol @@ -23,9 +23,9 @@ pragma solidity ^0.8.20; 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"; +import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { TDXTcbStatus } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; +import { TDXVerifier } from "src/L1/proofs/tee/TDXVerifier.sol"; contract DeployTDXVerifier is Script { /// @notice Maximum TDX quote age accepted by TDXVerifier. diff --git a/src/L1/proofs/tee/TDXVerifier.sol b/src/L1/proofs/tee/TDXVerifier.sol index d8175830a..d47eb5c7e 100644 --- a/src/L1/proofs/tee/TDXVerifier.sol +++ b/src/L1/proofs/tee/TDXVerifier.sol @@ -13,7 +13,7 @@ import { TDXVerifierJournal, ZkCoProcessorType, ZkCoProcessorConfig -} from "interfaces/multiproof/tee/ITDXVerifier.sol"; +} from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; /// @title TDXVerifier /// @notice Production-shape Intel TDX DCAP verifier for multiproof signer registration. diff --git a/test/L1/proofs/TDXVerifier.t.sol b/test/L1/proofs/TDXVerifier.t.sol index 291b34614..83c74afbd 100644 --- a/test/L1/proofs/TDXVerifier.t.sol +++ b/test/L1/proofs/TDXVerifier.t.sol @@ -3,10 +3,10 @@ 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 { TDXTcbStatus, TDXVerificationResult, TDXVerifierJournal } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; +import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; -import { TDXVerifier } from "src/multiproof/tee/TDXVerifier.sol"; +import { TDXVerifier } from "src/L1/proofs/tee/TDXVerifier.sol"; contract TDXVerifierTest is Test { TDXVerifier internal verifier; diff --git a/test/L1/proofs/TEEProverRegistryTDX.t.sol b/test/L1/proofs/TEEProverRegistryTDX.t.sol index 0df45c5b6..742960312 100644 --- a/test/L1/proofs/TEEProverRegistryTDX.t.sol +++ b/test/L1/proofs/TEEProverRegistryTDX.t.sol @@ -3,19 +3,19 @@ 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 { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; +import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; +import { INitroEnclaveVerifier } from "interfaces/L1/proofs/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"; +} from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; +import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { GameType } from "src/libraries/bridge/Types.sol"; -import { TEEProverRegistry } from "src/multiproof/tee/TEEProverRegistry.sol"; +import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; /// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. contract MockAggregateVerifierForTDXRegistry { From 92aeb9cf01db4bb9db459a5c4e35c3606c338989 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Wed, 6 May 2026 09:15:14 -0400 Subject: [PATCH 13/20] deploy dev --- deployments/11155111-dev-with-tdx.json | 11 +++++++++++ deployments/11155111-tdx-verifier.json | 7 +++++++ scripts/multiproof/justfile | 8 +++----- 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 deployments/11155111-dev-with-tdx.json create mode 100644 deployments/11155111-tdx-verifier.json diff --git a/deployments/11155111-dev-with-tdx.json b/deployments/11155111-dev-with-tdx.json new file mode 100644 index 000000000..b8a23d465 --- /dev/null +++ b/deployments/11155111-dev-with-tdx.json @@ -0,0 +1,11 @@ +{ + "AggregateVerifier": "0x5392E1C4C94721091A887096CAfD04850B812D59", + "AnchorStateRegistry": "0x9fB436292fEAD6bAadD64688C8911d6FEb813C65", + "DelayedWETH": "0x02B34E49D429B9499E59034e922b483054785D73", + "DisputeGameFactory": "0xe87372f9e11E5e3050969B81A5C5dc448ae2132e", + "NitroEnclaveVerifier": "0x77461a6434fFE3435206B19658F33274f3104e07", + "TDXRegistrationManager": "0x44E999A5859c2D12378a349882fAe5805DCE71b9", + "TDXVerifier": "0x9c38b756B31529Cad02D54A4ba810F3ff1e00054", + "TEEProverRegistry": "0x28Ce5A280A7775ee97587C513C4b4533597fB00b", + "TEEVerifier": "0x268b5AcA6a85f08fD61D3564f012b73D64926F49" +} diff --git a/deployments/11155111-tdx-verifier.json b/deployments/11155111-tdx-verifier.json new file mode 100644 index 000000000..e35a8cffc --- /dev/null +++ b/deployments/11155111-tdx-verifier.json @@ -0,0 +1,7 @@ +{ + "IntelRootCaHash": "0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009", + "MaxTimeDiff": 3600, + "RiscZeroVerifierRouter": "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187", + "TDXVerifier": "0x9c38b756B31529Cad02D54A4ba810F3ff1e00054", + "TDXVerifierId": "0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b" +} diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile index 86559bb92..2ce1b00d6 100644 --- a/scripts/multiproof/justfile +++ b/scripts/multiproof/justfile @@ -7,11 +7,10 @@ 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_tdx_verifier_id := "0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b" sepolia_intel_root_ca_hash := "0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009" -sepolia_tdx_image_hash := "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6" -sepolia_tdx_signer := "0x6A1f38f20044e8e69EAC755144F14f973e7b8d6E" -sepolia_tdx_registration_manager := "0x93900CB7eCdB5994352b19DfD8a900Cd4fa437B7" +sepolia_tdx_image_hash := "0x4cb35ee476a8098c4e567098714c65f5afe25236fc460b38487a356e14e7db66" +sepolia_tdx_registration_manager := "0x44E999A5859c2D12378a349882fAe5805DCE71b9" # List available multiproof deployment recipes. default: @@ -49,7 +48,6 @@ tdx-sepolia-config: @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. From 604854e956d3b2ab352fc1dacde3ffa0c9194a28 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Wed, 6 May 2026 10:27:35 -0400 Subject: [PATCH 14/20] add asr config to tdx script --- deployments/11155111-dev-with-tdx.json | 16 ++++---- scripts/multiproof/DeployDevWithTDX.s.sol | 29 ++++++++++--- scripts/multiproof/README.md | 20 ++++++--- scripts/multiproof/justfile | 50 +++++++++++++++++++++-- src/L1/proofs/tee/TDXVerifier.sol | 2 +- 5 files changed, 94 insertions(+), 23 deletions(-) diff --git a/deployments/11155111-dev-with-tdx.json b/deployments/11155111-dev-with-tdx.json index b8a23d465..1c90ea2b9 100644 --- a/deployments/11155111-dev-with-tdx.json +++ b/deployments/11155111-dev-with-tdx.json @@ -1,11 +1,13 @@ { - "AggregateVerifier": "0x5392E1C4C94721091A887096CAfD04850B812D59", - "AnchorStateRegistry": "0x9fB436292fEAD6bAadD64688C8911d6FEb813C65", - "DelayedWETH": "0x02B34E49D429B9499E59034e922b483054785D73", - "DisputeGameFactory": "0xe87372f9e11E5e3050969B81A5C5dc448ae2132e", + "ASRStartingBlockNumber": 41154575, + "ASRStartingOutputRoot": "0x2cb991c0d9e739677fbcc78772c01f2e4639dffb159422b08cfbea1160f8877c", + "AggregateVerifier": "0x4Cf26dE13cC4D64352f6c76b8ef888bEA6a2Acef", + "AnchorStateRegistry": "0x629cf75322989cE321e10BfA4DF2daE43ac19c82", + "DelayedWETH": "0x17626e33878597cbbFc877c9AA47945E64eB6D31", + "DisputeGameFactory": "0x6C9De6Cc269844c3121B2b59F239F79bace2303D", "NitroEnclaveVerifier": "0x77461a6434fFE3435206B19658F33274f3104e07", "TDXRegistrationManager": "0x44E999A5859c2D12378a349882fAe5805DCE71b9", "TDXVerifier": "0x9c38b756B31529Cad02D54A4ba810F3ff1e00054", - "TEEProverRegistry": "0x28Ce5A280A7775ee97587C513C4b4533597fB00b", - "TEEVerifier": "0x268b5AcA6a85f08fD61D3564f012b73D64926F49" -} + "TEEProverRegistry": "0x52F8f01Fb5e2cF6BE15a311EbFB30f24352C7221", + "TEEVerifier": "0xf218d04E5A582deEA9E952E4227C07e865BC8bA1" +} \ No newline at end of file diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index f38846a83..3a8aa17d6 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -54,6 +54,8 @@ contract DeployDevWithTDX is Script { IAnchorStateRegistry public mockAnchorRegistry; address public mockDelayedWETH; address public aggregateVerifier; + Hash public startingAnchorRoot; + uint256 public startingAnchorBlockNumber; function setUp() public { DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(cfg), _cname: "DeployConfig" }); @@ -65,12 +67,26 @@ contract DeployDevWithTDX is Script { } function run(address tdxVerifier, address registrationManager) public { + run(tdxVerifier, registrationManager, cfg.multiproofGenesisOutputRoot(), cfg.multiproofGenesisBlockNumber()); + } + + function run( + address tdxVerifier, + address registrationManager, + bytes32 asrStartingOutputRoot, + uint256 asrStartingBlockNumber + ) + 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"); + require(asrStartingOutputRoot != bytes32(0), "asrStartingOutputRoot must be non-zero"); tdxVerifierAddr = tdxVerifier; tdxRegistrationManager = registrationManager; + startingAnchorRoot = Hash.wrap(asrStartingOutputRoot); + startingAnchorBlockNumber = asrStartingBlockNumber; GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); @@ -83,6 +99,8 @@ contract DeployDevWithTDX is Script { console.log("NitroEnclaveVerifier:", nitroEnclaveVerifierAddr); console.log("TDXVerifier:", tdxVerifierAddr); console.log("TDX Registration Manager:", tdxRegistrationManager); + console.log("ASR Starting Output Root:", vm.toString(startingAnchorRoot.raw())); + console.log("ASR Starting L2 Block:", startingAnchorBlockNumber); console.log(""); console.log("NOTE: TDXVerifier owner must be the broadcaster/finalSystemOwner."); @@ -137,12 +155,7 @@ contract DeployDevWithTDX is Script { MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); mockAnchorRegistry = IAnchorStateRegistry(address(asr)); - asr.initialize( - disputeGameFactory, - Hash.wrap(cfg.multiproofGenesisOutputRoot()), - cfg.multiproofGenesisBlockNumber(), - gameType - ); + asr.initialize(disputeGameFactory, startingAnchorRoot, startingAnchorBlockNumber, gameType); } function _deployAggregateVerifier(GameType gameType) internal { @@ -186,6 +199,8 @@ contract DeployDevWithTDX is Script { console.log("\nInfrastructure:"); console.log(" DisputeGameFactory:", disputeGameFactory); console.log(" AnchorStateRegistry (mock):", address(mockAnchorRegistry)); + console.log(" ASR Starting Output Root:", vm.toString(startingAnchorRoot.raw())); + console.log(" ASR Starting L2 Block:", startingAnchorBlockNumber); console.log(" DelayedWETH (mock):", mockDelayedWETH); console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); @@ -212,6 +227,8 @@ contract DeployDevWithTDX is Script { vm.serializeAddress(key, "TEEVerifier", teeVerifier); vm.serializeAddress(key, "DisputeGameFactory", disputeGameFactory); vm.serializeAddress(key, "AnchorStateRegistry", address(mockAnchorRegistry)); + vm.serializeBytes32(key, "ASRStartingOutputRoot", startingAnchorRoot.raw()); + vm.serializeUint(key, "ASRStartingBlockNumber", startingAnchorBlockNumber); vm.serializeAddress(key, "DelayedWETH", mockDelayedWETH); string memory json = vm.serializeAddress(key, "AggregateVerifier", aggregateVerifier); diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 15987e1d1..4eb4f0e55 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -138,10 +138,10 @@ The deployer address (`finalSystemOwner`) is the owner of `DevTEEProverRegistry` 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`. | +| 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: @@ -217,6 +217,8 @@ Set `DEPLOY_CONFIG_PATH` to the Sepolia deploy config and pass the `TDXVerifier` 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. +The `deploy-tdx-stack` recipe resolves a recent L2 output root before invoking `DeployDevWithTDX`, then injects the resolved output root and L2 block through `run(address,address,bytes32,uint256)`. Use `L2_OUTPUT_ROOT_RPC_URL` if the `optimism_outputAtBlock` endpoint differs from the L2 execution RPC, and `ASR_ANCHOR_BLOCK_LOOKBACK` to anchor a fixed number of L2 blocks behind head. For a fixed anchor, set both `ASR_ANCHOR_OUTPUT_ROOT` and `ASR_ANCHOR_BLOCK_NUMBER`. + ```bash just --justfile scripts/multiproof/justfile deploy-tdx-stack $TDX_VERIFIER ``` @@ -224,10 +226,18 @@ just --justfile scripts/multiproof/justfile deploy-tdx-stack $TDX_VERIFIER To override the manager manually, use: ```bash +export L2_RPC_URL= +export L2_OUTPUT_ROOT_RPC_URL= +ASR_ANCHOR_BLOCK_NUMBER=$(cast block-number --rpc-url $L2_RPC_URL) +ASR_ANCHOR_OUTPUT_ROOT=$(cast rpc optimism_outputAtBlock $(cast to-hex $ASR_ANCHOR_BLOCK_NUMBER) \ + --rpc-url $L2_OUTPUT_ROOT_RPC_URL | jq -r '.outputRoot') + forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \ - --sig "run(address,address)" \ + --sig "run(address,address,bytes32,uint256)" \ $TDX_VERIFIER \ $TDX_REGISTRATION_MANAGER \ + $ASR_ANCHOR_OUTPUT_ROOT \ + $ASR_ANCHOR_BLOCK_NUMBER \ --rpc-url $L1_RPC_URL \ --broadcast \ --private-key $PRIVATE_KEY diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile index 2ce1b00d6..4d69f21a6 100644 --- a/scripts/multiproof/justfile +++ b/scripts/multiproof/justfile @@ -4,7 +4,9 @@ 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" +l1_rpc_url := env_var_or_default("L1_RPC_URL", "https://ethereum-full-sepolia-k8s-dev.cbhq.net") +l2_rpc_url := env_var_or_default("L2_RPC_URL", "https://base-sepolia-archive-k8s-dev.cbhq.net:8545") +l2_output_root_rpc_url := env_var_or_default("L2_OUTPUT_ROOT_RPC_URL", "https://base-sepolia-archive-k8s-dev.cbhq.net:7545") sepolia_risc0_verifier_router := "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" sepolia_tdx_verifier_id := "0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b" @@ -44,31 +46,69 @@ deploy-tdx-verifier risc0_router=sepolia_risc0_verifier_router tdx_verifier_id=s # Print the Sepolia TDX constants used by these recipes. tdx-sepolia-config: + @echo "L1_RPC_URL={{l1_rpc_url}}" @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_REGISTRATION_MANAGER={{sepolia_tdx_registration_manager}}" + @echo "L2_RPC_URL={{l2_rpc_url}}" + @echo "L2_OUTPUT_ROOT_RPC_URL={{l2_output_root_rpc_url}}" # Deploy the TDX multiproof dev/test stack using deploy-config/sepolia.json. # # Required env: # L1_RPC_URL +# L2_RPC_URL Optional when using this recipe; defaults to Base Sepolia L2. +# L2_OUTPUT_ROOT_RPC_URL Optional when using this recipe; defaults to Base Sepolia op-node/archive RPC. +# ASR_ANCHOR_BLOCK_LOOKBACK Optional L2 block lookback from head; defaults to 0. +# ASR_ANCHOR_BLOCK_NUMBER Optional explicit L2 block. Requires ASR_ANCHOR_OUTPUT_ROOT. +# ASR_ANCHOR_OUTPUT_ROOT Optional explicit output root. Requires ASR_ANCHOR_BLOCK_NUMBER. # # 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: +deploy-tdx-stack tdx_verifier tdx_manager=sepolia_tdx_registration_manager deploy_config=default_deploy_config l2_rpc=l2_rpc_url l2_output_root_rpc=l2_output_root_rpc_url: #!/usr/bin/env bash set -euo pipefail cd "{{repo_root}}" owner="$(cast wallet address --account "{{forge_account}}")" echo "Using {{forge_account}} as deployer: $owner" + + if [[ -n "${ASR_ANCHOR_BLOCK_NUMBER:-}" || -n "${ASR_ANCHOR_OUTPUT_ROOT:-}" ]]; then + if [[ -z "${ASR_ANCHOR_BLOCK_NUMBER:-}" || -z "${ASR_ANCHOR_OUTPUT_ROOT:-}" ]]; then + echo "ASR_ANCHOR_BLOCK_NUMBER and ASR_ANCHOR_OUTPUT_ROOT must be set together" >&2 + exit 1 + fi + asr_anchor_block="$ASR_ANCHOR_BLOCK_NUMBER" + asr_anchor_output_root="$ASR_ANCHOR_OUTPUT_ROOT" + else + lookback="${ASR_ANCHOR_BLOCK_LOOKBACK:-0}" + l2_head="$(cast block-number --rpc-url "{{l2_rpc}}")" + if (( lookback > l2_head )); then + echo "ASR_ANCHOR_BLOCK_LOOKBACK ($lookback) exceeds L2 head ($l2_head)" >&2 + exit 1 + fi + asr_anchor_block=$((l2_head - lookback)) + asr_anchor_block_hex="$(cast to-hex "$asr_anchor_block")" + asr_anchor_output_root="$(cast rpc optimism_outputAtBlock "$asr_anchor_block_hex" --rpc-url "{{l2_output_root_rpc}}" | jq -r '.outputRoot')" + fi + + if [[ -z "$asr_anchor_output_root" || "$asr_anchor_output_root" == "null" || "$asr_anchor_output_root" == "0x0000000000000000000000000000000000000000000000000000000000000000" ]]; then + echo "Invalid ASR anchor output root: $asr_anchor_output_root" >&2 + exit 1 + fi + + echo "Using ASR anchor output root: $asr_anchor_output_root" + echo "Using ASR anchor L2 block: $asr_anchor_block" + DEPLOY_CONFIG_PATH="{{deploy_config}}" \ forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \ - --sig "run(address,address)" \ + --sig "run(address,address,bytes32,uint256)" \ "{{tdx_verifier}}" \ "{{tdx_manager}}" \ + "$asr_anchor_output_root" \ + "$asr_anchor_block" \ --rpc-url "{{l1_rpc_url}}" \ --broadcast \ --account "{{forge_account}}" \ @@ -77,6 +117,8 @@ deploy-tdx-stack tdx_verifier tdx_manager=sepolia_tdx_registration_manager deplo # Print the two-step Sepolia command template. tdx-sepolia-help: @echo 'export L1_RPC_URL=' + @echo 'export L2_RPC_URL=' + @echo 'export L2_OUTPUT_ROOT_RPC_URL=' @echo 'just deploy-tdx-verifier' - @echo 'just deploy-tdx-stack [tdx-manager]' + @echo 'just deploy-tdx-stack [tdx-manager] [deploy-config] [l2-rpc] [l2-output-root-rpc]' @echo 'just tdx-sepolia-config' diff --git a/src/L1/proofs/tee/TDXVerifier.sol b/src/L1/proofs/tee/TDXVerifier.sol index d47eb5c7e..09e9b5195 100644 --- a/src/L1/proofs/tee/TDXVerifier.sol +++ b/src/L1/proofs/tee/TDXVerifier.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import { Ownable } from "@solady/auth/Ownable.sol"; -import { ISP1Verifier } from "lib/sp1-contracts/contracts/src/ISP1Verifier.sol"; +import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; From accc9230fd4fdbf45d7e8de9a040b5252911eebd Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Wed, 6 May 2026 12:44:34 -0400 Subject: [PATCH 15/20] fix AggregateVerifier flow --- deploy-config/local.json | 2 + deploy-config/sepolia.json | 2 + scripts/deploy/Deploy.s.sol | 3 +- scripts/deploy/DeployConfig.s.sol | 4 + scripts/deploy/DeployImplementations.s.sol | 5 +- scripts/multiproof/DeployDevNoNitro.s.sol | 13 +-- scripts/multiproof/DeployDevWithNitro.s.sol | 5 +- scripts/multiproof/DeployDevWithTDX.s.sol | 5 +- scripts/multiproof/README.md | 18 ++-- snapshots/abi/AggregateVerifier.json | 33 +++++- snapshots/abi/TEEProverRegistry.json | 15 ++- snapshots/abi/TEEVerifier.json | 21 ---- snapshots/semver-lock.json | 18 ++-- src/L1/proofs/AggregateVerifier.sol | 87 +++++++++++---- src/L1/proofs/tee/TEEProverRegistry.sol | 54 ++++++---- src/L1/proofs/tee/TEEVerifier.sol | 53 ++-------- test/L1/proofs/AggregateVerifier.t.sol | 73 +++++++++++-- test/L1/proofs/BaseTest.t.sol | 20 +++- test/L1/proofs/TEEProverRegistry.t.sol | 8 +- test/L1/proofs/TEEProverRegistryTDX.t.sol | 8 +- test/L1/proofs/TEEVerifier.t.sol | 111 +++++++------------- test/mocks/MockDevTEEProverRegistry.sol | 2 +- test/opcm/DeployImplementations.t.sol | 6 +- test/opcm/DeployOPChain.t.sol | 3 +- 24 files changed, 339 insertions(+), 230 deletions(-) diff --git a/deploy-config/local.json b/deploy-config/local.json index b13afac7b..501074350 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -67,6 +67,8 @@ "useRevenueShare": false, "chainFeesRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeNitroImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeTdxImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", "multiproofConfigHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "multiproofGameType": 621, "teeProposer": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index fd6db1552..a21340ed4 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -59,6 +59,8 @@ "useRevenueShare": true, "chainFeesRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", "teeImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", + "teeNitroImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", + "teeTdxImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360", "multiproofGameType": 621, "teeProposer": "0xdb84125f2f4229c81c579f41bc129c71b174eb58", diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol index 38c442abc..7538b1ea8 100644 --- a/scripts/deploy/Deploy.s.sol +++ b/scripts/deploy/Deploy.s.sol @@ -235,7 +235,8 @@ contract Deploy is Deployer { faultGameV2SplitDepth: cfg.faultGameV2SplitDepth(), faultGameV2ClockExtension: cfg.faultGameV2ClockExtension(), faultGameV2MaxClockDuration: cfg.faultGameV2MaxClockDuration(), - teeImageHash: cfg.teeImageHash(), + teeNitroImageHash: cfg.teeNitroImageHash(), + teeTdxImageHash: cfg.teeTdxImageHash(), multiproofConfigHash: cfg.multiproofConfigHash(), multiproofGameType: cfg.multiproofGameType(), nitroEnclaveVerifier: cfg.nitroEnclaveVerifier(), diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index a2bb04aed..95f18629b 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -85,6 +85,8 @@ contract DeployConfig is Script { // Multiproof Configuration bytes32 public teeImageHash; + bytes32 public teeNitroImageHash; + bytes32 public teeTdxImageHash; bytes32 public multiproofConfigHash; uint256 public multiproofGameType; address public teeProposer; @@ -199,6 +201,8 @@ contract DeployConfig is Script { faultGameV2ClockExtension = _readOr(_json, "$.faultGameV2ClockExtension", 10800); faultGameV2MaxClockDuration = _readOr(_json, "$.faultGameV2MaxClockDuration", 302400); teeImageHash = bytes32(_readOr(_json, "$.teeImageHash", 0)); + teeNitroImageHash = bytes32(_readOr(_json, "$.teeNitroImageHash", uint256(teeImageHash))); + teeTdxImageHash = bytes32(_readOr(_json, "$.teeTdxImageHash", uint256(teeImageHash))); multiproofConfigHash = bytes32(_readOr(_json, "$.multiproofConfigHash", 0)); multiproofGameType = _readOr(_json, "$.multiproofGameType", 621); teeProposer = stdJson.readAddress(_json, "$.teeProposer"); diff --git a/scripts/deploy/DeployImplementations.s.sol b/scripts/deploy/DeployImplementations.s.sol index 8db384da5..73d0b434c 100644 --- a/scripts/deploy/DeployImplementations.s.sol +++ b/scripts/deploy/DeployImplementations.s.sol @@ -62,7 +62,8 @@ contract DeployImplementations is Script { uint256 faultGameV2ClockExtension; uint256 faultGameV2MaxClockDuration; // Multiproof parameters - bytes32 teeImageHash; + bytes32 teeNitroImageHash; + bytes32 teeTdxImageHash; bytes32 multiproofConfigHash; uint256 multiproofGameType; address nitroEnclaveVerifier; @@ -634,7 +635,7 @@ contract DeployImplementations is Script { _output.delayedWETHImpl, IVerifier(teeVerifierImpl), IVerifier(zkVerifier), - _input.teeImageHash, + AggregateVerifier.TeeHashes(_input.teeNitroImageHash, _input.teeTdxImageHash), AggregateVerifier.ZkHashes(bytes32(0), bytes32(0)), _input.multiproofConfigHash, _input.l2ChainID, diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 56a379576..d1330c1e3 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -29,11 +29,11 @@ pragma solidity 0.8.15; * After deployment, register one dev Nitro signer and one dev TDX signer: * * cast send $TEE_PROVER_REGISTRY \ - * "addDevSigner(address,bytes32)" $NITRO_SIGNER_ADDRESS $TEE_IMAGE_HASH \ + * "addDevSigner(address,bytes32)" $NITRO_SIGNER_ADDRESS $TEE_NITRO_IMAGE_HASH \ * --private-key $OWNER_KEY --rpc-url $RPC_URL * * cast send $TEE_PROVER_REGISTRY \ - * "addDevTDXSigner(address,bytes32)" $TDX_SIGNER_ADDRESS $TEE_IMAGE_HASH \ + * "addDevTDXSigner(address,bytes32)" $TDX_SIGNER_ADDRESS $TEE_TDX_IMAGE_HASH \ * --private-key $OWNER_KEY --rpc-url $RPC_URL * * No attestation, PCR0 registration, or certificate validation required. @@ -190,7 +190,7 @@ contract DeployDevNoNitro is Script { IDelayedWETH(payable(mockDelayedWETH)), IVerifier(teeVerifier), IVerifier(zkVerifier), - cfg.teeImageHash(), + AggregateVerifier.TeeHashes(cfg.teeNitroImageHash(), cfg.teeTdxImageHash()), zkHashes, cfg.multiproofConfigHash(), cfg.l2ChainID(), @@ -219,17 +219,18 @@ contract DeployDevNoNitro is Script { console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); console.log(" Game Type:", cfg.multiproofGameType()); - console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); + console.log(" Nitro Image Hash:", vm.toString(cfg.teeNitroImageHash())); + console.log(" TDX Image Hash:", vm.toString(cfg.teeTdxImageHash())); console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); console.log("========================================"); console.log("\n>>> NEXT STEP - Register dev Nitro and TDX signers (NO ATTESTATION NEEDED) <<<"); console.log("\ncast send", teeProverRegistryProxy); console.log(' "addDevSigner(address,bytes32)" '); - console.log(" ", vm.toString(cfg.teeImageHash())); + console.log(" ", vm.toString(cfg.teeNitroImageHash())); console.log(" --private-key --rpc-url "); console.log("\ncast send", teeProverRegistryProxy); console.log(' "addDevTDXSigner(address,bytes32)" '); - console.log(" ", vm.toString(cfg.teeImageHash())); + console.log(" ", vm.toString(cfg.teeTdxImageHash())); console.log(" --private-key --rpc-url "); console.log("\n========================================\n"); } diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index c76a75018..b8ed52ebb 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -179,7 +179,7 @@ contract DeployDevWithNitro is Script { IDelayedWETH(payable(mockDelayedWETH)), IVerifier(teeVerifier), IVerifier(zkVerifier), - cfg.teeImageHash(), + AggregateVerifier.TeeHashes(cfg.teeNitroImageHash(), cfg.teeTdxImageHash()), zkHashes, cfg.multiproofConfigHash(), cfg.l2ChainID(), @@ -209,7 +209,8 @@ contract DeployDevWithNitro is Script { console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); console.log(" Game Type:", cfg.multiproofGameType()); - console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); + console.log(" Nitro Image Hash:", vm.toString(cfg.teeNitroImageHash())); + console.log(" TDX Image Hash:", vm.toString(cfg.teeTdxImageHash())); console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); console.log("========================================"); console.log("\n>>> NEXT STEP: Register one Nitro signer and one TDX signer <<<"); diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index 3a8aa17d6..98b101983 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -172,7 +172,7 @@ contract DeployDevWithTDX is Script { IDelayedWETH(payable(mockDelayedWETH)), IVerifier(teeVerifier), IVerifier(zkVerifier), - cfg.teeImageHash(), + AggregateVerifier.TeeHashes(cfg.teeNitroImageHash(), cfg.teeTdxImageHash()), zkHashes, cfg.multiproofConfigHash(), cfg.l2ChainID(), @@ -205,7 +205,8 @@ contract DeployDevWithTDX is Script { console.log("\nGame:"); console.log(" AggregateVerifier:", aggregateVerifier); console.log(" Game Type:", cfg.multiproofGameType()); - console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); + console.log(" Nitro Image Hash:", vm.toString(cfg.teeNitroImageHash())); + console.log(" TDX Image Hash:", vm.toString(cfg.teeTdxImageHash())); console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); console.log("========================================"); console.log("\n>>> NEXT STEP: Register one Nitro signer and one TDX signer <<<"); diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 4eb4f0e55..663649bd0 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -43,7 +43,8 @@ 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) | +| `teeNitroImageHash` | PCR0 hash used when registering the Nitro dev signer | +| `teeTdxImageHash` | TDX image hash used when registering the TDX dev signer | | `multiproofGameType` | Game type ID for the dispute game | | `multiproofGenesisOutputRoot` | Initial anchor output root | | `multiproofGenesisBlockNumber` | Initial anchor L2 block number | @@ -100,8 +101,9 @@ This returns a raw byte array representing an uncompressed secp256k1 public key 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. +> **Note:** PCR0 / TDX image enforcement is handled by `AggregateVerifier` (which bakes +> `teeNitroImageHash` and `teeTdxImageHash` into the journal the enclaves sign). The registry +> only tracks which signer addresses are valid. ```bash # Replace: @@ -110,15 +112,15 @@ Call `addDevSigner` for the Nitro signer and `addDevTDXSigner` for the TDX signe cast send 0x587d410B205449fB889EC4a5b351D375C656d084 \ "addDevSigner(address,bytes32)" \ 0x080f42420846c613158D7b4334257C78bE5A9B90 \ - $TEE_IMAGE_HASH \ + $TEE_NITRO_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. +# Register a TDX dev signer for the TDX image hash. cast send 0x587d410B205449fB889EC4a5b351D375C656d084 \ "addDevTDXSigner(address,bytes32)" \ $TDX_SIGNER_ADDRESS \ - $TEE_IMAGE_HASH \ + $TEE_TDX_IMAGE_HASH \ --rpc-url https://c3-chainproxy-eth-sepolia-full-dev.cbhq.net \ --ledger --mnemonic-derivation-path "m/44'/60'/1'/0/0" ``` @@ -154,7 +156,7 @@ 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: +The Solidity verifier then enforces local policy over the proven journal. The PoC maps TDX measurements into `TEE_TDX_IMAGE_HASH` as: ```text keccak256(MRTD || RTMR0 || RTMR1 || RTMR2 || RTMR3) @@ -168,7 +170,7 @@ The attested public key must be supplied as an uncompressed 65-byte secp256k1 pu 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`. +`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 their expected type-specific 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. diff --git a/snapshots/abi/AggregateVerifier.json b/snapshots/abi/AggregateVerifier.json index 9c991351e..54a0e5a02 100644 --- a/snapshots/abi/AggregateVerifier.json +++ b/snapshots/abi/AggregateVerifier.json @@ -27,9 +27,21 @@ "type": "address" }, { - "internalType": "bytes32", - "name": "teeImageHash", - "type": "bytes32" + "components": [ + { + "internalType": "bytes32", + "name": "nitroHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "tdxHash", + "type": "bytes32" + } + ], + "internalType": "struct AggregateVerifier.TeeHashes", + "name": "teeHashes", + "type": "tuple" }, { "components": [ @@ -230,7 +242,20 @@ }, { "inputs": [], - "name": "TEE_IMAGE_HASH", + "name": "TEE_NITRO_IMAGE_HASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TEE_TDX_IMAGE_HASH", "outputs": [ { "internalType": "bytes32", diff --git a/snapshots/abi/TEEProverRegistry.json b/snapshots/abi/TEEProverRegistry.json index ceff44a08..44586ef24 100644 --- a/snapshots/abi/TEEProverRegistry.json +++ b/snapshots/abi/TEEProverRegistry.json @@ -100,7 +100,20 @@ }, { "inputs": [], - "name": "getExpectedImageHash", + "name": "getExpectedNitroImageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getExpectedTDXImageHash", "outputs": [ { "internalType": "bytes32", diff --git a/snapshots/abi/TEEVerifier.json b/snapshots/abi/TEEVerifier.json index fff275ed7..b491ef7a5 100644 --- a/snapshots/abi/TEEVerifier.json +++ b/snapshots/abi/TEEVerifier.json @@ -116,17 +116,6 @@ "name": "VerifierNullified", "type": "event" }, - { - "inputs": [ - { - "internalType": "address", - "name": "signer", - "type": "address" - } - ], - "name": "DuplicateSigner", - "type": "error" - }, { "inputs": [ { @@ -175,16 +164,6 @@ "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 d2878448f..ffad91569 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -36,8 +36,8 @@ "sourceCodeHash": "0x5e68ac31e5f3a16a8845c0d1fd6ac4aa03260002b8f232924099e8be3db3a95a" }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x10bb6a60f21de103d1da9ff310a38f571c53a8113d04a029087648debf3f0341", - "sourceCodeHash": "0xd1fa080d716366236f8b963aa80a0c79e474c5603a2b1615dfb14fb5a99dbe6a" + "initCodeHash": "0x6e50ad8219dfc59503395bf63d2a4d91c002bd21a1df7c902debedea9c2e13eb", + "sourceCodeHash": "0x5708e88bac4268a2c1310c3b9aa84335310ae6941a90e41bc10212f356b2060a" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", @@ -55,13 +55,17 @@ "initCodeHash": "0x82f42b4d578bfcf9dc35eaa2c4ada04a45e1eca63021bceceb2ec794b12a9dd6", "sourceCodeHash": "0x172d22287f2131ef06e2fe3b475e802e15024de91b0a0ff8267ee63fc31b32a7" }, + "src/L1/proofs/tee/TDXVerifier.sol:TDXVerifier": { + "initCodeHash": "0xab68cfaae62a40d02a7d096bf1c81ec8ac2c106b19859ffa772a22b477a55af2", + "sourceCodeHash": "0x82a3eff9a7566e483b5f015dc97a88d90bdc4d9d7d49794556106bfc86d55ea0" + }, "src/L1/proofs/tee/TEEProverRegistry.sol:TEEProverRegistry": { - "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0", - "sourceCodeHash": "0x23b0045834f94839018f9db307bd387d5abd90d59d3d941b09e69bb9f475e9ba" + "initCodeHash": "0x4b2fb8ba42668b1b9a8ebb99518fc0552703dfda190acde45d771847b450ac65", + "sourceCodeHash": "0xd14971b269de818bcceac3493284196dd0168fad3d21315db965d6d05a2cc050" }, "src/L1/proofs/tee/TEEVerifier.sol:TEEVerifier": { - "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af", - "sourceCodeHash": "0xe7d2abb1b2193437ceeecf00f1b2a3a5447867e98bafe033df66d0653c22ff7b" + "initCodeHash": "0x5f42455c6d41c0a49e05681a6fa7dbf21eb22c89705fd11b6030952b3227e559", + "sourceCodeHash": "0x83a09b025e33838859a7b5848463bb7f4d6d76031f5b8b8bc9f3cd5e73ad4464" }, "src/L1/proofs/v2/FaultDisputeGameV2.sol:FaultDisputeGameV2": { "initCodeHash": "0x6fc59e2da083c9e2093e42b0fda705e8215cc216e4dcedbf728c08f69ec2d3bd", @@ -159,4 +163,4 @@ "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } -} +} \ No newline at end of file diff --git a/src/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index e308c11f3..fb7e90616 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -43,6 +43,23 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { bytes32 aggregateHash; } + /// @notice Hashes for the TEE proving images. + struct TeeHashes { + bytes32 nitroHash; + bytes32 tdxHash; + } + + /// @notice Common public inputs used to compute each TEE signature journal. + struct TeeJournalInputs { + address proposer; + bytes32 l1OriginHash; + bytes32 startingRoot; + uint64 startingL2SequenceNumber; + bytes32 endingRoot; + uint64 endingL2SequenceNumber; + bytes intermediateRoots; + } + //////////////////////////////////////////////////////////////// // Constants // //////////////////////////////////////////////////////////////// @@ -66,8 +83,10 @@ 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; + /// @notice TEE proof payload size: + /// nitro image hash(32) + nitro signature(65) + tdx image hash(32) + tdx signature(65). + uint256 internal constant TEE_PROOF_BYTES_LENGTH = 194; + //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -83,8 +102,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The TEE prover. IVerifier public immutable TEE_VERIFIER; - /// @notice The hash of the TEE image. - bytes32 public immutable TEE_IMAGE_HASH; + /// @notice The hash of the Nitro TEE image. + bytes32 public immutable TEE_NITRO_IMAGE_HASH; + + /// @notice The hash of the TDX TEE image. + bytes32 public immutable TEE_TDX_IMAGE_HASH; /// @notice The ZK prover. IVerifier public immutable ZK_VERIFIER; @@ -258,7 +280,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @param delayedWETH The delayed WETH contract. /// @param teeVerifier The TEE verifier. /// @param zkVerifier The ZK verifier. - /// @param teeImageHash The hash of the TEE image. + /// @param teeHashes The hashes of the Nitro and TDX TEE images. /// @param zkHashes The hashes of the ZK range and aggregate programs. /// @param configHash The hash of the rollup configuration. /// @param l2ChainId The chain ID of the L2 network. @@ -270,7 +292,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { IDelayedWETH delayedWETH, IVerifier teeVerifier, IVerifier zkVerifier, - bytes32 teeImageHash, + TeeHashes memory teeHashes, ZkHashes memory zkHashes, bytes32 configHash, uint256 l2ChainId, @@ -289,7 +311,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { DELAYED_WETH = delayedWETH; TEE_VERIFIER = teeVerifier; ZK_VERIFIER = zkVerifier; - TEE_IMAGE_HASH = teeImageHash; + TEE_NITRO_IMAGE_HASH = teeHashes.nitroHash; + TEE_TDX_IMAGE_HASH = teeHashes.tdxHash; ZK_RANGE_HASH = zkHashes.rangeHash; ZK_AGGREGATE_HASH = zkHashes.aggregateHash; CONFIG_HASH = configHash; @@ -880,7 +903,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } /// @notice Verifies a TEE proof for the current game. - /// @param proofBytes The proof: nitro signature(65) + tdx signature(65). + /// @param proofBytes The proof: nitro image hash(32) + nitro signature(65) + tdx image hash(32) + /// + tdx signature(65). function _verifyTeeProof( bytes calldata proofBytes, address proposer, @@ -896,23 +920,48 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { { if (proofBytes.length != TEE_PROOF_BYTES_LENGTH) revert InvalidProof(); + bytes32 nitroImageHash = bytes32(proofBytes[:32]); + bytes32 tdxImageHash = bytes32(proofBytes[97:129]); + if (nitroImageHash != TEE_NITRO_IMAGE_HASH || tdxImageHash != TEE_TDX_IMAGE_HASH) revert InvalidProof(); + + TeeJournalInputs memory inputs = TeeJournalInputs({ + proposer: proposer, + l1OriginHash: l1OriginHash, + startingRoot: startingRoot, + startingL2SequenceNumber: startingL2SequenceNumber, + endingRoot: endingRoot, + endingL2SequenceNumber: endingL2SequenceNumber, + intermediateRoots: intermediateRoots + }); + + _verifyTeeSignature(proofBytes[32:97], nitroImageHash, inputs); + _verifyTeeSignature(proofBytes[129:194], tdxImageHash, inputs); + } + + function _verifyTeeSignature( + bytes calldata signature, + bytes32 imageHash, + TeeJournalInputs memory inputs + ) + internal + view + { bytes32 journal = keccak256( abi.encodePacked( - proposer, - l1OriginHash, - startingRoot, - startingL2SequenceNumber, - endingRoot, - endingL2SequenceNumber, - intermediateRoots, + inputs.proposer, + inputs.l1OriginHash, + inputs.startingRoot, + inputs.startingL2SequenceNumber, + inputs.endingRoot, + inputs.endingL2SequenceNumber, + inputs.intermediateRoots, CONFIG_HASH, - TEE_IMAGE_HASH + imageHash ) ); - // Validate the proof. - bytes memory proof = abi.encodePacked(proposer, proofBytes); - if (!TEE_VERIFIER.verify(proof, TEE_IMAGE_HASH, journal)) revert InvalidProof(); + bytes memory verifierProof = abi.encodePacked(inputs.proposer, signature); + if (!TEE_VERIFIER.verify(verifierProof, imageHash, journal)) revert InvalidProof(); } /// @notice Verifies a ZK proof for the current game. diff --git a/src/L1/proofs/tee/TEEProverRegistry.sol b/src/L1/proofs/tee/TEEProverRegistry.sol index de6cc0496..44401f028 100644 --- a/src/L1/proofs/tee/TEEProverRegistry.sol +++ b/src/L1/proofs/tee/TEEProverRegistry.sol @@ -22,9 +22,9 @@ import { GameType } from "src/libraries/bridge/Types.sol"; /// 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 +/// enabling pre-registration before hardforks. PCR0 / image enforcement happens at proof-submission /// time in TEEVerifier, which checks signerImageHash against the AggregateVerifier's -/// TEE_IMAGE_HASH and requires one Nitro signer plus one TDX signer. +/// type-specific TEE image hash and requires one Nitro signer plus one TDX signer. contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { using EnumerableSetLib for EnumerableSetLib.AddressSet; enum TEEType { @@ -47,7 +47,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @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. + /// @notice The DisputeGameFactory used to look up the current AggregateVerifier and its TEE image hashes. IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; /// @notice The game type used to look up the AggregateVerifier in the factory. @@ -59,7 +59,7 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @notice Mapping of signer address to the PCR0 image hash from their attestation. /// @dev Stored at registration time from the ZK-verified attestation document. - /// TEEVerifier checks this against the AggregateVerifier's TEE_IMAGE_HASH at + /// TEEVerifier checks this against the AggregateVerifier's type-specific TEE image hash at /// proof-submission time, so signers automatically become unusable when the /// AggregateVerifier upgrades to a new image hash. isValidSigner also uses /// this for off-chain pre-submission checks. @@ -106,10 +106,10 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @notice Thrown when the dispute game factory is not configured. error DisputeGameFactoryNotSet(); - /// @notice Thrown when reading TEE_IMAGE_HASH from the AggregateVerifier fails. + /// @notice Thrown when reading a TEE image hash from the AggregateVerifier fails. error ImageHashReadFailed(); - /// @notice Thrown when setting a game type whose AggregateVerifier has no TEE_IMAGE_HASH. + /// @notice Thrown when setting a game type whose AggregateVerifier has no type-specific TEE image hash. error InvalidGameType(); /// @notice Thrown when the TDX verifier is not configured. @@ -144,10 +144,13 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { } /// @notice Updates the game type used to look up the AggregateVerifier. - /// @dev Validates that the new game type has an AggregateVerifier with a non-zero TEE_IMAGE_HASH. + /// @dev Validates that the new game type has an AggregateVerifier with non-zero type-specific TEE image hashes. /// @param gameType_ The new game type ID. function setGameType(GameType gameType_) external onlyOwner { - if (_getExpectedImageHash(gameType_) == bytes32(0)) revert InvalidGameType(); + if ( + _getExpectedImageHash(gameType_, TEEType.NITRO) == bytes32(0) + || _getExpectedImageHash(gameType_, TEEType.TDX) == bytes32(0) + ) revert InvalidGameType(); gameType = gameType_; emit GameTypeUpdated(gameType_); } @@ -158,9 +161,9 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// 2. Is less than MAX_AGE old /// Registration is PCR0-agnostic: any enclave with a valid attestation can register. /// This enables pre-registration of new-PCR0 enclaves before a hardfork, eliminating - /// proof-generation delay when the on-chain TEE_IMAGE_HASH rotates. The TEEVerifier + /// proof-generation delay when the on-chain Nitro image hash rotates. The TEEVerifier /// enforces PCR0 correctness at proof-submission time by checking signerImageHash - /// against the AggregateVerifier's TEE_IMAGE_HASH, so pre-registered enclaves cannot + /// against the AggregateVerifier's TEE_NITRO_IMAGE_HASH, so pre-registered enclaves cannot /// produce accepted proofs until the hardfork activates. /// @param output The ABI-encoded VerifierJournal from the ZK proof. /// @param proofBytes The Risc0 ZK proof bytes. @@ -211,13 +214,15 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @notice Checks if an address is a valid signer. /// @dev Defense-in-depth: checks both that the signer is registered AND that their - /// registered image hash matches the current AggregateVerifier's TEE_IMAGE_HASH. + /// registered image hash matches the current AggregateVerifier's type-specific TEE image hash. /// This ensures signers automatically become invalid when the AggregateVerifier upgrades. /// @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) { if (!isRegisteredSigner[signer]) return false; - return signerImageHash[signer] == _getExpectedImageHash(gameType); + TEEType teeType = signerTEEType[signer]; + if (teeType == TEEType.NONE) return false; + return signerImageHash[signer] == _getExpectedImageHash(gameType, teeType); } /// @notice Returns all currently registered signer addresses. @@ -228,10 +233,16 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { return _registeredSigners.values(); } - /// @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(gameType); + /// @notice Returns the expected Nitro TEE image hash from the current AggregateVerifier. + /// @return The TEE_NITRO_IMAGE_HASH from the AggregateVerifier registered in the factory. + function getExpectedNitroImageHash() external view returns (bytes32) { + return _getExpectedImageHash(gameType, TEEType.NITRO); + } + + /// @notice Returns the expected TDX TEE image hash from the current AggregateVerifier. + /// @return The TEE_TDX_IMAGE_HASH from the AggregateVerifier registered in the factory. + function getExpectedTDXImageHash() external view returns (bytes32) { + return _getExpectedImageHash(gameType, TEEType.TDX); } /// @notice Initializes the contract with owner, manager, proposers, and game type. @@ -281,11 +292,14 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { emit SignerRegistered(signer); } - /// @dev Reads TEE_IMAGE_HASH from the AggregateVerifier registered in the factory for `gameType_`. - function _getExpectedImageHash(GameType gameType_) internal view returns (bytes32) { + /// @dev Reads a type-specific TEE image hash from the AggregateVerifier registered in the factory for `gameType_`. + function _getExpectedImageHash(GameType gameType_, TEEType teeType) internal view returns (bytes32) { + if (teeType == TEEType.NONE) revert InvalidTEEType(); 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()")); + bytes memory callData = teeType == TEEType.NITRO + ? abi.encodeWithSignature("TEE_NITRO_IMAGE_HASH()") + : abi.encodeWithSignature("TEE_TDX_IMAGE_HASH()"); + (bool success, bytes memory data) = impl.staticcall(callData); if (!success || data.length != 32) revert ImageHashReadFailed(); return abi.decode(data, (bytes32)); } diff --git a/src/L1/proofs/tee/TEEVerifier.sol b/src/L1/proofs/tee/TEEVerifier.sol index b8d9eea00..79e777eb0 100644 --- a/src/L1/proofs/tee/TEEVerifier.sol +++ b/src/L1/proofs/tee/TEEVerifier.sol @@ -12,9 +12,7 @@ import { Verifier } from "../Verifier.sol"; /// @title TEEVerifier /// @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 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. +/// It verifies one TEE signature at a time against a registered Nitro or TDX signer. /// 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 { @@ -25,8 +23,8 @@ contract TEEVerifier is Verifier, ISemver { /// @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 Size of a TEE proof: proposer(20) + signature(65). + uint256 internal constant TEE_PROOF_SIZE = 20 + SIGNATURE_SIZE; /// @notice Thrown when a recovered signer is not a valid registered signer. error InvalidSigner(address signer); @@ -43,15 +41,6 @@ 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( @@ -64,10 +53,8 @@ contract TEEVerifier is Verifier, ISemver { } /// @notice Verifies a TEE proof for a state transition. - /// @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 each signer's registered image hash to prevent cross-game-type attacks. + /// @param proofBytes The proof: proposer(20) + signature(65) = 85 bytes. + /// @param imageId The TEE image hash expected for the recovered signer. /// @param journal The keccak256 hash of the proof's public inputs. /// @return valid Whether the proof is valid. function verify( @@ -88,23 +75,9 @@ contract TEEVerifier is Verifier, ISemver { revert InvalidProposer(proposer); } - 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(); - } + bytes calldata signature = proofBytes[20:TEE_PROOF_SIZE]; + address signer = _recoverSigner(journal, signature); + _validateSigner(signer, imageId); return true; } @@ -116,15 +89,11 @@ contract TEEVerifier is Verifier, ISemver { if (err != ECDSA.RecoverError.NoError) revert InvalidSignature(); } - function _validateSigner(address signer, bytes32 imageId) - internal - view - returns (TEEProverRegistry.TEEType teeType) - { + function _validateSigner(address signer, bytes32 imageId) internal view { // 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); + TEEProverRegistry.TEEType signerTEEType = TEE_PROVER_REGISTRY.signerTEEType(signer); + if (signerTEEType == 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). diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index 7414e039c..a452398bb 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -19,6 +19,53 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + bytes memory intermediateRoots = + abi.encodePacked(_generateIntermediateRootsExceptLast(currentL2BlockNumber), rootClaim.raw()); + bytes32 l1OriginHash = blockhash(block.number - 1); + bytes32 startingRoot = keccak256(abi.encode(uint256(0))); + bytes32 saltHash = keccak256("tee-proof"); + bytes memory nitroSignature = abi.encodePacked(saltHash, bytes32(0), uint8(27)); + bytes memory tdxSignature = abi.encodePacked(saltHash, bytes32(uint256(1)), uint8(28)); + bytes32 nitroJournal = keccak256( + abi.encodePacked( + TEE_PROVER, + l1OriginHash, + startingRoot, + uint64(0), + rootClaim.raw(), + uint64(currentL2BlockNumber), + intermediateRoots, + CONFIG_HASH, + TEE_NITRO_IMAGE_HASH + ) + ); + bytes32 tdxJournal = keccak256( + abi.encodePacked( + TEE_PROVER, + l1OriginHash, + startingRoot, + uint64(0), + rootClaim.raw(), + uint64(currentL2BlockNumber), + intermediateRoots, + CONFIG_HASH, + TEE_TDX_IMAGE_HASH + ) + ); + + vm.expectCall( + address(teeVerifier), + abi.encodeCall( + IVerifier.verify, (abi.encodePacked(TEE_PROVER, nitroSignature), TEE_NITRO_IMAGE_HASH, nitroJournal) + ) + ); + vm.expectCall( + address(teeVerifier), + abi.encodeCall( + IVerifier.verify, (abi.encodePacked(TEE_PROVER, tdxSignature), TEE_TDX_IMAGE_HASH, tdxJournal) + ) + ); + AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); @@ -268,7 +315,7 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(130)); + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(194)); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) @@ -290,7 +337,7 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(130)); + abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(194)); vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); _createAggregateVerifierGame( @@ -325,8 +372,12 @@ contract AggregateVerifierTest is BaseTest { bytes32 l1OriginHash = blockhash(l1OriginNumber); Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, new bytes(130)); + bytes memory proofBytes = abi.encodePacked( + uint8(AggregateVerifier.ProofType.TEE), + l1OriginHash, + l1OriginNumber, + _generateProofBody("tee-proof", AggregateVerifier.ProofType.TEE) + ); _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes @@ -351,8 +402,12 @@ contract AggregateVerifierTest is BaseTest { abi.encode(expectedHash) // returns the blockhash ); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, new bytes(130)); + bytes memory proofBytes = abi.encodePacked( + uint8(AggregateVerifier.ProofType.TEE), + expectedHash, + l1OriginNumber, + _generateProofBody("tee-proof", AggregateVerifier.ProofType.TEE) + ); _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes @@ -370,7 +425,7 @@ contract AggregateVerifierTest is BaseTest { IDelayedWETH(payable(address(delayedWETH))), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, + AggregateVerifier.TeeHashes(TEE_NITRO_IMAGE_HASH, TEE_TDX_IMAGE_HASH), AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, @@ -386,7 +441,7 @@ contract AggregateVerifierTest is BaseTest { IDelayedWETH(payable(address(delayedWETH))), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, + AggregateVerifier.TeeHashes(TEE_NITRO_IMAGE_HASH, TEE_TDX_IMAGE_HASH), AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, @@ -402,7 +457,7 @@ contract AggregateVerifierTest is BaseTest { IDelayedWETH(payable(address(delayedWETH))), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, + AggregateVerifier.TeeHashes(TEE_NITRO_IMAGE_HASH, TEE_TDX_IMAGE_HASH), AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 6c552ef08..2e6e5eca4 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -47,7 +47,8 @@ contract BaseTest is Test { address public immutable ZK_PROVER = makeAddr("zk-prover"); address public immutable ATTACKER = makeAddr("attacker"); - bytes32 public immutable TEE_IMAGE_HASH = keccak256("tee-image"); + bytes32 public immutable TEE_NITRO_IMAGE_HASH = keccak256("tee-nitro-image"); + bytes32 public immutable TEE_TDX_IMAGE_HASH = keccak256("tee-tdx-image"); bytes32 public immutable ZK_RANGE_HASH = keccak256("zk-range"); bytes32 public immutable ZK_AGGREGATE_HASH = keccak256("zk-aggregate"); bytes32 public immutable CONFIG_HASH = keccak256("config"); @@ -130,7 +131,7 @@ contract BaseTest is Test { IDelayedWETH(payable(address(delayedWETH))), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, + AggregateVerifier.TeeHashes(TEE_NITRO_IMAGE_HASH, TEE_TDX_IMAGE_HASH), AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, @@ -226,7 +227,7 @@ contract BaseTest is Test { AggregateVerifier.ProofType proofType ) internal - pure + view returns (bytes memory) { return abi.encodePacked(uint8(proofType), _generateProofBody(salt, proofType)); @@ -237,12 +238,21 @@ contract BaseTest is Test { AggregateVerifier.ProofType proofType ) internal - pure + view 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( + TEE_NITRO_IMAGE_HASH, + saltHash, + bytes32(0), + uint8(27), + TEE_TDX_IMAGE_HASH, + saltHash, + bytes32(uint256(1)), + uint8(28) + ); } return abi.encodePacked(salt, bytes32(0), bytes32(0), uint8(27)); } diff --git a/test/L1/proofs/TEEProverRegistry.t.sol b/test/L1/proofs/TEEProverRegistry.t.sol index e4c93e5aa..0a95fbc94 100644 --- a/test/L1/proofs/TEEProverRegistry.t.sol +++ b/test/L1/proofs/TEEProverRegistry.t.sol @@ -16,12 +16,14 @@ import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; -/// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. +/// @notice Mock AggregateVerifier that returns configurable TEE image hashes. contract MockAggregateVerifierForRegistry { - bytes32 public TEE_IMAGE_HASH; + bytes32 public TEE_NITRO_IMAGE_HASH; + bytes32 public TEE_TDX_IMAGE_HASH; constructor(bytes32 imageHash) { - TEE_IMAGE_HASH = imageHash; + TEE_NITRO_IMAGE_HASH = imageHash; + TEE_TDX_IMAGE_HASH = imageHash; } } diff --git a/test/L1/proofs/TEEProverRegistryTDX.t.sol b/test/L1/proofs/TEEProverRegistryTDX.t.sol index 742960312..bc8ad918f 100644 --- a/test/L1/proofs/TEEProverRegistryTDX.t.sol +++ b/test/L1/proofs/TEEProverRegistryTDX.t.sol @@ -17,12 +17,14 @@ import { GameType } from "src/libraries/bridge/Types.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; -/// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. +/// @notice Mock AggregateVerifier that returns configurable TEE image hashes. contract MockAggregateVerifierForTDXRegistry { - bytes32 public TEE_IMAGE_HASH; + bytes32 public TEE_NITRO_IMAGE_HASH; + bytes32 public TEE_TDX_IMAGE_HASH; constructor(bytes32 imageHash) { - TEE_IMAGE_HASH = imageHash; + TEE_NITRO_IMAGE_HASH = imageHash; + TEE_TDX_IMAGE_HASH = imageHash; } } diff --git a/test/L1/proofs/TEEVerifier.t.sol b/test/L1/proofs/TEEVerifier.t.sol index 7ae88b9bf..df7788e7b 100644 --- a/test/L1/proofs/TEEVerifier.t.sol +++ b/test/L1/proofs/TEEVerifier.t.sol @@ -18,12 +18,14 @@ import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; -/// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. +/// @notice Mock AggregateVerifier that returns configurable TEE image hashes. contract MockAggregateVerifierForVerifier { - bytes32 public TEE_IMAGE_HASH; + bytes32 public TEE_NITRO_IMAGE_HASH; + bytes32 public TEE_TDX_IMAGE_HASH; - constructor(bytes32 imageHash) { - TEE_IMAGE_HASH = imageHash; + constructor(bytes32 nitroImageHash, bytes32 tdxImageHash) { + TEE_NITRO_IMAGE_HASH = nitroImageHash; + TEE_TDX_IMAGE_HASH = tdxImageHash; } } @@ -53,7 +55,8 @@ contract TEEVerifierTest is Test { address internal nitroSignerAddress; address internal tdxSignerAddress; - bytes32 internal constant IMAGE_ID = keccak256("test-image-id"); + bytes32 internal constant NITRO_IMAGE_ID = keccak256("test-nitro-image-id"); + bytes32 internal constant TDX_IMAGE_ID = keccak256("test-tdx-image-id"); uint32 internal constant TEST_GAME_TYPE = 621; address internal immutable PROPOSER = makeAddr("proposer"); @@ -66,7 +69,8 @@ contract TEEVerifierTest is Test { tdxSignerAddress = vm.addr(TDX_SIGNER_PRIVATE_KEY); // Deploy mock factory and verifier - MockAggregateVerifierForVerifier mockVerifier = new MockAggregateVerifierForVerifier(IMAGE_ID); + MockAggregateVerifierForVerifier mockVerifier = + new MockAggregateVerifierForVerifier(NITRO_IMAGE_ID, TDX_IMAGE_ID); MockDisputeGameFactoryForVerifier mockFactory = new MockDisputeGameFactoryForVerifier(); mockFactory.setImpl(TEST_GAME_TYPE, address(mockVerifier)); @@ -90,8 +94,8 @@ contract TEEVerifierTest is Test { teeProverRegistry = DevTEEProverRegistry(address(proxy)); // Register one Nitro signer and one TDX signer with the correct image hash. - teeProverRegistry.addDevSigner(nitroSignerAddress, IMAGE_ID); - teeProverRegistry.addDevTDXSigner(tdxSignerAddress, IMAGE_ID); + teeProverRegistry.addDevSigner(nitroSignerAddress, NITRO_IMAGE_ID); + teeProverRegistry.addDevTDXSigner(tdxSignerAddress, TDX_IMAGE_ID); // Set the proposer as valid teeProverRegistry.setProposer(PROPOSER, true); @@ -103,10 +107,17 @@ contract TEEVerifierTest is Test { ); } - function testVerifyValidSignatures() public view { + function testVerifyValidNitroSignature() public view { bytes32 journal = keccak256("test-journal"); - bytes memory proofBytes = _proofBytes(journal); - bool result = verifier.verify(proofBytes, IMAGE_ID, journal); + bytes memory proofBytes = _proofBytes(journal, NITRO_SIGNER_PRIVATE_KEY); + bool result = verifier.verify(proofBytes, NITRO_IMAGE_ID, journal); + assertTrue(result); + } + + function testVerifyValidTDXSignature() public view { + bytes32 journal = keccak256("test-journal"); + bytes memory proofBytes = _proofBytes(journal, TDX_SIGNER_PRIVATE_KEY); + bool result = verifier.verify(proofBytes, TDX_IMAGE_ID, journal); assertTrue(result); } @@ -115,20 +126,18 @@ contract TEEVerifierTest is Test { bytes memory invalidSignature = new bytes(65); invalidSignature[64] = bytes1(uint8(27)); // Set v to 27 - bytes memory proofBytes = _buildProof(PROPOSER, invalidSignature, _signature(TDX_SIGNER_PRIVATE_KEY, journal)); + bytes memory proofBytes = _buildProof(PROPOSER, invalidSignature); vm.expectRevert(TEEVerifier.InvalidSignature.selector); - verifier.verify(proofBytes, IMAGE_ID, journal); + verifier.verify(proofBytes, NITRO_IMAGE_ID, journal); } function testVerifyFailsWithInvalidProposer() public { bytes32 journal = keccak256("test-journal"); - bytes memory proofBytes = _buildProof( - address(0), _signature(NITRO_SIGNER_PRIVATE_KEY, journal), _signature(TDX_SIGNER_PRIVATE_KEY, journal) - ); + bytes memory proofBytes = _buildProof(address(0), _signature(NITRO_SIGNER_PRIVATE_KEY, journal)); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidProposer.selector, address(0))); - verifier.verify(proofBytes, IMAGE_ID, journal); + verifier.verify(proofBytes, NITRO_IMAGE_ID, journal); } function testVerifyFailsWithUnregisteredSigner() public { @@ -138,58 +147,28 @@ contract TEEVerifierTest is Test { bytes32 journal = keccak256("test-journal"); - bytes memory proofBytes = - _buildProof(PROPOSER, _signature(unregisteredKey, journal), _signature(TDX_SIGNER_PRIVATE_KEY, journal)); + bytes memory proofBytes = _buildProof(PROPOSER, _signature(unregisteredKey, journal)); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); - verifier.verify(proofBytes, IMAGE_ID, journal); + verifier.verify(proofBytes, NITRO_IMAGE_ID, journal); } function testVerifyFailsWithImageIdMismatch() public { bytes32 journal = keccak256("test-journal"); - bytes memory proofBytes = _proofBytes(journal); - // Different imageId should fail — signer was registered with IMAGE_ID + // Different imageId should fail — Nitro signer was registered with NITRO_IMAGE_ID bytes32 wrongImageId = keccak256("different-image"); - vm.expectRevert(abi.encodeWithSelector(TEEVerifier.ImageIdMismatch.selector, IMAGE_ID, wrongImageId)); + bytes memory proofBytes = _proofBytes(journal, NITRO_SIGNER_PRIVATE_KEY); + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.ImageIdMismatch.selector, NITRO_IMAGE_ID, wrongImageId)); verifier.verify(proofBytes, wrongImageId, journal); } - function testVerifyFailsWithDuplicateSigner() public { + function testVerifyFailsWhenTEETypeIsIncludedInProof() 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 = abi.encodePacked(PROPOSER, uint8(3), _signature(NITRO_SIGNER_PRIVATE_KEY, journal)); - 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); + vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); + verifier.verify(proofBytes, NITRO_IMAGE_ID, journal); } function testVerifyFailsWithInvalidProofFormat() public { @@ -199,17 +178,15 @@ contract TEEVerifierTest is Test { bytes memory shortProof = new bytes(50); vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); - verifier.verify(shortProof, IMAGE_ID, journal); + verifier.verify(shortProof, NITRO_IMAGE_ID, journal); } 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 _proofBytes(bytes32 journal, uint256 signerPrivateKey) internal view returns (bytes memory) { + return _buildProof(PROPOSER, _signature(signerPrivateKey, journal)); } function _signature(uint256 privateKey, bytes32 journal) internal pure returns (bytes memory) { @@ -217,15 +194,7 @@ contract TEEVerifierTest is Test { 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); + function _buildProof(address proposer, bytes memory signature) internal pure returns (bytes memory) { + return abi.encodePacked(proposer, signature); } } diff --git a/test/mocks/MockDevTEEProverRegistry.sol b/test/mocks/MockDevTEEProverRegistry.sol index f4c6ed10b..66bff358c 100644 --- a/test/mocks/MockDevTEEProverRegistry.sol +++ b/test/mocks/MockDevTEEProverRegistry.sol @@ -23,7 +23,7 @@ contract DevTEEProverRegistry is TEEProverRegistry { /// @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. + /// the current AggregateVerifier's TEE_NITRO_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 { diff --git a/test/opcm/DeployImplementations.t.sol b/test/opcm/DeployImplementations.t.sol index d18cd349c..17313846d 100644 --- a/test/opcm/DeployImplementations.t.sol +++ b/test/opcm/DeployImplementations.t.sol @@ -174,7 +174,8 @@ contract DeployImplementations_Test is Test, FeatureFlags { _faultGameV2SplitDepth, // faultGameV2SplitDepth (bounded) _faultGameV2ClockExtension, // faultGameV2ClockExtension (bounded) _faultGameV2MaxClockDuration, // faultGameV2MaxClockDuration (bounded) - bytes32(uint256(1)), // teeImageHash + bytes32(uint256(1)), // teeNitroImageHash + bytes32(uint256(2)), // teeTdxImageHash bytes32(0), // multiproofConfigHash 621, // multiproofGameType address(0), // nitroEnclaveVerifier @@ -404,7 +405,8 @@ contract DeployImplementations_Test is Test, FeatureFlags { 30, // faultGameV2SplitDepth 10800, // faultGameV2ClockExtension 302400, // faultGameV2MaxClockDuration - bytes32(uint256(1)), // teeImageHash + bytes32(uint256(1)), // teeNitroImageHash + bytes32(uint256(2)), // teeTdxImageHash bytes32(0), // multiproofConfigHash 621, // multiproofGameType address(0), // nitroEnclaveVerifier diff --git a/test/opcm/DeployOPChain.t.sol b/test/opcm/DeployOPChain.t.sol index 19a89a3f8..43d82faf1 100644 --- a/test/opcm/DeployOPChain.t.sol +++ b/test/opcm/DeployOPChain.t.sol @@ -87,7 +87,8 @@ contract DeployOPChain_TestBase is Test, FeatureFlags { faultGameV2SplitDepth: 30, faultGameV2ClockExtension: 10800, faultGameV2MaxClockDuration: 302400, - teeImageHash: bytes32(uint256(1)), + teeNitroImageHash: bytes32(uint256(1)), + teeTdxImageHash: bytes32(uint256(2)), multiproofConfigHash: bytes32(0), multiproofGameType: 621, nitroEnclaveVerifier: address(0), From 993bb6526c6620712105d8016cc845001b0df69c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Wed, 6 May 2026 13:01:03 -0400 Subject: [PATCH 16/20] fix semver --- snapshots/semver-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index ba4014842..01ccf8ab8 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -37,7 +37,7 @@ }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { "initCodeHash": "0x6e50ad8219dfc59503395bf63d2a4d91c002bd21a1df7c902debedea9c2e13eb", - "sourceCodeHash": "0x5708e88bac4268a2c1310c3b9aa84335310ae6941a90e41bc10212f356b2060a" + "sourceCodeHash": "0x802a3645c05ae98232b091f779f6321f60230dd22f536b00dcbacccde6af4d3a" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", @@ -61,11 +61,11 @@ }, "src/L1/proofs/tee/TEEProverRegistry.sol:TEEProverRegistry": { "initCodeHash": "0x4b2fb8ba42668b1b9a8ebb99518fc0552703dfda190acde45d771847b450ac65", - "sourceCodeHash": "0xd14971b269de818bcceac3493284196dd0168fad3d21315db965d6d05a2cc050" + "sourceCodeHash": "0x3e763a25f5353756c2ab52f6ae003f8cc7c1da2965aa2fd8b6ec6a0ac0ae6720" }, "src/L1/proofs/tee/TEEVerifier.sol:TEEVerifier": { "initCodeHash": "0x5f42455c6d41c0a49e05681a6fa7dbf21eb22c89705fd11b6030952b3227e559", - "sourceCodeHash": "0x83a09b025e33838859a7b5848463bb7f4d6d76031f5b8b8bc9f3cd5e73ad4464" + "sourceCodeHash": "0xcd8358734ff27b0c17af73713b7f3d2b0f348ba0646efc356345bad9fa8d6a8c" }, "src/L1/proofs/v2/FaultDisputeGameV2.sol:FaultDisputeGameV2": { "initCodeHash": "0x6fc59e2da083c9e2093e42b0fda705e8215cc216e4dcedbf728c08f69ec2d3bd", @@ -163,4 +163,4 @@ "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } -} +} \ No newline at end of file From 72ce71e22ce7ef92b9d3e7ec5e3afb427257ff64 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Wed, 6 May 2026 16:40:16 -0400 Subject: [PATCH 17/20] another deployment --- deploy-config/sepolia.json | 5 ++--- deployments/11155111-dev-with-tdx.json | 16 ++++++++-------- scripts/multiproof/justfile | 2 -- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index a21340ed4..ceb331ae1 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -58,9 +58,8 @@ "useFaultProofs": true, "useRevenueShare": true, "chainFeesRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", - "teeImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", - "teeNitroImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", - "teeTdxImageHash": "0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6", + "teeNitroImageHash": "0x11fb64617dfa2875d31b0cfb656666fd8cee65eb134fefeca171b9b6b4444a64", + "teeTdxImageHash": "0x4cb35ee476a8098c4e567098714c65f5afe25236fc460b38487a356e14e7db66", "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360", "multiproofGameType": 621, "teeProposer": "0xdb84125f2f4229c81c579f41bc129c71b174eb58", diff --git a/deployments/11155111-dev-with-tdx.json b/deployments/11155111-dev-with-tdx.json index 1c90ea2b9..6dc233488 100644 --- a/deployments/11155111-dev-with-tdx.json +++ b/deployments/11155111-dev-with-tdx.json @@ -1,13 +1,13 @@ { - "ASRStartingBlockNumber": 41154575, - "ASRStartingOutputRoot": "0x2cb991c0d9e739677fbcc78772c01f2e4639dffb159422b08cfbea1160f8877c", - "AggregateVerifier": "0x4Cf26dE13cC4D64352f6c76b8ef888bEA6a2Acef", - "AnchorStateRegistry": "0x629cf75322989cE321e10BfA4DF2daE43ac19c82", - "DelayedWETH": "0x17626e33878597cbbFc877c9AA47945E64eB6D31", - "DisputeGameFactory": "0x6C9De6Cc269844c3121B2b59F239F79bace2303D", + "ASRStartingBlockNumber": 41162517, + "ASRStartingOutputRoot": "0xb7b34000df977c594da02d331a54c31015b4255998956051bbbcce03e42e9f88", + "AggregateVerifier": "0xfc9D070464370BD6B1a99413f83b574769b0c0B7", + "AnchorStateRegistry": "0xEDA113F9DE36455f8BeCBb2301BCac3CcFFd5D24", + "DelayedWETH": "0xc947c5F371Da8A18CC4374F67775A693dea7fb10", + "DisputeGameFactory": "0xe07A57058b1e7DbD31b2BE1C22476Fbe3eF2B050", "NitroEnclaveVerifier": "0x77461a6434fFE3435206B19658F33274f3104e07", "TDXRegistrationManager": "0x44E999A5859c2D12378a349882fAe5805DCE71b9", "TDXVerifier": "0x9c38b756B31529Cad02D54A4ba810F3ff1e00054", - "TEEProverRegistry": "0x52F8f01Fb5e2cF6BE15a311EbFB30f24352C7221", - "TEEVerifier": "0xf218d04E5A582deEA9E952E4227C07e865BC8bA1" + "TEEProverRegistry": "0xFFABA937771b13F9fCF018C68Dd877826c8F2762", + "TEEVerifier": "0xC7c3a7822DEa94Dde1EBEe567918047dAE305d08" } \ No newline at end of file diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile index 4d69f21a6..24d2fc4e2 100644 --- a/scripts/multiproof/justfile +++ b/scripts/multiproof/justfile @@ -11,7 +11,6 @@ l2_output_root_rpc_url := env_var_or_default("L2_OUTPUT_ROOT_RPC_URL", "https:// sepolia_risc0_verifier_router := "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" sepolia_tdx_verifier_id := "0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b" sepolia_intel_root_ca_hash := "0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009" -sepolia_tdx_image_hash := "0x4cb35ee476a8098c4e567098714c65f5afe25236fc460b38487a356e14e7db66" sepolia_tdx_registration_manager := "0x44E999A5859c2D12378a349882fAe5805DCE71b9" # List available multiproof deployment recipes. @@ -50,7 +49,6 @@ 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_REGISTRATION_MANAGER={{sepolia_tdx_registration_manager}}" @echo "L2_RPC_URL={{l2_rpc_url}}" @echo "L2_OUTPUT_ROOT_RPC_URL={{l2_output_root_rpc_url}}" From 9d6bf3cf5839e40f0d454e4996d80d8119d909e1 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Wed, 6 May 2026 21:47:40 -0400 Subject: [PATCH 18/20] add helper command --- scripts/multiproof/justfile | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile index 24d2fc4e2..934437b25 100644 --- a/scripts/multiproof/justfile +++ b/scripts/multiproof/justfile @@ -3,6 +3,7 @@ set dotenv-load repo_root := justfile_directory() + "/../.." justfile_path := justfile_directory() + "/justfile" default_deploy_config := "deploy-config/sepolia.json" +sepolia_tdx_stack_deployments := "deployments/11155111-dev-with-tdx.json" forge_account := "testnet-admin" l1_rpc_url := env_var_or_default("L1_RPC_URL", "https://ethereum-full-sepolia-k8s-dev.cbhq.net") l2_rpc_url := env_var_or_default("L2_RPC_URL", "https://base-sepolia-archive-k8s-dev.cbhq.net:8545") @@ -46,6 +47,7 @@ deploy-tdx-verifier risc0_router=sepolia_risc0_verifier_router tdx_verifier_id=s # Print the Sepolia TDX constants used by these recipes. tdx-sepolia-config: @echo "L1_RPC_URL={{l1_rpc_url}}" + @jq -r '"TEE_PROVER_REGISTRY=" + (.TEEProverRegistry // "")' "{{repo_root}}/{{sepolia_tdx_stack_deployments}}" @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}}" @@ -112,6 +114,58 @@ deploy-tdx-stack tdx_verifier tdx_manager=sepolia_tdx_registration_manager deplo --account "{{forge_account}}" \ --sender "$owner" +# Args: +# registry TEEProverRegistry address. Defaults to deployments/11155111-dev-with-tdx.json. +# rpc L1 Sepolia RPC URL. +# +# Query currently registered TEE signer addresses on Sepolia. +tee-registered-signers registry="" rpc=l1_rpc_url: + #!/usr/bin/env bash + set -euo pipefail + cd "{{repo_root}}" + + registry="{{registry}}" + if [[ -z "$registry" ]]; then + registry="$(jq -r '.TEEProverRegistry // empty' "{{sepolia_tdx_stack_deployments}}")" + fi + + if [[ -z "$registry" || "$registry" == "null" ]]; then + echo "Missing TEEProverRegistry address. Pass one explicitly or update {{sepolia_tdx_stack_deployments}}." >&2 + exit 1 + fi + + echo "TEEProverRegistry: $registry" + echo "RPC: {{rpc}}" + + signers=() + while IFS= read -r signer; do + signers+=("$signer") + done < <(cast call "$registry" 'getRegisteredSigners()(address[])' --rpc-url "{{rpc}}" --json | jq -r '.[0][]') + if (( ${#signers[@]} == 0 )); then + echo "No registered TEE signers found." + exit 0 + fi + + printf "%-42s %-6s %-66s %-5s\n" "SIGNER" "TYPE" "IMAGE_HASH" "VALID" + for signer in "${signers[@]}"; do + tee_type_id="$(cast call "$registry" 'signerTEEType(address)(uint8)' "$signer" --rpc-url "{{rpc}}" --json | jq -r '.[0]')" + image_hash="$(cast call "$registry" 'signerImageHash(address)(bytes32)' "$signer" --rpc-url "{{rpc}}" --json | jq -r '.[0]')" + valid_raw="$(cast call "$registry" 'isValidSigner(address)(bool)' "$signer" --rpc-url "{{rpc}}" --json 2>/dev/null || true)" + if [[ -n "$valid_raw" ]]; then + valid="$(echo "$valid_raw" | jq -r '.[0]')" + else + valid="unknown" + fi + + case "$tee_type_id" in + 1) tee_type="NITRO" ;; + 2) tee_type="TDX" ;; + *) tee_type="NONE" ;; + esac + + printf "%-42s %-6s %-66s %-5s\n" "$signer" "$tee_type" "$image_hash" "$valid" + done + # Print the two-step Sepolia command template. tdx-sepolia-help: @echo 'export L1_RPC_URL=' @@ -119,4 +173,5 @@ tdx-sepolia-help: @echo 'export L2_OUTPUT_ROOT_RPC_URL=' @echo 'just deploy-tdx-verifier' @echo 'just deploy-tdx-stack [tdx-manager] [deploy-config] [l2-rpc] [l2-output-root-rpc]' + @echo 'just tee-registered-signers' @echo 'just tdx-sepolia-config' From c4c830e3a92d63de04df0357a62c81281d14429c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Thu, 7 May 2026 12:55:27 -0400 Subject: [PATCH 19/20] fix tdx deploy script --- scripts/multiproof/DeployDevWithTDX.s.sol | 4 +++- scripts/multiproof/README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index 98b101983..ad7f3cd5f 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -8,6 +8,7 @@ pragma solidity 0.8.15; * 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 NitroEnclaveVerifier in the deploy config must also already be deployed. * The default run(address) entrypoint configures DEFAULT_TDX_REGISTRATION_MANAGER * as the registry manager so it can submit TDX signer registrations. */ @@ -102,7 +103,7 @@ contract DeployDevWithTDX is Script { console.log("ASR Starting Output Root:", vm.toString(startingAnchorRoot.raw())); console.log("ASR Starting L2 Block:", startingAnchorBlockNumber); console.log(""); - console.log("NOTE: TDXVerifier owner must be the broadcaster/finalSystemOwner."); + console.log("NOTE: TDXVerifier and NitroEnclaveVerifier owners must be the broadcaster/finalSystemOwner."); vm.startBroadcast(); @@ -139,6 +140,7 @@ contract DeployDevWithTDX is Script { teeProverRegistryProxy = address(registryProxy); ITDXVerifier(tdxVerifierAddr).setProofSubmitter(teeProverRegistryProxy); + INitroEnclaveVerifier(nitroEnclaveVerifierAddr).setProofSubmitter(teeProverRegistryProxy); teeVerifier = address(new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), mockAnchorRegistry)); } diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 13841b32f..b7794a936 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -215,7 +215,7 @@ The script saves output to `deployments/-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`. +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 both `TDXVerifier.proofSubmitter` and `NitroEnclaveVerifier.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. From aa612e147b3a28aae24490bc5622e2339205dd5b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 9 May 2026 09:14:42 -0400 Subject: [PATCH 20/20] update dev deployment --- deploy-config/zeronet-tdx.json | 78 +++++ deployments/11155111-dev-with-tdx.json | 20 +- deployments/11155111-tee-verifiers.json | 17 + scripts/multiproof/DeployDevWithNitro.s.sol | 10 +- scripts/multiproof/DeployDevWithTDX.s.sol | 19 +- scripts/multiproof/DeployNitroVerifier.s.sol | 201 ++++++++++++ scripts/multiproof/DeployTEEVerifiers.s.sol | 320 +++++++++++++++++++ scripts/multiproof/README.md | 71 +++- scripts/multiproof/justfile | 165 +++++++++- 9 files changed, 868 insertions(+), 33 deletions(-) create mode 100644 deploy-config/zeronet-tdx.json create mode 100644 deployments/11155111-tee-verifiers.json create mode 100644 scripts/multiproof/DeployNitroVerifier.s.sol create mode 100644 scripts/multiproof/DeployTEEVerifiers.s.sol diff --git a/deploy-config/zeronet-tdx.json b/deploy-config/zeronet-tdx.json new file mode 100644 index 000000000..3aad31a1e --- /dev/null +++ b/deploy-config/zeronet-tdx.json @@ -0,0 +1,78 @@ +{ + "finalSystemOwner": "0x8C1a617BdB47342F9C17Ac8750E0b070c372C721", + "superchainConfigGuardian": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "l1StartingBlockTag": "0x48f520cf4ddaf34c8336e6e490632ea3cf1e5e93b0b2bc6e917557e31845371b", + "l1ChainID": 11155111, + "l2ChainID": 11155420, + "l2BlockTime": 2, + "l1BlockTime": 12, + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + "p2pSequencerAddress": "0x57CACBB0d30b01eb2462e5dC940c161aff3230D3", + "batchInboxAddress": "0xff00000000000000000000000000000011155420", + "batchSenderAddress": "0x8F23BB38F531600e5d8FDDaAEC41F13FaB46E98c", + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": 1690493568, + "l2OutputOracleProposer": "0x49277EE36A024120Ee218127354c4a3591dc90A9", + "l2OutputOracleChallenger": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "finalizationPeriodSeconds": 12, + "proxyAdminOwner": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "baseFeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "l1FeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "sequencerFeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "operatorFeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + "operatorFeeVaultWithdrawalNetwork": 0, + "enableGovernance": true, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "eip1559Denominator": 50, + "eip1559Elasticity": 6, + "l2GenesisRegolithTimeOffset": "0x0", + "systemConfigStartBlock": 4071248, + "fundDevAccounts": false, + "faultGameAbsolutePrestate": "0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b", + "faultGameMaxDepth": 73, + "faultGameClockExtension": 10800, + "faultGameMaxClockDuration": 302400, + "faultGameGenesisBlock": 9496192, + "faultGameGenesisOutputRoot": "0x63b1cda487c072b020a57c1203f7c2921754005cadbd54bed7f558111b8278d8", + "faultGameSplitDepth": 30, + "faultGameWithdrawalDelay": 604800, + "preimageOracleMinProposalSize": 126000, + "preimageOracleChallengePeriod": 86400, + "proofMaturityDelaySeconds": 604800, + "disputeGameFinalityDelaySeconds": 302400, + "respectedGameType": 0, + "useFaultProofs": true, + "useRevenueShare": true, + "chainFeesRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "teeNitroImageHash": "0x213ad2e8f1594e1c386ef88ed9d2ec0df5a968f5228a5b17f4b0040599726515", + "teeTdxImageHash": "0x4cb35ee476a8098c4e567098714c65f5afe25236fc460b38487a356e14e7db66", + "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360", + "multiproofGameType": 621, + "teeProposer": "0xdb84125f2f4229c81c579f41bc129c71b174eb58", + "teeChallenger": "0xadc09b63a3ac57a2ce86d946617a18df9db029a1", + "zkRangeHash": "0x51708a6b4a3b800a14607e902c1aad47719905c12698a3e01ca8b3321761bc52", + "zkAggregationHash": "0x005aa369dd9445e172018a9eaa4a0f9767b2c2079ece90ca120422b3c4c65f11", + "nitroEnclaveVerifier": "0x2DC52760D13a3C2dF33fcc42913C4dddd8d976B9", + "tdxVerifier": "0xB5F7f92dA7aBfDBDEB8e84EE78765fd0D3D3E092", + "multiproofGenesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79", + "multiproofGenesisBlockNumber": 37223829, + "multiproofBlockInterval": 600, + "multiproofIntermediateBlockInterval": 30, + "risc0VerifierRouter": "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187", + "risc0SetBuilderImageId": "0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9", + "sp1Verifier": "0x397A5f7f3dBd538f23DE225B51f532c34448dA9B" +} diff --git a/deployments/11155111-dev-with-tdx.json b/deployments/11155111-dev-with-tdx.json index 6dc233488..17d4be0a7 100644 --- a/deployments/11155111-dev-with-tdx.json +++ b/deployments/11155111-dev-with-tdx.json @@ -1,13 +1,13 @@ { - "ASRStartingBlockNumber": 41162517, - "ASRStartingOutputRoot": "0xb7b34000df977c594da02d331a54c31015b4255998956051bbbcce03e42e9f88", - "AggregateVerifier": "0xfc9D070464370BD6B1a99413f83b574769b0c0B7", - "AnchorStateRegistry": "0xEDA113F9DE36455f8BeCBb2301BCac3CcFFd5D24", - "DelayedWETH": "0xc947c5F371Da8A18CC4374F67775A693dea7fb10", - "DisputeGameFactory": "0xe07A57058b1e7DbD31b2BE1C22476Fbe3eF2B050", - "NitroEnclaveVerifier": "0x77461a6434fFE3435206B19658F33274f3104e07", + "ASRStartingBlockNumber": 41281684, + "ASRStartingOutputRoot": "0xc9f0f3a76f7c15306635a9990a8d4a3c1e281ca9e1519824679a73ca781f3404", + "AggregateVerifier": "0xA1Fd6E923d8Efa8deebde9D2AE98Df08B9A31C7d", + "AnchorStateRegistry": "0xe3E9ec1a72D8AD322187B4a4B757B2e906f4edF8", + "DelayedWETH": "0x2CCe62a3496aC7bD1A706cf7E1a765fd24e59666", + "DisputeGameFactory": "0x4aae21Fc11669463d37d2B875B52c7D63F794a08", + "NitroEnclaveVerifier": "0x2DC52760D13a3C2dF33fcc42913C4dddd8d976B9", "TDXRegistrationManager": "0x44E999A5859c2D12378a349882fAe5805DCE71b9", - "TDXVerifier": "0x9c38b756B31529Cad02D54A4ba810F3ff1e00054", - "TEEProverRegistry": "0xFFABA937771b13F9fCF018C68Dd877826c8F2762", - "TEEVerifier": "0xC7c3a7822DEa94Dde1EBEe567918047dAE305d08" + "TDXVerifier": "0xB5F7f92dA7aBfDBDEB8e84EE78765fd0D3D3E092", + "TEEProverRegistry": "0x45111D4FDF5EC6bD2bDBF9Aed4a5f01140B892D9", + "TEEVerifier": "0x3065B3175F0265212e041Ea119D0001cf751E5a7" } \ No newline at end of file diff --git a/deployments/11155111-tee-verifiers.json b/deployments/11155111-tee-verifiers.json new file mode 100644 index 000000000..ebe25da12 --- /dev/null +++ b/deployments/11155111-tee-verifiers.json @@ -0,0 +1,17 @@ +{ + "IntelRootCaHash": "0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009", + "NitroEnclaveVerifier": "0x2DC52760D13a3C2dF33fcc42913C4dddd8d976B9", + "NitroMaxTimeDiff": 3600, + "NitroRiscZeroVerifierRouter": "0xB121B667dd2cf27F95f9F5107137696F56f188f6", + "NitroRootCert": "0x641a0321a3e244efe456463195d606317ed7cdcc3c1756e09893f3c68f79bb5b", + "NitroVerifierId": "0x15051db631d6ed382d957c795a558a0abdd00d0d22a1670455721bc2712d3d6e", + "NitroVerifierProofId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "RiscZeroSetBuilderImageId": "0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9", + "RiscZeroSetVerifier": "0xef981185595Ed2a49dEC9B2D5073B88Ef9e00Fd4", + "RiscZeroSetVerifierSelector": "0x242f9d5b00000000000000000000000000000000000000000000000000000000", + "RiscZeroVerifierRouter": "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187", + "TDXMaxTimeDiff": 3600, + "TDXRiscZeroVerifierRouter": "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187", + "TDXVerifier": "0xB5F7f92dA7aBfDBDEB8e84EE78765fd0D3D3E092", + "TDXVerifierId": "0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b" +} \ No newline at end of file diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index c1eef99b7..492026b63 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -14,8 +14,8 @@ pragma solidity 0.8.15; * You cannot use addDevSigner() - you must go through the full registerSigner() flow. * * PREREQUISITES: - * 1. Deploy the RISC Zero verifier stack AND NitroEnclaveVerifier using - * DeployRiscZeroStack.s.sol (required because NitroEnclaveVerifier and its + * 1. Deploy the RISC Zero verifier route AND NitroEnclaveVerifier using + * DeployNitroVerifier.s.sol (required because NitroEnclaveVerifier and its * dependencies need Solidity ^0.8.20, while this script is pinned to =0.8.15). * 2. Set `nitroEnclaveVerifier` in the deploy config to the deployed address. * @@ -65,7 +65,7 @@ import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; /// @title DeployDevWithNitro /// @notice Development deployment WITH AWS Nitro attestation validation. /// @dev Uses real TEEProverRegistry which requires registerSigner() with valid attestation. -/// NitroEnclaveVerifier must be pre-deployed via DeployRiscZeroStack.s.sol. +/// NitroEnclaveVerifier must be pre-deployed via DeployNitroVerifier.s.sol. contract DeployDevWithNitro is Script { uint256 public constant BLOCK_INTERVAL = 600; uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30; @@ -91,12 +91,12 @@ contract DeployDevWithNitro is Script { function run() public { GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); - // NitroEnclaveVerifier must be pre-deployed (via DeployRiscZeroStack.s.sol) + // NitroEnclaveVerifier must be pre-deployed (via DeployNitroVerifier.s.sol) nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier(); tdxVerifierAddr = cfg.tdxVerifier(); require( nitroEnclaveVerifierAddr != address(0), - "nitroEnclaveVerifier must be set in config (deploy via DeployRiscZeroStack.s.sol first)" + "nitroEnclaveVerifier must be set in config (deploy via DeployNitroVerifier.s.sol first)" ); require(tdxVerifierAddr != address(0), "tdxVerifier must be set in config"); diff --git a/scripts/multiproof/DeployDevWithTDX.s.sol b/scripts/multiproof/DeployDevWithTDX.s.sol index ad7f3cd5f..e600c33c6 100644 --- a/scripts/multiproof/DeployDevWithTDX.s.sol +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -8,7 +8,8 @@ pragma solidity 0.8.15; * 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 NitroEnclaveVerifier in the deploy config must also already be deployed. + * The NitroEnclaveVerifier can either come from the deploy config or be passed + * explicitly to the five-argument run() overload. * The default run(address) entrypoint configures DEFAULT_TDX_REGISTRATION_MANAGER * as the registry manager so it can submit TDX signer registrations. */ @@ -79,9 +80,21 @@ contract DeployDevWithTDX is Script { ) public { - nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier(); + run(cfg.nitroEnclaveVerifier(), tdxVerifier, registrationManager, asrStartingOutputRoot, asrStartingBlockNumber); + } + + function run( + address nitroEnclaveVerifier, + address tdxVerifier, + address registrationManager, + bytes32 asrStartingOutputRoot, + uint256 asrStartingBlockNumber + ) + public + { + nitroEnclaveVerifierAddr = nitroEnclaveVerifier; require(tdxVerifier != address(0), "tdxVerifier must be non-zero"); - require(nitroEnclaveVerifierAddr != address(0), "nitroEnclaveVerifier must be set in config"); + require(nitroEnclaveVerifierAddr != address(0), "nitroEnclaveVerifier must be non-zero"); require(registrationManager != address(0), "registrationManager must be non-zero"); require(asrStartingOutputRoot != bytes32(0), "asrStartingOutputRoot must be non-zero"); tdxVerifierAddr = tdxVerifier; diff --git a/scripts/multiproof/DeployNitroVerifier.s.sol b/scripts/multiproof/DeployNitroVerifier.s.sol new file mode 100644 index 000000000..f84289df6 --- /dev/null +++ b/scripts/multiproof/DeployNitroVerifier.s.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @title DeployNitroVerifier + * @notice Deploys the RISC Zero set verifier route and NitroEnclaveVerifier used by TEEProverRegistry. + * + * This script is separated from the main multiproof deployment scripts because + * NitroEnclaveVerifier 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/DeployNitroVerifier.s.sol:DeployNitroVerifier \ + * --sig "run(address,address,bytes32,bytes32,bytes32)" \ + * \ + * \ + * --rpc-url --broadcast --private-key + * + * If using batched Nitro proofs, use the overload that also supplies + * : + * + * --sig "run(address,address,bytes32,bytes32,bytes32,bytes32)" + * + * The broadcaster must be the owner because this script calls addVerifyRoute() + * on the freshly deployed NitroEnclaveVerifier. + * + * After running DeployDevWithTDX.s.sol, the Nitro 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 { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import { RiscZeroSetVerifier, RiscZeroSetVerifierLib } from "lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol"; + +import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { NitroEnclaveVerifier } from "src/L1/proofs/tee/NitroEnclaveVerifier.sol"; + +contract DeployNitroVerifier is Script { + /// @notice Maximum Nitro attestation age accepted by NitroEnclaveVerifier. + uint64 internal constant NITRO_MAX_TIME_DIFF = 3600; + + address public setVerifier; + address public nitroEnclaveVerifier; + + /// @param owner Owner for NitroEnclaveVerifier. Must be the broadcaster for route setup. + /// @param risc0VerifierRouter Existing RISC Zero verifier router. + /// @param setBuilderImageId RISC Zero set builder image ID. + /// @param nitroRootCert SHA-256 hash of the AWS Nitro root certificate. + /// @param nitroVerifierId RISC Zero image ID for the Nitro attestation verifier guest. + function run( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId + ) + public + { + run(owner, risc0VerifierRouter, setBuilderImageId, nitroRootCert, nitroVerifierId, bytes32(0)); + } + + /// @param nitroVerifierProofId Optional verifier proof ID used by Nitro batch verification. + function run( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId + ) + public + { + bytes4 setVerifierSelector = + _validateInputs(owner, risc0VerifierRouter, setBuilderImageId, nitroRootCert, nitroVerifierId); + + console.log("=== Deploying NitroEnclaveVerifier ==="); + console.log("Owner:", owner); + console.log("RISC Zero Verifier Router:", risc0VerifierRouter); + console.log("Set Builder Image ID:", vm.toString(setBuilderImageId)); + console.log("Set Verifier Selector:", vm.toString(setVerifierSelector)); + console.log("Nitro Root Cert:", vm.toString(nitroRootCert)); + console.log("Nitro Verifier ID:", vm.toString(nitroVerifierId)); + console.log("Nitro Verifier Proof ID:", vm.toString(nitroVerifierProofId)); + console.log("Max Time Diff:", NITRO_MAX_TIME_DIFF); + console.log(""); + console.log("NOTE: proofSubmitter is set to owner as placeholder."); + console.log(" DeployDevWithTDX.s.sol updates it to TEEProverRegistry."); + console.log(""); + + vm.startBroadcast(); + + (setVerifier, nitroEnclaveVerifier) = _deployNitroVerifier( + owner, risc0VerifierRouter, setBuilderImageId, nitroRootCert, nitroVerifierId, nitroVerifierProofId + ); + + vm.stopBroadcast(); + + console.log("RiscZeroSetVerifier:", setVerifier); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); + console.log(""); + console.log(">>> Use this NitroEnclaveVerifier address in the deploy config <<<"); + + _writeOutput( + setVerifier, + nitroEnclaveVerifier, + risc0VerifierRouter, + setBuilderImageId, + setVerifierSelector, + nitroRootCert, + nitroVerifierId, + nitroVerifierProofId + ); + } + + function _deployNitroVerifier( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId + ) + internal + returns (address deployedSetVerifier, address deployedNitroVerifier) + { + deployedSetVerifier = + address(new RiscZeroSetVerifier(IRiscZeroVerifier(risc0VerifierRouter), setBuilderImageId, "")); + + ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ + verifierId: nitroVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter + }); + + NitroEnclaveVerifier verifier = new NitroEnclaveVerifier( + owner, + NITRO_MAX_TIME_DIFF, + new bytes32[](0), + new uint64[](0), + nitroRootCert, + owner, + address(0), + ZkCoProcessorType.RiscZero, + zkConfig, + nitroVerifierProofId + ); + deployedNitroVerifier = address(verifier); + + bytes4 setVerifierSelector = RiscZeroSetVerifierLib.selector(setBuilderImageId); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, setVerifierSelector, deployedSetVerifier); + } + + function _validateInputs( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId + ) + internal + pure + returns (bytes4 setVerifierSelector) + { + require(owner != address(0), "owner must be non-zero"); + require(risc0VerifierRouter != address(0), "risc0VerifierRouter must be non-zero"); + require(setBuilderImageId != bytes32(0), "setBuilderImageId must be non-zero"); + require(nitroRootCert != bytes32(0), "nitroRootCert must be non-zero"); + require(nitroVerifierId != bytes32(0), "nitroVerifierId must be non-zero"); + + setVerifierSelector = RiscZeroSetVerifierLib.selector(setBuilderImageId); + } + + function _writeOutput( + address deployedSetVerifier, + address deployedNitroVerifier, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes4 setVerifierSelector, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId + ) + internal + { + string memory key = "deployment"; + vm.serializeAddress(key, "RiscZeroSetVerifier", deployedSetVerifier); + vm.serializeAddress(key, "NitroEnclaveVerifier", deployedNitroVerifier); + vm.serializeAddress(key, "RiscZeroVerifierRouter", risc0VerifierRouter); + vm.serializeBytes32(key, "RiscZeroSetBuilderImageId", setBuilderImageId); + vm.serializeString(key, "RiscZeroSetVerifierSelector", vm.toString(setVerifierSelector)); + vm.serializeBytes32(key, "NitroRootCert", nitroRootCert); + vm.serializeBytes32(key, "NitroVerifierId", nitroVerifierId); + vm.serializeBytes32(key, "NitroVerifierProofId", nitroVerifierProofId); + string memory json = vm.serializeUint(key, "MaxTimeDiff", NITRO_MAX_TIME_DIFF); + + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-nitro-verifier.json"); + vm.writeJson(json, outPath); + console.log("Deployment saved to:", outPath); + } +} diff --git a/scripts/multiproof/DeployTEEVerifiers.s.sol b/scripts/multiproof/DeployTEEVerifiers.s.sol new file mode 100644 index 000000000..46613dfd2 --- /dev/null +++ b/scripts/multiproof/DeployTEEVerifiers.s.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @title DeployTEEVerifiers + * @notice Deploys NitroEnclaveVerifier and TDXVerifier in one broadcast. + * + * Usage: + * + * forge script scripts/multiproof/DeployTEEVerifiers.s.sol:DeployTEEVerifiers \ + * --sig "run(address,address,bytes32,bytes32,bytes32,bytes32,bytes32)" \ + * \ + * \ + * \ + * --rpc-url --broadcast --private-key + * + * If using batched Nitro proofs, use the overload that also supplies + * : + * + * --sig "run(address,address,bytes32,bytes32,bytes32,bytes32,bytes32,bytes32)" + * + * If Nitro and TDX use different RISC Zero verifier routers, use: + * + * --sig "run(address,address,address,bytes32,bytes32,bytes32,bytes32,bytes32,bytes32)" + * + * The broadcaster must be the owner because this script calls addVerifyRoute() + * on the freshly deployed NitroEnclaveVerifier. Use the longest overload when + * Nitro and TDX proofs use different RISC Zero verifier router contracts. + */ + +import { Script } from "forge-std/Script.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +import { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import { RiscZeroSetVerifier, RiscZeroSetVerifierLib } from "lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol"; + +import { ZkCoProcessorConfig, ZkCoProcessorType } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { TDXTcbStatus } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; +import { NitroEnclaveVerifier } from "src/L1/proofs/tee/NitroEnclaveVerifier.sol"; +import { TDXVerifier } from "src/L1/proofs/tee/TDXVerifier.sol"; + +contract DeployTEEVerifiers is Script { + /// @notice Maximum Nitro attestation age accepted by NitroEnclaveVerifier. + uint64 internal constant NITRO_MAX_TIME_DIFF = 3600; + + /// @notice Maximum TDX quote age accepted by TDXVerifier. + uint64 internal constant TDX_MAX_TIME_DIFF = 3600; + + address public setVerifier; + address public nitroEnclaveVerifier; + address public tdxVerifier; + + function run( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + public + { + run( + owner, + risc0VerifierRouter, + setBuilderImageId, + nitroRootCert, + nitroVerifierId, + bytes32(0), + tdxVerifierId, + intelRootCaHash + ); + } + + function run( + address owner, + address nitroRisc0VerifierRouter, + address tdxRisc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + public + { + run( + owner, + nitroRisc0VerifierRouter, + tdxRisc0VerifierRouter, + setBuilderImageId, + nitroRootCert, + nitroVerifierId, + bytes32(0), + tdxVerifierId, + intelRootCaHash + ); + } + + function run( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + public + { + run( + owner, + risc0VerifierRouter, + risc0VerifierRouter, + setBuilderImageId, + nitroRootCert, + nitroVerifierId, + nitroVerifierProofId, + tdxVerifierId, + intelRootCaHash + ); + } + + function run( + address owner, + address nitroRisc0VerifierRouter, + address tdxRisc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + public + { + bytes4 setVerifierSelector = _validateInputs( + owner, + nitroRisc0VerifierRouter, + tdxRisc0VerifierRouter, + setBuilderImageId, + nitroRootCert, + nitroVerifierId, + tdxVerifierId, + intelRootCaHash + ); + + console.log("=== Deploying TEE Verifiers ==="); + console.log("Owner:", owner); + console.log("Nitro RISC Zero Verifier Router:", nitroRisc0VerifierRouter); + console.log("TDX RISC Zero Verifier Router:", tdxRisc0VerifierRouter); + console.log("Set Builder Image ID:", vm.toString(setBuilderImageId)); + console.log("Set Verifier Selector:", vm.toString(setVerifierSelector)); + console.log("Nitro Root Cert:", vm.toString(nitroRootCert)); + console.log("Nitro Verifier ID:", vm.toString(nitroVerifierId)); + console.log("Nitro Verifier Proof ID:", vm.toString(nitroVerifierProofId)); + console.log("TDX Verifier ID:", vm.toString(tdxVerifierId)); + console.log("Intel Root CA Hash:", vm.toString(intelRootCaHash)); + console.log(""); + console.log("NOTE: proofSubmitter is set to owner as placeholder on both verifiers."); + console.log(" DeployDevWithTDX.s.sol updates both to TEEProverRegistry."); + console.log(""); + + vm.startBroadcast(); + + (setVerifier, nitroEnclaveVerifier) = _deployNitroVerifier( + owner, nitroRisc0VerifierRouter, setBuilderImageId, nitroRootCert, nitroVerifierId, nitroVerifierProofId + ); + tdxVerifier = _deployTDXVerifier(owner, tdxRisc0VerifierRouter, tdxVerifierId, intelRootCaHash); + + vm.stopBroadcast(); + + console.log("RiscZeroSetVerifier:", setVerifier); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); + console.log("TDXVerifier:", tdxVerifier); + console.log(""); + console.log(">>> Use these verifier addresses for the TDX multiproof deployment <<<"); + + _writeOutput( + setVerifier, + nitroEnclaveVerifier, + tdxVerifier, + nitroRisc0VerifierRouter, + tdxRisc0VerifierRouter, + setBuilderImageId, + setVerifierSelector, + nitroRootCert, + nitroVerifierId, + nitroVerifierProofId, + tdxVerifierId, + intelRootCaHash + ); + } + + function _deployNitroVerifier( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId + ) + internal + returns (address deployedSetVerifier, address deployedNitroVerifier) + { + deployedSetVerifier = + address(new RiscZeroSetVerifier(IRiscZeroVerifier(risc0VerifierRouter), setBuilderImageId, "")); + + ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ + verifierId: nitroVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter + }); + + NitroEnclaveVerifier verifier = new NitroEnclaveVerifier( + owner, + NITRO_MAX_TIME_DIFF, + new bytes32[](0), + new uint64[](0), + nitroRootCert, + owner, + address(0), + ZkCoProcessorType.RiscZero, + zkConfig, + nitroVerifierProofId + ); + deployedNitroVerifier = address(verifier); + + bytes4 setVerifierSelector = RiscZeroSetVerifierLib.selector(setBuilderImageId); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, setVerifierSelector, deployedSetVerifier); + } + + function _deployTDXVerifier( + address owner, + address risc0VerifierRouter, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + internal + returns (address deployedTDXVerifier) + { + 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 + }); + + deployedTDXVerifier = address( + new TDXVerifier( + owner, TDX_MAX_TIME_DIFF, intelRootCaHash, owner, ZkCoProcessorType.RiscZero, zkConfig, allowedStatuses + ) + ); + } + + function _validateInputs( + address owner, + address nitroRisc0VerifierRouter, + address tdxRisc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + internal + pure + returns (bytes4 setVerifierSelector) + { + require(owner != address(0), "owner must be non-zero"); + require(nitroRisc0VerifierRouter != address(0), "nitroRisc0VerifierRouter must be non-zero"); + require(tdxRisc0VerifierRouter != address(0), "tdxRisc0VerifierRouter must be non-zero"); + require(setBuilderImageId != bytes32(0), "setBuilderImageId must be non-zero"); + require(nitroRootCert != bytes32(0), "nitroRootCert must be non-zero"); + require(nitroVerifierId != bytes32(0), "nitroVerifierId must be non-zero"); + require(tdxVerifierId != bytes32(0), "tdxVerifierId must be non-zero"); + require(intelRootCaHash != bytes32(0), "intelRootCaHash must be non-zero"); + + setVerifierSelector = RiscZeroSetVerifierLib.selector(setBuilderImageId); + } + + function _writeOutput( + address deployedSetVerifier, + address deployedNitroVerifier, + address deployedTDXVerifier, + address nitroRisc0VerifierRouter, + address tdxRisc0VerifierRouter, + bytes32 setBuilderImageId, + bytes4 setVerifierSelector, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + bytes32 nitroVerifierProofId, + bytes32 tdxVerifierId, + bytes32 intelRootCaHash + ) + internal + { + string memory key = "deployment"; + vm.serializeAddress(key, "RiscZeroSetVerifier", deployedSetVerifier); + vm.serializeAddress(key, "NitroEnclaveVerifier", deployedNitroVerifier); + vm.serializeAddress(key, "TDXVerifier", deployedTDXVerifier); + vm.serializeAddress(key, "RiscZeroVerifierRouter", tdxRisc0VerifierRouter); + vm.serializeAddress(key, "NitroRiscZeroVerifierRouter", nitroRisc0VerifierRouter); + vm.serializeAddress(key, "TDXRiscZeroVerifierRouter", tdxRisc0VerifierRouter); + vm.serializeBytes32(key, "RiscZeroSetBuilderImageId", setBuilderImageId); + vm.serializeString(key, "RiscZeroSetVerifierSelector", vm.toString(setVerifierSelector)); + vm.serializeBytes32(key, "NitroRootCert", nitroRootCert); + vm.serializeBytes32(key, "NitroVerifierId", nitroVerifierId); + vm.serializeBytes32(key, "NitroVerifierProofId", nitroVerifierProofId); + vm.serializeBytes32(key, "TDXVerifierId", tdxVerifierId); + vm.serializeBytes32(key, "IntelRootCaHash", intelRootCaHash); + vm.serializeUint(key, "NitroMaxTimeDiff", NITRO_MAX_TIME_DIFF); + string memory json = vm.serializeUint(key, "TDXMaxTimeDiff", TDX_MAX_TIME_DIFF); + + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-tee-verifiers.json"); + vm.writeJson(json, outPath); + console.log("Deployment saved to:", outPath); + } +} diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index b7794a936..a48f93383 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -174,36 +174,63 @@ The quote's TDREPORT `REPORTDATA` must put `keccak256(x || y)` in the first 32 b > **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` +### Step 1: Deploy verifier policy contracts -`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`. +`NitroEnclaveVerifier` and `TDXVerifier` are deployed separately from the main multiproof stack because they depend 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 +NITRO_RISC0_VERIFIER_ROUTER=0xB121B667dd2cf27F95f9F5107137696F56f188f6 +TDX_RISC0_VERIFIER_ROUTER=0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 +RISC0_SET_BUILDER_IMAGE_ID=0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9 +TDX_VERIFIER_ID=0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b INTEL_ROOT_CA_HASH=0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009 -TDX_IMAGE_HASH=0xa227306080459e4bcf1324b229b344af4469846f9861aa8aff450f35046df9d6 -TDX_SIGNER=0x6A1f38f20044e8e69EAC755144F14f973e7b8d6E -TDX_REGISTRATION_MANAGER=0x93900CB7eCdB5994352b19DfD8a900Cd4fa437B7 +TDX_IMAGE_HASH=0x4cb35ee476a8098c4e567098714c65f5afe25236fc460b38487a356e14e7db66 +TDX_REGISTRATION_MANAGER=0x44E999A5859c2D12378a349882fAe5805DCE71b9 ``` -`deploy-config/sepolia.json` uses the same `RISC0_VERIFIER_ROUTER` and `TDX_IMAGE_HASH`. The `TEEProverRegistry` +`deploy-config/sepolia.json` uses the same TDX RISC Zero 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. +Deploy both TEE verifier policy contracts with one command: + +```bash +just --justfile scripts/multiproof/justfile deploy-tee-verifiers $NITRO_ROOT_CERT $NITRO_VERIFIER_ID +``` + +This saves output to `deployments/-tee-verifiers.json`. + +To deploy a fresh Nitro verifier, a fresh TDX verifier, and then the TDX +multiproof stack against those exact verifier addresses without editing +`deploy-config/sepolia.json`, use: + +```bash +just --justfile scripts/multiproof/justfile deploy-tdx-system $NITRO_ROOT_CERT $NITRO_VERIFIER_ID +``` + +To deploy Nitro separately: + +```bash +just --justfile scripts/multiproof/justfile deploy-nitro-verifier $NITRO_ROOT_CERT $NITRO_VERIFIER_ID +``` + +This saves output to `deployments/-nitro-verifier.json`. + +To deploy TDX separately: + ```bash just --justfile scripts/multiproof/justfile deploy-tdx-verifier ``` -To override any verifier input manually, pass all three verifier args: +To override any TDX 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_RISC0_VERIFIER_ROUTER \ $TDX_VERIFIER_ID \ $INTEL_ROOT_CA_HASH \ --rpc-url $L1_RPC_URL \ @@ -213,6 +240,23 @@ forge script scripts/multiproof/DeployTDXVerifier.s.sol:DeployTDXVerifier \ The script saves output to `deployments/-tdx-verifier.json`. +To override Nitro inputs manually: + +```bash +forge script scripts/multiproof/DeployNitroVerifier.s.sol:DeployNitroVerifier \ + --sig "run(address,address,bytes32,bytes32,bytes32)" \ + $OWNER \ + $NITRO_RISC0_VERIFIER_ROUTER \ + $RISC0_SET_BUILDER_IMAGE_ID \ + $NITRO_ROOT_CERT \ + $NITRO_VERIFIER_ID \ + --rpc-url $L1_RPC_URL \ + --broadcast \ + --private-key $PRIVATE_KEY +``` + +Use `DeployTEEVerifiers.s.sol` with `run(address,address,address,bytes32,bytes32,bytes32,bytes32,bytes32,bytes32)` to override all Nitro and TDX inputs while still deploying both in one broadcast. + ### 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 both `TDXVerifier.proofSubmitter` and `NitroEnclaveVerifier.proofSubmitter` to the deployed `TEEProverRegistry`. @@ -225,6 +269,13 @@ The `deploy-tdx-stack` recipe resolves a recent L2 output root before invoking ` just --justfile scripts/multiproof/justfile deploy-tdx-stack $TDX_VERIFIER ``` +If the Nitro verifier in `deploy-config/sepolia.json` is owned by another account, do not edit the shared Sepolia deploy +config. Pass both verifier addresses explicitly instead: + +```bash +just --justfile scripts/multiproof/justfile deploy-tdx-stack-with-verifiers $NITRO_VERIFIER $TDX_VERIFIER +``` + To override the manager manually, use: ```bash diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile index 934437b25..238d8eaf0 100644 --- a/scripts/multiproof/justfile +++ b/scripts/multiproof/justfile @@ -3,16 +3,21 @@ set dotenv-load repo_root := justfile_directory() + "/../.." justfile_path := justfile_directory() + "/justfile" default_deploy_config := "deploy-config/sepolia.json" +zeronet_tdx_deploy_config := "deploy-config/zeronet-tdx.json" sepolia_tdx_stack_deployments := "deployments/11155111-dev-with-tdx.json" +sepolia_tee_verifiers_deployments := "deployments/11155111-tee-verifiers.json" forge_account := "testnet-admin" l1_rpc_url := env_var_or_default("L1_RPC_URL", "https://ethereum-full-sepolia-k8s-dev.cbhq.net") l2_rpc_url := env_var_or_default("L2_RPC_URL", "https://base-sepolia-archive-k8s-dev.cbhq.net:8545") l2_output_root_rpc_url := env_var_or_default("L2_OUTPUT_ROOT_RPC_URL", "https://base-sepolia-archive-k8s-dev.cbhq.net:7545") -sepolia_risc0_verifier_router := "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" +sepolia_nitro_risc0_verifier_router := "0xB121B667dd2cf27F95f9F5107137696F56f188f6" +sepolia_tdx_risc0_verifier_router := "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" +sepolia_risc0_set_builder_image_id := "0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9" sepolia_tdx_verifier_id := "0xb9681d1f76f5dbf70da84ad06b5b20befa392638060e947965269b6f63ebbf3b" sepolia_intel_root_ca_hash := "0xa1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009" sepolia_tdx_registration_manager := "0x44E999A5859c2D12378a349882fAe5805DCE71b9" +zero_bytes32 := "0x0000000000000000000000000000000000000000000000000000000000000000" # List available multiproof deployment recipes. default: @@ -27,7 +32,7 @@ default: # 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: +deploy-tdx-verifier risc0_router=sepolia_tdx_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}}" @@ -44,18 +49,78 @@ deploy-tdx-verifier risc0_router=sepolia_risc0_verifier_router tdx_verifier_id=s --account "{{forge_account}}" \ --sender "$owner" +# Deploy the NitroEnclaveVerifier policy contract and its local RISC Zero set verifier route. +# +# Required args: +# nitro_root_cert SHA-256 hash of the AWS Nitro root certificate +# nitro_verifier_id RISC Zero image ID for the Nitro attestation verifier guest +# +# Optional args: +# risc0_router RISC Zero verifier router +# set_builder_image_id RISC Zero set builder image ID +# nitro_verifier_proof_id Verifier proof ID for batched Nitro proofs; defaults to zero +deploy-nitro-verifier nitro_root_cert nitro_verifier_id risc0_router=sepolia_nitro_risc0_verifier_router set_builder_image_id=sepolia_risc0_set_builder_image_id nitro_verifier_proof_id=zero_bytes32: + #!/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/DeployNitroVerifier.s.sol:DeployNitroVerifier \ + --sig "run(address,address,bytes32,bytes32,bytes32,bytes32)" \ + "$owner" \ + "{{risc0_router}}" \ + "{{set_builder_image_id}}" \ + "{{nitro_root_cert}}" \ + "{{nitro_verifier_id}}" \ + "{{nitro_verifier_proof_id}}" \ + --rpc-url "{{l1_rpc_url}}" \ + --broadcast \ + --account "{{forge_account}}" \ + --sender "$owner" + +# Deploy both TEE verifier policy contracts in one broadcast. +# +# Required args: +# nitro_root_cert SHA-256 hash of the AWS Nitro root certificate +# nitro_verifier_id RISC Zero image ID for the Nitro attestation verifier guest +# +# Optional args default to the Sepolia TDX testing values in this justfile. +deploy-tee-verifiers nitro_root_cert nitro_verifier_id nitro_risc0_router=sepolia_nitro_risc0_verifier_router tdx_risc0_router=sepolia_tdx_risc0_verifier_router set_builder_image_id=sepolia_risc0_set_builder_image_id nitro_verifier_proof_id=zero_bytes32 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/DeployTEEVerifiers.s.sol:DeployTEEVerifiers \ + --sig "run(address,address,address,bytes32,bytes32,bytes32,bytes32,bytes32,bytes32)" \ + "$owner" \ + "{{nitro_risc0_router}}" \ + "{{tdx_risc0_router}}" \ + "{{set_builder_image_id}}" \ + "{{nitro_root_cert}}" \ + "{{nitro_verifier_id}}" \ + "{{nitro_verifier_proof_id}}" \ + "{{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 "L1_RPC_URL={{l1_rpc_url}}" @jq -r '"TEE_PROVER_REGISTRY=" + (.TEEProverRegistry // "")' "{{repo_root}}/{{sepolia_tdx_stack_deployments}}" - @echo "RISC0_VERIFIER_ROUTER={{sepolia_risc0_verifier_router}}" + @echo "NITRO_RISC0_VERIFIER_ROUTER={{sepolia_nitro_risc0_verifier_router}}" + @echo "TDX_RISC0_VERIFIER_ROUTER={{sepolia_tdx_risc0_verifier_router}}" + @echo "RISC0_SET_BUILDER_IMAGE_ID={{sepolia_risc0_set_builder_image_id}}" @echo "TDX_VERIFIER_ID={{sepolia_tdx_verifier_id}}" @echo "INTEL_ROOT_CA_HASH={{sepolia_intel_root_ca_hash}}" @echo "TDX_REGISTRATION_MANAGER={{sepolia_tdx_registration_manager}}" @echo "L2_RPC_URL={{l2_rpc_url}}" @echo "L2_OUTPUT_ROOT_RPC_URL={{l2_output_root_rpc_url}}" -# Deploy the TDX multiproof dev/test stack using deploy-config/sepolia.json. +# Deploy the TDX multiproof dev/test stack using the zeronet TDX config. # # Required env: # L1_RPC_URL @@ -68,7 +133,7 @@ tdx-sepolia-config: # 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 l2_rpc=l2_rpc_url l2_output_root_rpc=l2_output_root_rpc_url: +deploy-tdx-stack tdx_verifier tdx_manager=sepolia_tdx_registration_manager deploy_config=zeronet_tdx_deploy_config l2_rpc=l2_rpc_url l2_output_root_rpc=l2_output_root_rpc_url: #!/usr/bin/env bash set -euo pipefail cd "{{repo_root}}" @@ -114,6 +179,92 @@ deploy-tdx-stack tdx_verifier tdx_manager=sepolia_tdx_registration_manager deplo --account "{{forge_account}}" \ --sender "$owner" +# Deploy the TDX multiproof dev/test stack with both verifier addresses supplied explicitly. +# +# Use this when you want to bind the deployed stack to explicit verifier addresses. +# +# Args: +# nitro_verifier NitroEnclaveVerifier address from deploy-nitro-verifier or deploy-tee-verifiers +# tdx_verifier TDXVerifier address from deploy-tdx-verifier or deploy-tee-verifiers +# tdx_manager Manager that can call registerTDXSigner() +deploy-tdx-stack-with-verifiers nitro_verifier tdx_verifier tdx_manager=sepolia_tdx_registration_manager deploy_config=zeronet_tdx_deploy_config l2_rpc=l2_rpc_url l2_output_root_rpc=l2_output_root_rpc_url: + #!/usr/bin/env bash + set -euo pipefail + cd "{{repo_root}}" + owner="$(cast wallet address --account "{{forge_account}}")" + echo "Using {{forge_account}} as deployer: $owner" + + if [[ -n "${ASR_ANCHOR_BLOCK_NUMBER:-}" || -n "${ASR_ANCHOR_OUTPUT_ROOT:-}" ]]; then + if [[ -z "${ASR_ANCHOR_BLOCK_NUMBER:-}" || -z "${ASR_ANCHOR_OUTPUT_ROOT:-}" ]]; then + echo "ASR_ANCHOR_BLOCK_NUMBER and ASR_ANCHOR_OUTPUT_ROOT must be set together" >&2 + exit 1 + fi + asr_anchor_block="$ASR_ANCHOR_BLOCK_NUMBER" + asr_anchor_output_root="$ASR_ANCHOR_OUTPUT_ROOT" + else + lookback="${ASR_ANCHOR_BLOCK_LOOKBACK:-0}" + l2_head="$(cast block-number --rpc-url "{{l2_rpc}}")" + if (( lookback > l2_head )); then + echo "ASR_ANCHOR_BLOCK_LOOKBACK ($lookback) exceeds L2 head ($l2_head)" >&2 + exit 1 + fi + asr_anchor_block=$((l2_head - lookback)) + asr_anchor_block_hex="$(cast to-hex "$asr_anchor_block")" + asr_anchor_output_root="$(cast rpc optimism_outputAtBlock "$asr_anchor_block_hex" --rpc-url "{{l2_output_root_rpc}}" | jq -r '.outputRoot')" + fi + + if [[ -z "$asr_anchor_output_root" || "$asr_anchor_output_root" == "null" || "$asr_anchor_output_root" == "0x0000000000000000000000000000000000000000000000000000000000000000" ]]; then + echo "Invalid ASR anchor output root: $asr_anchor_output_root" >&2 + exit 1 + fi + + echo "Using NitroEnclaveVerifier: {{nitro_verifier}}" + echo "Using TDXVerifier: {{tdx_verifier}}" + echo "Using ASR anchor output root: $asr_anchor_output_root" + echo "Using ASR anchor L2 block: $asr_anchor_block" + + DEPLOY_CONFIG_PATH="{{deploy_config}}" \ + forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \ + --sig "run(address,address,address,bytes32,uint256)" \ + "{{nitro_verifier}}" \ + "{{tdx_verifier}}" \ + "{{tdx_manager}}" \ + "$asr_anchor_output_root" \ + "$asr_anchor_block" \ + --rpc-url "{{l1_rpc_url}}" \ + --broadcast \ + --account "{{forge_account}}" \ + --sender "$owner" + +# Deploy fresh Nitro and TDX verifier policy contracts, then deploy the TDX stack +# against those verifier addresses without editing shared Sepolia deploy config. +# +# Required args: +# nitro_root_cert SHA-256 hash of the AWS Nitro root certificate +# nitro_verifier_id RISC Zero image ID for the Nitro attestation verifier guest +deploy-tdx-system nitro_root_cert nitro_verifier_id tdx_manager=sepolia_tdx_registration_manager deploy_config=zeronet_tdx_deploy_config l2_rpc=l2_rpc_url l2_output_root_rpc=l2_output_root_rpc_url: + #!/usr/bin/env bash + set -euo pipefail + cd "{{repo_root}}" + + just --justfile "{{justfile_path}}" deploy-tee-verifiers "{{nitro_root_cert}}" "{{nitro_verifier_id}}" + + nitro_verifier="$(jq -r '.NitroEnclaveVerifier // empty' "{{sepolia_tee_verifiers_deployments}}")" + tdx_verifier="$(jq -r '.TDXVerifier // empty' "{{sepolia_tee_verifiers_deployments}}")" + + if [[ -z "$nitro_verifier" || -z "$tdx_verifier" ]]; then + echo "Missing verifier address in {{sepolia_tee_verifiers_deployments}}" >&2 + exit 1 + fi + + just --justfile "{{justfile_path}}" deploy-tdx-stack-with-verifiers \ + "$nitro_verifier" \ + "$tdx_verifier" \ + "{{tdx_manager}}" \ + "{{deploy_config}}" \ + "{{l2_rpc}}" \ + "{{l2_output_root_rpc}}" + # Args: # registry TEEProverRegistry address. Defaults to deployments/11155111-dev-with-tdx.json. # rpc L1 Sepolia RPC URL. @@ -171,7 +322,11 @@ tdx-sepolia-help: @echo 'export L1_RPC_URL=' @echo 'export L2_RPC_URL=' @echo 'export L2_OUTPUT_ROOT_RPC_URL=' + @echo 'just deploy-nitro-verifier ' @echo 'just deploy-tdx-verifier' + @echo 'just deploy-tee-verifiers ' + @echo 'just deploy-tdx-system ' @echo 'just deploy-tdx-stack [tdx-manager] [deploy-config] [l2-rpc] [l2-output-root-rpc]' + @echo 'just deploy-tdx-stack-with-verifiers [tdx-manager] [deploy-config] [l2-rpc] [l2-output-root-rpc]' @echo 'just tee-registered-signers' @echo 'just tdx-sepolia-config'