diff --git a/deploy-config/local.json b/deploy-config/local.json index f1d05276f..855a5b720 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -37,8 +37,11 @@ "sp1Verifier": "0x0000000000000000000000000000000000000000", "superchainConfigGuardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "superchainConfigIncidentResponder": "0x0000000000000000000000000000000000000000", + "tdxVerifier": "0x0000000000000000000000000000000000000001", "teeChallenger": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", "teeImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeNitroImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "teeTdxImageHash": "0x0000000000000000000000000000000000000000000000000000000000000001", "teeProposer": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "zkAggregationHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "zkRangeHash": "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index cfcea5d2d..c2edba5bc 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -36,8 +36,10 @@ "sp1Verifier": "0x397A5f7f3dBd538f23DE225B51f532c34448dA9B", "superchainConfigGuardian": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", "superchainConfigIncidentResponder": "0x0000000000000000000000000000000000000000", + "tdxVerifier": "0x5431a6Fd5365e9629BD2B0969e54c4f09d6781bA", "teeChallenger": "0xadc09b63a3ac57a2ce86d946617a18df9db029a1", - "teeImageHash": "0xd04c8147410930fade36986c9357fd9a7591231e38e6e87e51183bd5909f509c", + "teeNitroImageHash": "0x11fb64617dfa2875d31b0cfb656666fd8cee65eb134fefeca171b9b6b4444a64", + "teeTdxImageHash": "0x4cb35ee476a8098c4e567098714c65f5afe25236fc460b38487a356e14e7db66", "teeProposer": "0xdb84125f2f4229c81c579f41bc129c71b174eb58", "zkAggregationHash": "0x005aa369dd9445e172018a9eaa4a0f9767b2c2079ece90ca120422b3c4c65f11", "zkRangeHash": "0x51708a6b4a3b800a14607e902c1aad47719905c12698a3e01ca8b3321761bc52" diff --git a/deploy-config/zeronet-tdx.json b/deploy-config/zeronet-tdx.json new file mode 100644 index 000000000..5833504ca --- /dev/null +++ b/deploy-config/zeronet-tdx.json @@ -0,0 +1,47 @@ +{ + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultRecipient": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "baseFeeVaultWithdrawalNetwork": 0, + "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "disputeGameFinalityDelaySeconds": 302400, + "delayedWETHWithdrawalDelay": 302400, + "finalSystemOwner": "0x8C1a617BdB47342F9C17Ac8750E0b070c372C721", + "fundDevAccounts": true, + "gasPriceOracleBaseFeeScalar": 1368, + "gasPriceOracleBlobBaseFeeScalar": 810949, + "l1ChainId": 11155111, + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultRecipient": "0x71bE63f3384f5fb98995898A86B02Fb2426c5788", + "l1FeeVaultWithdrawalNetwork": 0, + "l2ChainId": 84532, + "l2GenesisBlockGasLimit": "0x17D7840", + "l2OutputOracleStartingBlockNumber": 1, + "l2OutputOracleStartingTimestamp": 1, + "multiproofBlockInterval": 100, + "multiproofConfigHash": "0x12e9c45f19f9817c6d4385fad29e7a70c355502cf0883e76a9a7e478a85d1360", + "multiproofGameType": 621, + "multiproofGenesisBlockNumber": 0, + "multiproofIntermediateBlockInterval": 10, + "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001", + "nitroEnclaveVerifier": "0x2DC52760D13a3C2dF33fcc42913C4dddd8d976B9", + "operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "operatorFeeVaultRecipient": "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec", + "operatorFeeVaultWithdrawalNetwork": 0, + "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "proofMaturityDelaySeconds": 604800, + "proxyAdminOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "respectedGameType": 621, + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a", + "sequencerFeeVaultWithdrawalNetwork": 0, + "sp1Verifier": "0x397A5f7f3dBd538f23DE225B51f532c34448dA9B", + "superchainConfigGuardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "superchainConfigIncidentResponder": "0x0000000000000000000000000000000000000000", + "tdxVerifier": "0xB5F7f92dA7aBfDBDEB8e84EE78765fd0D3D3E092", + "teeChallenger": "0xadc09b63a3ac57a2ce86d946617a18df9db029a1", + "teeNitroImageHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "teeTdxImageHash": "0x278ba4ed4251d187fb7215be37d8ee2ccb36906c71611dd89b655a81ed2445dc", + "teeProposer": "0x0ce54eFf9256b1da3b67656278EbAD2A9dfCFd4F", + "zkAggregationHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "zkRangeHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/deployments/11155111-dev-with-tdx.json b/deployments/11155111-dev-with-tdx.json new file mode 100644 index 000000000..b90978d0f --- /dev/null +++ b/deployments/11155111-dev-with-tdx.json @@ -0,0 +1,13 @@ +{ + "ASRStartingBlockNumber": 43536599, + "ASRStartingOutputRoot": "0x5bc62bbffa839dff9e9a13cb0ec240be19e76fc2c0e44082402697c728ba6150", + "AggregateVerifier": "0x1c03e8Ce7184d2d3A075f93A3637c6a3be866704", + "AnchorStateRegistry": "0x6C0dF1080cbd2856A4E2d0708bE23F3E9cf44A6A", + "DelayedWETH": "0x4bBCFa9bE58620B4fb1989A774917D57E2519985", + "DisputeGameFactory": "0xa92fe37ED3DFEE686c305Ab7cc2c445A1bCf300F", + "NitroEnclaveVerifier": "0x2DC52760D13a3C2dF33fcc42913C4dddd8d976B9", + "TDXRegistrationManager": "0x44E999A5859c2D12378a349882fAe5805DCE71b9", + "TDXVerifier": "0xB5F7f92dA7aBfDBDEB8e84EE78765fd0D3D3E092", + "TEEProverRegistry": "0x6123Ab4Ab4f860BA4810f7f7Cb0be4a111D8C490", + "TEEVerifier": "0x828e90BA966Abcd869e39D69EC4d89F521547E5A" +} 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/deployments/11155111-tee-verifiers.json b/deployments/11155111-tee-verifiers.json new file mode 100644 index 000000000..d9688a14f --- /dev/null +++ b/deployments/11155111-tee-verifiers.json @@ -0,0 +1,4 @@ +{ + "NitroEnclaveVerifier": "0x2DC52760D13a3C2dF33fcc42913C4dddd8d976B9", + "TDXVerifier": "0xB5F7f92dA7aBfDBDEB8e84EE78765fd0D3D3E092" +} diff --git a/deployments/560048-dev-no-nitro.json b/deployments/560048-dev-no-nitro.json new file mode 100644 index 000000000..ddcdabe6a --- /dev/null +++ b/deployments/560048-dev-no-nitro.json @@ -0,0 +1,11 @@ +{ + "ASRStartingBlockNumber": 4353541, + "ASRStartingOutputRoot": "0xb63f4c2646f83e684e14f6a217332ef8c372e95281269384cdb2aa93efd3109a", + "AggregateVerifier": "0x2aB6e58257e9F2689F8a17464450e1cf3870BAD1", + "AnchorStateRegistry": "0xc14F7E178BfE6209d30c431a8C69b4EcE6d15334", + "DelayedWETH": "0x4fc52f45202cfa48Cb84308B05A0D2A6adBAbBF0", + "DisputeGameFactory": "0x8e993853C303288f4fcd138E180E31a3c798E4F9", + "TDXVerifier": "0x0000000000000000000000000000000000000001", + "TEEProverRegistry": "0x8BE44CA11B9F81111242D0f854Db9171B3978Fc7", + "TEEVerifier": "0x09F9E99d379A9963Fe13814b31B90ba81bf9a74f" +} \ No newline at end of file diff --git a/interfaces/L1/proofs/IAggregateVerifier.sol b/interfaces/L1/proofs/IAggregateVerifier.sol index dd588e4ec..532b4a394 100644 --- a/interfaces/L1/proofs/IAggregateVerifier.sol +++ b/interfaces/L1/proofs/IAggregateVerifier.sol @@ -18,7 +18,8 @@ interface IAggregateVerifier is IDisputeGame { function DISPUTE_GAME_FACTORY() external view returns (IDisputeGameFactory); function DELAYED_WETH() external view returns (IDelayedWETH); function TEE_VERIFIER() external view returns (IVerifier); - function TEE_IMAGE_HASH() external view returns (bytes32); + function TEE_NITRO_IMAGE_HASH() external view returns (bytes32); + function TEE_TDX_IMAGE_HASH() external view returns (bytes32); function ZK_VERIFIER() external view returns (IVerifier); function ZK_RANGE_HASH() external view returns (bytes32); function ZK_AGGREGATE_HASH() external view returns (bytes32); diff --git a/interfaces/L1/proofs/tee/ITDXVerifier.sol b/interfaces/L1/proofs/tee/ITDXVerifier.sol new file mode 100644 index 000000000..8b5248bcf --- /dev/null +++ b/interfaces/L1/proofs/tee/ITDXVerifier.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @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 offchain/ZK TDX DCAP verifier. +/// @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 publicKeyX secp256k1 public key x-coordinate. +/// @param publicKeyY secp256k1 public key y-coordinate. +/// @param imageHash Multiproof-compatible image hash derived from MRTD and RTMR0-3. +/// @param reportDataPrefix First 32 bytes of TDREPORT.REPORTDATA. +struct TDXVerifierJournal { + TDXTcbStatus tcbStatus; + uint64 timestamp; + uint64 collateralExpiration; + bytes32 rootCaHash; + bytes32 publicKeyX; + bytes32 publicKeyY; + bytes32 imageHash; + bytes32 reportDataPrefix; +} + +/// @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 proofBytes ZK proof bytes. + /// @return signer Ethereum address derived from the attested public key. + /// @return imageHash Multiproof-compatible image hash derived from MRTD and RTMR0-3. + function verify( + bytes calldata output, + bytes calldata proofBytes + ) + external + view + returns (address signer, bytes32 imageHash); +} diff --git a/interfaces/L1/proofs/tee/ITEEProverRegistry.sol b/interfaces/L1/proofs/tee/ITEEProverRegistry.sol index ea38c36ed..0b45e722b 100644 --- a/interfaces/L1/proofs/tee/ITEEProverRegistry.sol +++ b/interfaces/L1/proofs/tee/ITEEProverRegistry.sol @@ -21,7 +21,7 @@ interface ITEEProverRegistry { function deregisterSigner(address signer) external; function isValidSigner(address signer) external view returns (bool); function getRegisteredSigners() external view returns (address[] memory); - function getExpectedImageHash() external view returns (bytes32); + function getExpectedImageHash(uint8 teeType) external view returns (bytes32); function initialize( address initialOwner, address initialManager, diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index 7fe484580..031a3ce97 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -22,6 +22,7 @@ contract DeployConfig is Script { address public sp1Verifier; address public superchainConfigGuardian; address public superchainConfigIncidentResponder; + address public tdxVerifier; address public teeChallenger; address public teeProposer; @@ -31,6 +32,8 @@ contract DeployConfig is Script { bytes32 public multiproofConfigHash; bytes32 public multiproofGenesisOutputRoot; bytes32 public teeImageHash; + bytes32 public teeNitroImageHash; + bytes32 public teeTdxImageHash; bytes32 public zkAggregationHash; bytes32 public zkRangeHash; @@ -75,6 +78,7 @@ contract DeployConfig is Script { sp1Verifier = _json.readAddress("$.sp1Verifier"); superchainConfigGuardian = _json.readAddress("$.superchainConfigGuardian"); superchainConfigIncidentResponder = _json.readAddress("$.superchainConfigIncidentResponder"); + tdxVerifier = _json.readAddressOr("$.tdxVerifier", address(0)); teeChallenger = _json.readAddress("$.teeChallenger"); teeProposer = _json.readAddress("$.teeProposer"); @@ -82,7 +86,9 @@ contract DeployConfig is Script { multiproofConfigHash = _json.readBytes32("$.multiproofConfigHash"); multiproofGenesisOutputRoot = _json.readBytes32("$.multiproofGenesisOutputRoot"); - teeImageHash = _json.readBytes32("$.teeImageHash"); + teeImageHash = _json.readBytes32Or("$.teeImageHash", bytes32(0)); + teeNitroImageHash = _json.readBytes32Or("$.teeNitroImageHash", teeImageHash); + teeTdxImageHash = _json.readBytes32Or("$.teeTdxImageHash", teeImageHash); zkAggregationHash = _json.readBytes32("$.zkAggregationHash"); zkRangeHash = _json.readBytes32("$.zkRangeHash"); diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index 227d123d6..b69679f7c 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -37,6 +37,7 @@ import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { ITDXVerifier } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -72,12 +73,14 @@ contract SystemDeploy is Script { uint256 withdrawalDelaySeconds; uint256 proofMaturityDelaySeconds; uint256 disputeGameFinalityDelaySeconds; - bytes32 teeImageHash; + bytes32 teeNitroImageHash; + bytes32 teeTdxImageHash; bytes32 zkRangeHash; bytes32 zkAggregationHash; bytes32 multiproofConfigHash; uint256 multiproofGameType; address nitroEnclaveVerifier; + address tdxVerifier; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; ISP1Verifier sp1Verifier; @@ -120,7 +123,8 @@ contract SystemDeploy is Script { IDelayedWETH delayedWETH; IVerifier teeVerifier; IVerifier zkVerifier; - bytes32 teeImageHash; + bytes32 teeNitroImageHash; + bytes32 teeTdxImageHash; bytes32 zkRangeHash; bytes32 zkAggregationHash; bytes32 multiproofConfigHash; @@ -257,12 +261,14 @@ contract SystemDeploy is Script { withdrawalDelaySeconds: cfg.delayedWETHWithdrawalDelay(), proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(), disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds(), - teeImageHash: cfg.teeImageHash(), + teeNitroImageHash: cfg.teeNitroImageHash(), + teeTdxImageHash: cfg.teeTdxImageHash(), zkRangeHash: cfg.zkRangeHash(), zkAggregationHash: cfg.zkAggregationHash(), multiproofConfigHash: cfg.multiproofConfigHash(), multiproofGameType: cfg.multiproofGameType(), nitroEnclaveVerifier: cfg.nitroEnclaveVerifier(), + tdxVerifier: cfg.tdxVerifier(), multiproofBlockInterval: cfg.multiproofBlockInterval(), multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(), sp1Verifier: ISP1Verifier(cfg.sp1Verifier()), @@ -961,7 +967,9 @@ contract SystemDeploy is Script { vm.broadcast(msg.sender); output_.teeProverRegistryImpl = new TEEProverRegistry( - INitroEnclaveVerifier(_input.nitroEnclaveVerifier), _output.disputeGameFactoryProxy + INitroEnclaveVerifier(_input.nitroEnclaveVerifier), + ITDXVerifier(_input.tdxVerifier), + _output.disputeGameFactoryProxy ); output_.teeProverRegistryProxy = @@ -1003,7 +1011,8 @@ contract SystemDeploy is Script { delayedWETH: _output.delayedWETHProxy, teeVerifier: output_.teeVerifier, zkVerifier: output_.zkVerifier, - teeImageHash: _input.teeImageHash, + teeNitroImageHash: _input.teeNitroImageHash, + teeTdxImageHash: _input.teeTdxImageHash, zkRangeHash: _input.zkRangeHash, zkAggregationHash: _input.zkAggregationHash, multiproofConfigHash: _input.multiproofConfigHash, @@ -1033,7 +1042,8 @@ contract SystemDeploy is Script { _input.delayedWETH, _input.teeVerifier, _input.zkVerifier, - _input.teeImageHash, + _input.teeNitroImageHash, + _input.teeTdxImageHash, AggregateVerifier.ZkHashes(_input.zkRangeHash, _input.zkAggregationHash), _input.multiproofConfigHash, _input.l2ChainId, @@ -1066,14 +1076,17 @@ contract SystemDeploy is Script { } function _assertValidMultiproofInput(ImplementationInput memory _input) internal view { - require(_input.teeImageHash != bytes32(0), "SystemDeploy: teeImageHash not set"); + require(_input.teeNitroImageHash != bytes32(0), "SystemDeploy: teeNitroImageHash not set"); + require(_input.teeTdxImageHash != bytes32(0), "SystemDeploy: teeTdxImageHash not set"); require(_input.zkRangeHash != bytes32(0), "SystemDeploy: zkRangeHash not set"); require(_input.zkAggregationHash != bytes32(0), "SystemDeploy: zkAggregationHash not set"); require(_input.multiproofConfigHash != bytes32(0), "SystemDeploy: multiproofConfigHash not set"); require(_input.multiproofGameType != 0, "SystemDeploy: multiproofGameType not set"); require(_input.nitroEnclaveVerifier != address(0), "SystemDeploy: nitroEnclaveVerifier not set"); + require(_input.tdxVerifier != address(0), "SystemDeploy: tdxVerifier not set"); require(address(_input.sp1Verifier) != address(0), "SystemDeploy: sp1Verifier not set"); DeployUtils.assertValidContractAddress(_input.nitroEnclaveVerifier); + DeployUtils.assertValidContractAddress(_input.tdxVerifier); DeployUtils.assertValidContractAddress(address(_input.sp1Verifier)); require(_input.multiproofBlockInterval != 0, "SystemDeploy: multiproof block interval not set"); require( diff --git a/scripts/multiproof/DeployDevBase.s.sol b/scripts/multiproof/DeployDevBase.s.sol index 31d009270..01ac32d77 100644 --- a/scripts/multiproof/DeployDevBase.s.sol +++ b/scripts/multiproof/DeployDevBase.s.sol @@ -7,6 +7,9 @@ import { console2 as console } from "lib/forge-std/src/console2.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 { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { ITDXVerifier } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; import { DisputeGameFactory } from "src/L1/proofs/DisputeGameFactory.sol"; import { GameType, Hash } from "src/libraries/bridge/Types.sol"; @@ -16,72 +19,60 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; +import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.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"; import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; abstract contract DeployDevBase is Script { - DeployConfig public constant cfg = + DeployConfig internal constant cfg = DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); - 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() public { + function _run( + bytes32 asrStartingOutputRoot, + uint256 asrStartingBlockNumber, + address nitroVerifier, + address tdxVerifier, + address registrationManager, + uint256 initBond, + string memory outputSuffix + ) + internal + { + require(asrStartingOutputRoot != bytes32(0), "asrStartingOutputRoot must be non-zero"); + require(tdxVerifier != address(0), "tdxVerifier must be non-zero"); + require(registrationManager != address(0), "registrationManager must be non-zero"); GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); - - _preflight(); - _logHeader(); + string memory key = "deployment"; + if (nitroVerifier != address(0)) { + vm.serializeAddress(key, "NitroEnclaveVerifier", nitroVerifier); + vm.serializeAddress(key, "TDXRegistrationManager", registrationManager); + } + vm.serializeAddress(key, "TDXVerifier", tdxVerifier); vm.startBroadcast(); - _deployInfrastructure(gameType); - _deployTEEContracts(gameType); - _deployAggregateVerifier(gameType); - - vm.stopBroadcast(); - - _printSummary(); - _writeOutput(); - } - - function _deployInfrastructure(GameType gameType) internal { - address owner = cfg.finalSystemOwner(); - address factoryImpl = address(new DisputeGameFactory()); - MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(owner); - Proxy proxy = new Proxy(msg.sender); - proxy.upgradeTo(factoryImpl); - proxy.changeAdmin(address(proxyAdmin)); - DisputeGameFactory(address(proxy)).initialize(owner); - disputeGameFactory = address(proxy); + proxy.upgradeTo(address(new DisputeGameFactory())); + DisputeGameFactory factory = DisputeGameFactory(address(proxy)); + factory.initialize(msg.sender); + proxy.changeAdmin(address(0xdead)); + factory.setInitBond(gameType, initBond); + vm.serializeAddress(key, "DisputeGameFactory", address(factory)); MockAnchorStateRegistry asr = new MockAnchorStateRegistry(); - mockAnchorRegistry = IAnchorStateRegistry(address(asr)); - asr.initialize( - disputeGameFactory, - Hash.wrap(cfg.multiproofGenesisOutputRoot()), - cfg.multiproofGenesisBlockNumber(), - gameType - ); - } - - function _deployTEEContracts(GameType gameType) internal { - address owner = cfg.finalSystemOwner(); - address teeRegistryImpl = _deployTEERegistryImpl(); + asr.initialize(address(factory), Hash.wrap(asrStartingOutputRoot), asrStartingBlockNumber, gameType); + vm.serializeAddress(key, "AnchorStateRegistry", address(asr)); + vm.serializeBytes32(key, "ASRStartingOutputRoot", asrStartingOutputRoot); + vm.serializeUint(key, "ASRStartingBlockNumber", asrStartingBlockNumber); address[] memory initialProposers = new address[](2); initialProposers[0] = cfg.teeProposer(); @@ -89,65 +80,58 @@ abstract contract DeployDevBase is Script { Proxy teeProxy = new Proxy(msg.sender); teeProxy.upgradeToAndCall( - teeRegistryImpl, abi.encodeCall(TEEProverRegistry.initialize, (owner, owner, initialProposers, gameType)) + address( + new DevTEEProverRegistry( + INitroEnclaveVerifier(nitroVerifier), + ITDXVerifier(tdxVerifier), + IDisputeGameFactory(address(factory)) + ) + ), + abi.encodeCall( + TEEProverRegistry.initialize, (cfg.finalSystemOwner(), registrationManager, initialProposers, gameType) + ) ); teeProxy.changeAdmin(address(0xdead)); - teeProverRegistryProxy = address(teeProxy); + address teeProverRegistryProxy = address(teeProxy); + vm.serializeAddress(key, "TEEProverRegistry", teeProverRegistryProxy); - teeVerifier = address(new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), mockAnchorRegistry)); - } + if (nitroVerifier != address(0)) { + INitroEnclaveVerifier(nitroVerifier).setProofSubmitter(teeProverRegistryProxy); + } - function _deployAggregateVerifier(GameType gameType) internal { - address zkVerifier = address(new MockVerifier(mockAnchorRegistry)); - mockDelayedWETH = address(new MockDelayedWETH()); + address teeVerifier = + address(new TEEVerifier(TEEProverRegistry(teeProverRegistryProxy), IAnchorStateRegistry(address(asr)))); + vm.serializeAddress(key, "TEEVerifier", teeVerifier); - AggregateVerifier.ZkHashes memory zkHashes = - AggregateVerifier.ZkHashes({ rangeHash: cfg.zkRangeHash(), aggregateHash: cfg.zkAggregationHash() }); + MockDelayedWETH delayedWETH = new MockDelayedWETH(); + vm.serializeAddress(key, "DelayedWETH", address(delayedWETH)); - aggregateVerifier = address( + address aggregateVerifier = address( new AggregateVerifier( gameType, - mockAnchorRegistry, - IDelayedWETH(payable(mockDelayedWETH)), + IAnchorStateRegistry(address(asr)), + IDelayedWETH(payable(address(delayedWETH))), IVerifier(teeVerifier), - IVerifier(zkVerifier), - cfg.teeImageHash(), - zkHashes, + IVerifier(address(new MockVerifier(IAnchorStateRegistry(address(asr))))), + cfg.teeNitroImageHash(), + cfg.teeTdxImageHash(), + AggregateVerifier.ZkHashes({ rangeHash: cfg.zkRangeHash(), aggregateHash: cfg.zkAggregationHash() }), cfg.multiproofConfigHash(), cfg.l2ChainId(), - _blockInterval(), - _intermediateBlockInterval() + cfg.multiproofBlockInterval(), + cfg.multiproofIntermediateBlockInterval() ) ); - DisputeGameFactory factory = DisputeGameFactory(disputeGameFactory); - factory.setImplementation(gameType, IDisputeGame(aggregateVerifier), ""); - factory.setInitBond(gameType, _initBond()); - } + factory.setImplementation(gameType, IDisputeGame(aggregateVerifier)); + factory.transferOwnership(cfg.finalSystemOwner()); - function _writeOutput() internal { - string memory key = "deployment"; - vm.serializeAddress(key, "TEEProverRegistry", teeProverRegistryProxy); - vm.serializeAddress(key, "TEEVerifier", teeVerifier); - _serializeExtra(key); - 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), _outputSuffix()); + vm.stopBroadcast(); + + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), outputSuffix); vm.writeJson(json, outPath); console.log("Deployment saved to:", outPath); } - - function _blockInterval() internal pure virtual returns (uint256); - function _intermediateBlockInterval() internal pure virtual returns (uint256); - function _initBond() internal pure virtual returns (uint256); - function _outputSuffix() internal pure virtual returns (string memory); - function _deployTEERegistryImpl() internal virtual returns (address); - function _logHeader() internal view virtual; - function _printSummary() internal view virtual; - - function _preflight() internal virtual { } - function _serializeExtra(string memory key) internal virtual { } } diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 83264f695..68eee6c38 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -1,64 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { console2 as console } from "lib/forge-std/src/console2.sol"; - -import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; -import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; - import { DeployDevBase } from "./DeployDevBase.s.sol"; /// @title DeployDevNoNitro /// @notice Development deployment using DevTEEProverRegistry, which bypasses AWS Nitro attestation /// validation. See scripts/multiproof/README.md for usage. Not for production. contract DeployDevNoNitro is DeployDevBase { - uint256 public constant BLOCK_INTERVAL = 100; - uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; - uint256 public constant INIT_BOND = 0.001 ether; - - function _blockInterval() internal pure override returns (uint256) { - return BLOCK_INTERVAL; - } - - function _intermediateBlockInterval() internal pure override returns (uint256) { - return INTERMEDIATE_BLOCK_INTERVAL; - } - - function _initBond() internal pure override returns (uint256) { - return INIT_BOND; - } - - function _outputSuffix() internal pure override returns (string memory) { - return "-dev-no-nitro.json"; - } - - function _deployTEERegistryImpl() internal override returns (address) { - return - address( - new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(disputeGameFactory)) - ); - } - - function _logHeader() internal view override { - console.log("=== Deploying Dev Infrastructure (NO NITRO) ==="); - 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()); + function run() public { + run(cfg.multiproofGenesisOutputRoot(), cfg.multiproofGenesisBlockNumber()); } - function _printSummary() internal view override { - console.log("\n=== DEV DEPLOYMENT COMPLETE (NO NITRO) ==="); - console.log("DevTEEProverRegistry:", teeProverRegistryProxy); - console.log("TEEVerifier:", teeVerifier); - console.log("DisputeGameFactory:", disputeGameFactory); - console.log("AnchorStateRegistry (mock):", address(mockAnchorRegistry)); - console.log("DelayedWETH (mock):", mockDelayedWETH); - 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())); + function run(bytes32 asrStartingOutputRoot, uint256 asrStartingBlockNumber) public { + _run( + asrStartingOutputRoot, + asrStartingBlockNumber, + address(0), + cfg.tdxVerifier(), + cfg.finalSystemOwner(), + 0.001 ether, + "-dev-no-nitro.json" + ); } } diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol deleted file mode 100644 index 0282a504f..000000000 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { console2 as console } from "lib/forge-std/src/console2.sol"; - -import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; -import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; - -import { DeployDevBase } from "./DeployDevBase.s.sol"; - -/// @title DeployDevWithNitro -/// @notice Development deployment WITH AWS Nitro attestation validation. Uses the real -/// TEEProverRegistry, so signer registration requires a ZK proof of a valid AWS -/// Nitro attestation (no addDevSigner bypass). -/// @dev Prerequisite: deploy the RISC Zero verifier stack and NitroEnclaveVerifier via -/// DeployRiscZeroStack.s.sol first (those contracts need Solidity ^0.8.20, while this -/// script is pinned to =0.8.15), then set `nitroEnclaveVerifier` in the deploy config. -/// Note: AWS Nitro attestations are only valid for 60 minutes — generate the ZK proof -/// and submit registerSigner() within that window. -contract DeployDevWithNitro is DeployDevBase { - uint256 public constant BLOCK_INTERVAL = 600; - uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30; - uint256 public constant INIT_BOND = 0.00001 ether; - - address public nitroEnclaveVerifierAddr; - - function _blockInterval() internal pure override returns (uint256) { - return BLOCK_INTERVAL; - } - - function _intermediateBlockInterval() internal pure override returns (uint256) { - return INTERMEDIATE_BLOCK_INTERVAL; - } - - function _initBond() internal pure override returns (uint256) { - return INIT_BOND; - } - - function _outputSuffix() internal pure override returns (string memory) { - return "-dev-with-nitro.json"; - } - - function _preflight() internal override { - nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier(); - require( - nitroEnclaveVerifierAddr != address(0), - "nitroEnclaveVerifier must be set in config (deploy via DeployRiscZeroStack.s.sol first)" - ); - } - - function _deployTEERegistryImpl() internal override returns (address) { - return address( - new TEEProverRegistry( - INitroEnclaveVerifier(nitroEnclaveVerifierAddr), IDisputeGameFactory(disputeGameFactory) - ) - ); - } - - function _serializeExtra(string memory key) internal override { - vm.serializeAddress(key, "NitroEnclaveVerifier", nitroEnclaveVerifierAddr); - } - - function _logHeader() internal view override { - console.log("=== Deploying Dev Infrastructure (WITH NITRO) ==="); - console.log("Chain ID:", block.chainid); - console.log("Owner:", cfg.finalSystemOwner()); - console.log("TEE Proposer:", cfg.teeProposer()); - console.log("TEE Challenger:", cfg.teeChallenger()); - console.log("Game Type:", cfg.multiproofGameType()); - console.log("NitroEnclaveVerifier:", nitroEnclaveVerifierAddr); - console.log(""); - console.log("NOTE: Using REAL TEEProverRegistry - ZK attestation proof REQUIRED."); - } - - function _printSummary() internal view override { - console.log("\n========================================"); - console.log(" DEV DEPLOYMENT COMPLETE (WITH NITRO)"); - console.log("========================================"); - console.log("\nTEE Contracts:"); - console.log(" NitroEnclaveVerifier:", nitroEnclaveVerifierAddr); - console.log(" TEEProverRegistry:", teeProverRegistryProxy); - 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 signer with ZK attestation proof <<<"); - console.log("\n cast send", teeProverRegistryProxy); - console.log(' "registerSigner(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 new file mode 100644 index 000000000..ccee75b0b --- /dev/null +++ b/scripts/multiproof/DeployDevWithTDX.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { DeployDevBase } from "./DeployDevBase.s.sol"; + +/// @notice Development deployment using the TDX signer-registration path. +contract DeployDevWithTDX is DeployDevBase { + function run( + address nitroEnclaveVerifier, + address tdxVerifier, + address registrationManager, + bytes32 asrStartingOutputRoot, + uint256 asrStartingBlockNumber + ) + public + { + require(nitroEnclaveVerifier != address(0), "nitroEnclaveVerifier must be non-zero"); + _run( + asrStartingOutputRoot, + asrStartingBlockNumber, + nitroEnclaveVerifier, + tdxVerifier, + registrationManager, + 0.00001 ether, + "-dev-with-tdx.json" + ); + } +} diff --git a/scripts/multiproof/DeployNitroVerifier.s.sol b/scripts/multiproof/DeployNitroVerifier.s.sol new file mode 100644 index 000000000..dbf434725 --- /dev/null +++ b/scripts/multiproof/DeployNitroVerifier.s.sol @@ -0,0 +1,77 @@ +// 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. + */ + +import { Script } from "forge-std/Script.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 { + /// @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. + /// @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 + { + 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"); + + ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ + verifierId: nitroVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter + }); + + vm.startBroadcast(); + + address setVerifier = + address(new RiscZeroSetVerifier(IRiscZeroVerifier(risc0VerifierRouter), setBuilderImageId, "")); + NitroEnclaveVerifier verifier = new NitroEnclaveVerifier( + owner, + 1 hours, + new bytes32[](0), + new uint64[](0), + nitroRootCert, + owner, + address(0), + ZkCoProcessorType.RiscZero, + zkConfig, + nitroVerifierProofId + ); + + verifier.addVerifyRoute( + ZkCoProcessorType.RiscZero, RiscZeroSetVerifierLib.selector(setBuilderImageId), setVerifier + ); + + vm.stopBroadcast(); + + vm.serializeAddress("deployment", "RiscZeroSetVerifier", setVerifier); + string memory json = vm.serializeAddress("deployment", "NitroEnclaveVerifier", address(verifier)); + + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-nitro-verifier.json"); + vm.writeJson(json, outPath); + } +} diff --git a/scripts/multiproof/DeployRiscZeroStack.s.sol b/scripts/multiproof/DeployRiscZeroStack.s.sol index 42fac5883..1bb071cf6 100644 --- a/scripts/multiproof/DeployRiscZeroStack.s.sol +++ b/scripts/multiproof/DeployRiscZeroStack.s.sol @@ -40,11 +40,8 @@ pragma solidity ^0.8.20; * - NitroEnclaveVerifier with route wired to the local SetVerifier * * POST-DEPLOY: - * After deploying TEEProverRegistry via DeployDevWithNitro.s.sol, update the - * proofSubmitter on NitroEnclaveVerifier to the TEEProverRegistry address: - * - * cast send "setProofSubmitter(address)" \ - * --rpc-url --private-key + * Deploy the multiproof stack with DeployDevWithTDX.s.sol; it updates + * proofSubmitter on NitroEnclaveVerifier to the TEEProverRegistry address. * * ───────────────────────────────────────────────────────────────────────────────── */ @@ -154,9 +151,7 @@ contract DeployRiscZeroStack is Script { console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); console.log("RISC Zero Router (external):", risc0VerifierRouter); console.log(""); - console.log(">>> Set nitroEnclaveVerifier in deploy config to:", nitroEnclaveVerifier); - console.log(">>> Then run DeployDevWithNitro.s.sol <<<"); - console.log(">>> Then call setProofSubmitter(TEEProverRegistry) on NitroEnclaveVerifier <<<"); + console.log(">>> Then deploy the multiproof stack with DeployDevWithTDX.s.sol <<<"); console.log("========================================"); string memory key = "deployment"; diff --git a/scripts/multiproof/DeployTDXVerifier.s.sol b/scripts/multiproof/DeployTDXVerifier.s.sol new file mode 100644 index 000000000..c0d8a8832 --- /dev/null +++ b/scripts/multiproof/DeployTDXVerifier.s.sol @@ -0,0 +1,32 @@ +// 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. + */ + +import { Script } from "forge-std/Script.sol"; + +import { TDXVerifier } from "src/L1/proofs/tee/TDXVerifier.sol"; + +contract DeployTDXVerifier is Script { + /// @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 risc0VerifierRouter, bytes32 tdxVerifierId, bytes32 intelRootCaHash) public { + vm.startBroadcast(); + + address tdxVerifier = address(new TDXVerifier(1 hours, intelRootCaHash, risc0VerifierRouter, tdxVerifierId)); + + vm.stopBroadcast(); + + string memory json = vm.serializeAddress("deployment", "TDXVerifier", tdxVerifier); + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-tdx-verifier.json"); + vm.writeJson(json, outPath); + } +} diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 166f86d16..3b1de9679 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -1,204 +1,48 @@ # Multiproof Deployment Guide This guide covers deploying the multiproof contracts and registering a prover on Sepolia. +Deploy recipes print addresses and write `deployments/-*.json`. ---- +## Dev/Test Scripts Only -## ⚠️ Dev/Test Scripts Only +The scripts in this directory are **development and testing tools only**. They are not suitable for production deployments. They use the simplified `MockAnchorStateRegistry`; the NoNitro path also bypasses AWS Nitro attestation checking through `MockDevTEEProverRegistry.addDevSigner()`. -The scripts in this directory are **development and testing tools only**. They are not suitable for production deployments. Specifically, the NoNitro path (`DeployDevNoNitro.s.sol`): +## Run a Deploy Recipe -- Does **no AWS Nitro attestation checking**. Instead it uses a bypass function for quickly registering provers: [`MockDevTEEProverRegistry.addDevSigner()`](https://github.com/base/contracts/blob/main/test/mocks/MockDevTEEProverRegistry.sol#L22) -- Uses a simplified mock `AnchorStateRegistry` (with some differences from the real one): [`MockAnchorStateRegistry`](https://github.com/base/contracts/blob/main/scripts/multiproof/mocks/MockAnchorStateRegistry.sol) +See `scripts/multiproof/justfile` for defaults, config paths, and optional anchor-block arguments. ---- +| Path | Recipe | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NoNitro dev, no attestation | `just --justfile scripts/multiproof/justfile deploy-no-nitro-stack` | +| TDX dev/test verifier policies | `just --justfile scripts/multiproof/justfile deploy-nitro-verifier $NITRO_ROOT_CERT $NITRO_VERIFIER_ID`
`just --justfile scripts/multiproof/justfile deploy-tdx-verifier` | +| TDX dev/test stack | `just --justfile scripts/multiproof/justfile deploy-tdx-stack $NITRO_VERIFIER $TDX_VERIFIER` | -## Prerequisites +## Register Signers -Install dependencies if you haven't already (required after any `lib/` changes): +### NoNitro Dev Signers -```bash -just deps -``` - ---- - -## Path 1: NoNitro (Dev — No Attestation) - -Use this when you don't have access to an AWS Nitro enclave and want to quickly test the prover without attestation overhead. - -### Step 1: Configure `deploy-config/sepolia.json` - -Ensure `finalSystemOwner` is set to the address you will deploy from (i.e. the address on your Ledger at the HD path you intend to use). This address becomes the owner of all deployed contracts and must sign all subsequent admin calls. - -```json -{ - "finalSystemOwner": "0xYOUR_DEPLOYER_ADDRESS", - ... -} -``` - -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 | - -### Step 2: Deploy contracts - -```bash -DEPLOY_CONFIG_PATH=deploy-config/sepolia.json forge script scripts/multiproof/DeployDevNoNitro.s.sol --rpc-url https://sepolia.base.org --broadcast --ledger --hd-paths "m/44'/60'/1'/0/0" -``` - -On success, deployed addresses are printed to the console and saved to `deployments/-dev-no-nitro.json`. You will need the `AnchorStateRegistry` and `TEEProverRegistry` addresses for the steps below. - -### Step 3: Set the anchor state - -The proving system needs a recent anchor state to catch up to chain tip. Set this immediately after deployment using a fresh block. - -```bash -# 1. Get the latest L2 block number -BLOCK=$(cast block-number --rpc-url https://base-sepolia-archive-k8s-dev.cbhq.net:8545) - -# 2. Get the output root at that block -OUTPUT_ROOT=$(cast rpc optimism_outputAtBlock $(cast 2h $BLOCK) --rpc-url https://base-sepolia-archive-k8s-dev.cbhq.net:7545 | jq -r '.outputRoot') - -# 3. Set the anchor state on the deployed MockAnchorStateRegistry -# Replace 0x983b... with the AnchorStateRegistry address from your deployment output -cast send 0x983bD53AE522C74F1d505fb3A55d5d5B774573A7 \ - "setAnchorState(bytes32,uint256)" $OUTPUT_ROOT $BLOCK \ - --rpc-url https://c3-chainproxy-eth-sepolia-full-dev.cbhq.net \ - --ledger --mnemonic-derivation-path "m/44'/60'/1'/0/0" -``` - -> **Note:** `MockAnchorStateRegistry.setAnchorState()` has no access control — any address can call it. - -### Step 4: Get the enclave signer public key - -Query the enclave for its signer public key: - -```bash -cast rpc enclave_signerPublicKey -r https://base-proofs-prover-nitro-dev.cbhq.net -``` - -This returns a raw byte array representing an uncompressed secp256k1 public key (65 bytes, starting with `0x04`). To convert it to an Ethereum address, strip the `0x04` prefix byte, keccak256-hash the remaining 64 bytes, and take the last 20 bytes: - -```bash -# Example — replace the array with the actual bytes returned by enclave_signerPublicKey -% PUB_KEY_HEX=$(python3 -c 'data=[4,100,32,206,76,214,221,167,247,152,244,81,135,139,245,114,92,16,194,181,5,126,180,170,159,214,176,6,51,103,228,117,224,176,243,160,107,112,6,214,20,46,169,42,75,45,190,178,224,54,111,208,42,6,11,198,138,118,144,226,1,147,38,86,196]; print("0x" + bytes(data[1:]).hex())') -% HASH=$(cast keccak $PUB_KEY_HEX) -% RAW_ADDRESS="0x${HASH: -40}" -% cast to-check-sum-address $RAW_ADDRESS -0x0cbe4A965B41DA6B2D5AF4d53c0C16a37d6f9F7D -``` - -### Step 5: Register the dev signer - -Call `addDevSigner` on the deployed `DevTEEProverRegistry` with the **signer address** derived in Step 4. +Register each 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. +The registry stores signer image hashes; `AggregateVerifier` enforces the expected Nitro and TDX image hashes. ```bash -# Replace: -# 0x587d... with the TEEProverRegistry address from your deployment output -# 0x080f... with the signer address derived in Step 4 -cast send 0x587d410B205449fB889EC4a5b351D375C656d084 \ - "addDevSigner(address)" \ - 0x080f42420846c613158D7b4334257C78bE5A9B90 \ - --rpc-url https://c3-chainproxy-eth-sepolia-full-dev.cbhq.net \ - --ledger --mnemonic-derivation-path "m/44'/60'/1'/0/0" -``` - -The deployer address (`finalSystemOwner`) is the owner of `DevTEEProverRegistry` and must sign this call. - ---- - -## Path 2: WithNitro (Dev — Real Attestation) - -> **TODO:** Add deployment and registration guide for `DeployDevWithNitro.s.sol`. - ---- - -## 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. - -Games are created using `ProofType.ZK` with the `MockVerifier` (deployed by both WithNitro and NoNitro), which auto-accepts any proof. The output roots themselves are real values fetched from an L2 archive node. +cast send "$TEE_PROVER_REGISTRY" "addDevSigner(address,bytes32,uint8)" "$NITRO_SIGNER" "$NITRO_IMAGE_HASH" 1 \ + --rpc-url "$L1_RPC_URL" --ledger --mnemonic-derivation-path "$LEDGER_PATH" -### Step 1: Set the anchor state - -Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers `BLOCK_INTERVAL` (600) L2 blocks, so for 500 games you need 300,000 blocks of headroom. - -```bash -# Calculate an anchor block 300,000 blocks behind the L2 tip -ANCHOR_BLOCK=$(( $(cast block-number --rpc-url $L2_RPC_URL) - 300000 )) - -# Get the real output root at that block -OUTPUT_ROOT=$(cast rpc optimism_outputAtBlock $(printf "0x%x" $ANCHOR_BLOCK) \ - --rpc-url $L2_RPC_URL | jq -r '.outputRoot') - -# Set it on the MockAnchorStateRegistry (no access control — any caller works) -cast send $ANCHOR_STATE_REGISTRY_ADDRESS \ - "setAnchorState(bytes32,uint256)" $OUTPUT_ROOT $ANCHOR_BLOCK \ - --rpc-url $L1_RPC_URL --private-key $PRIVATE_KEY +cast send "$TEE_PROVER_REGISTRY" "addDevSigner(address,bytes32,uint8)" "$TDX_SIGNER" "$TDX_IMAGE_HASH" 2 \ + --rpc-url "$L1_RPC_URL" --ledger --mnemonic-derivation-path "$LEDGER_PATH" ``` -### Step 2: Generate real output roots - -Fetch the real L2 output roots for every intermediate block across all games. This queries `optimism_outputAtBlock` on the L2 archive node (10,000 queries for 500 games, parallelized). +The deployer address (`finalSystemOwner`) is the owner of `DevTEEProverRegistry` and must sign these calls. -```bash -./scripts/multiproof/generate-roots.sh $ANCHOR_BLOCK $L2_RPC_URL 500 -``` +### TDX Dev/Test Signers -Arguments: ` [game_count] [parallelism] [output_file]` - -Defaults: `game_count=500`, `parallelism=20`, `output_file=roots.json`. - -### Step 3: Move roots file for Foundry access - -Foundry's filesystem sandbox only allows reads from paths listed in `foundry.toml` `fs_permissions`. The `deployments/` directory already has read-write access, so move the file there: - -```bash -mv roots.json deployments/roots.json -``` - -### Step 4: Run the seeding script - -Create all games on-chain. Each game is chained to the previous one (game 0's parent is the `AnchorStateRegistry`, game N's parent is game N-1). The account running this needs enough ETH for bonds and gas (500 games at 0.00001 ETH bond = 0.005 ETH + gas). +Register each signer with its proof output. ```bash -ROOTS_FILE=./deployments/roots.json \ -FACTORY_ADDRESS=$FACTORY_ADDRESS \ -ANCHOR_STATE_REGISTRY_ADDRESS=$ANCHOR_STATE_REGISTRY_ADDRESS \ -forge script scripts/multiproof/SeedGames.s.sol \ - --rpc-url $L1_RPC_URL --broadcast --private-key $PRIVATE_KEY -``` - -> **Note:** Use `--private-key` instead of `--ledger` to avoid manually confirming 500 transactions on a hardware wallet. - -Optional env vars: - -| Variable | Default | Description | -| ------------ | ------------ | ----------------------------- | -| `GAME_COUNT` | 500 | Number of games to create | -| `ROOTS_FILE` | `roots.json` | Path to the output roots JSON | +cast send "$TEE_PROVER_REGISTRY" "registerSigner(bytes,bytes)" "$NITRO_OUTPUT" "$NITRO_PROOF_BYTES" \ + --rpc-url "$L1_RPC_URL" --private-key "$PRIVATE_KEY" -### Step 5: Verify on-chain - -```bash -# Check total game count -cast call $FACTORY_ADDRESS "gameCount()(uint256)" --rpc-url $L1_RPC_URL - -# Check first game's parent is the AnchorStateRegistry -FIRST_GAME=$(cast call $FACTORY_ADDRESS \ - "gameAtIndex(uint256)(uint32,uint64,address)" 0 --rpc-url $L1_RPC_URL | tail -1) -cast call $FIRST_GAME "parentAddress()(address)" --rpc-url $L1_RPC_URL +cast send "$TEE_PROVER_REGISTRY" "registerTDXSigner(bytes,bytes)" "$TDX_OUTPUT" "$TDX_PROOF_BYTES" \ + --rpc-url "$L1_RPC_URL" --private-key "$PRIVATE_KEY" ``` - -Output metadata is saved to `deployments/-seeded-games.json`. diff --git a/scripts/multiproof/SeedGames.s.sol b/scripts/multiproof/SeedGames.s.sol index e04121dbd..91b734dff 100644 --- a/scripts/multiproof/SeedGames.s.sol +++ b/scripts/multiproof/SeedGames.s.sol @@ -26,7 +26,7 @@ import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; /// All transactions must confirm within the 256-block blockhash window of the /// L1 origin captured at simulation time. For large counts, use --slow. contract SeedGames is Script { - /// @notice Must match the AggregateVerifier deployment constants from DeployDevWithNitro/NoNitro. + /// @notice Must match the AggregateVerifier deployment constants from DeployDevWithTDX/NoNitro. uint256 public constant BLOCK_INTERVAL = 600; uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30; uint256 public constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; diff --git a/scripts/multiproof/justfile b/scripts/multiproof/justfile new file mode 100644 index 000000000..9fc6c4ba1 --- /dev/null +++ b/scripts/multiproof/justfile @@ -0,0 +1,97 @@ +set dotenv-load +set working-directory := "../.." +set shell := ["bash", "-euo", "pipefail", "-c"] + +forge_account := "testnet-admin" +owner := `cast wallet address --account testnet-admin` +broadcast_args := "--rpc-url " + quote(l1_rpc_url) + " --broadcast --account " + quote(forge_account) + " --sender " + quote(owner) +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-reth-proofs-k8s-donotuse.cbhq.net:8545") +l2_output_root_rpc_url := env_var_or_default("L2_OUTPUT_ROOT_RPC_URL", "https://base-sepolia-reth-internal-rpc-donotuse.cbhq.net:7545") + +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" + +default: + +# Deploy the TDXVerifier policy contract. +deploy-tdx-verifier: + forge script scripts/multiproof/DeployTDXVerifier.s.sol:DeployTDXVerifier \ + --sig "run(address,bytes32,bytes32)" \ + "{{ sepolia_tdx_risc0_verifier_router }}" \ + "{{ sepolia_tdx_verifier_id }}" \ + "{{ sepolia_intel_root_ca_hash }}" \ + {{ broadcast_args }} + +# Deploy the NitroEnclaveVerifier policy contract and its local RISC Zero set verifier route. +deploy-nitro-verifier nitro_root_cert nitro_verifier_id: + forge script scripts/multiproof/DeployNitroVerifier.s.sol:DeployNitroVerifier \ + --sig "run(address,address,bytes32,bytes32,bytes32,bytes32)" \ + "{{ owner }}" \ + "{{ sepolia_nitro_risc0_verifier_router }}" \ + "{{ sepolia_risc0_set_builder_image_id }}" \ + "{{ nitro_root_cert }}" \ + "{{ nitro_verifier_id }}" \ + "0x0000000000000000000000000000000000000000000000000000000000000000" \ + {{ broadcast_args }} + +# Deploy the NoNitro multiproof dev/test stack using the Sepolia config. +[script] +deploy-no-nitro-stack asr_anchor_block="": + asr_anchor_block="$([[ -n "{{ asr_anchor_block }}" ]] && printf '%s' "{{ asr_anchor_block }}" || cast block-number --rpc-url "{{ l2_rpc_url }}")" + asr_anchor_output_root="$(cast rpc optimism_outputAtBlock "$(cast to-hex "$asr_anchor_block")" --rpc-url "{{ l2_output_root_rpc_url }}" | jq -er '.outputRoot')" + + if [[ "$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/sepolia.json" \ + forge script scripts/multiproof/DeployDevNoNitro.s.sol:DeployDevNoNitro \ + --sig "run(bytes32,uint256)" \ + "$asr_anchor_output_root" \ + "$asr_anchor_block" \ + {{ broadcast_args }} + +# Deploy the TDX multiproof dev/test stack using the zeronet TDX config. +[script] +deploy-tdx-stack nitro_verifier tdx_verifier asr_anchor_block="": + asr_anchor_block="$([[ -n "{{ asr_anchor_block }}" ]] && printf '%s' "{{ asr_anchor_block }}" || cast block-number --rpc-url "{{ l2_rpc_url }}")" + asr_anchor_output_root="$(cast rpc optimism_outputAtBlock "$(cast to-hex "$asr_anchor_block")" --rpc-url "{{ l2_output_root_rpc_url }}" | jq -er '.outputRoot')" + + if [[ "$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/zeronet-tdx.json" \ + forge script scripts/multiproof/DeployDevWithTDX.s.sol:DeployDevWithTDX \ + --sig "run(address,address,address,bytes32,uint256)" \ + "{{ nitro_verifier }}" \ + "{{ tdx_verifier }}" \ + "0x44E999A5859c2D12378a349882fAe5805DCE71b9" \ + "$asr_anchor_output_root" \ + "$asr_anchor_block" \ + {{ broadcast_args }} + +# Deploy the TDX multiproof dev/test stack on Sepolia. +[script] +deploy-sepolia-tdx-dev asr_anchor_block="": + nitro_verifier="$(jq -er '.nitroEnclaveVerifier' deploy-config/zeronet-tdx.json)" + tdx_verifier="$(jq -er '.tdxVerifier' deploy-config/zeronet-tdx.json)" + + just --justfile scripts/multiproof/justfile deploy-tdx-stack \ + "$nitro_verifier" \ + "$tdx_verifier" \ + "{{ asr_anchor_block }}" diff --git a/scripts/multiproof/mocks/MinimalProxyAdmin.sol b/scripts/multiproof/mocks/MinimalProxyAdmin.sol deleted file mode 100644 index ccf9b20dd..000000000 --- a/scripts/multiproof/mocks/MinimalProxyAdmin.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/// @title MinimalProxyAdmin -/// @notice Minimal contract to satisfy DisputeGameFactory's proxy admin check. -/// @dev ProxyAdminOwnedBase.proxyAdminOwner() reads `owner()` off the proxy admin, so this -/// exposes an `owner` getter set to the expected initializer caller. -contract MinimalProxyAdmin { - address public owner; - - constructor(address initialOwner) { - owner = initialOwner; - } -} diff --git a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol index bb4282d59..59451f3cb 100644 --- a/scripts/multiproof/mocks/MockAnchorStateRegistry.sol +++ b/scripts/multiproof/mocks/MockAnchorStateRegistry.sol @@ -15,6 +15,7 @@ import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; contract MockAnchorStateRegistry { Hash public anchorRoot; uint256 public anchorL2BlockNumber; + IDisputeGame public anchorGame; address public factory; GameType public respectedGameType; @@ -33,6 +34,10 @@ contract MockAnchorStateRegistry { } function getAnchorRoot() external view returns (Hash, uint256) { + if (address(anchorGame) != address(0)) { + return (Hash.wrap(anchorGame.rootClaim().raw()), anchorGame.l2SequenceNumber()); + } + return (anchorRoot, anchorL2BlockNumber); } @@ -45,6 +50,7 @@ contract MockAnchorStateRegistry { } function setAnchorState(Hash newAnchorRoot, uint256 newAnchorL2BlockNumber) external { + anchorGame = IDisputeGame(address(0)); anchorRoot = newAnchorRoot; anchorL2BlockNumber = newAnchorL2BlockNumber; } @@ -79,5 +85,7 @@ contract MockAnchorStateRegistry { return _game.resolvedAt().raw() != 0; } - function setAnchorState(IDisputeGame) external { } + function setAnchorState(IDisputeGame game) external { + anchorGame = game; + } } diff --git a/snapshots/abi/AggregateVerifier.json b/snapshots/abi/AggregateVerifier.json index d8ee465e9..65e82fb17 100644 --- a/snapshots/abi/AggregateVerifier.json +++ b/snapshots/abi/AggregateVerifier.json @@ -28,7 +28,12 @@ }, { "internalType": "bytes32", - "name": "teeImageHash", + "name": "teeNitroImageHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "teeTdxImageHash", "type": "bytes32" }, { @@ -230,7 +235,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/TDXVerifier.json b/snapshots/abi/TDXVerifier.json new file mode 100644 index 000000000..d1eaad43a --- /dev/null +++ b/snapshots/abi/TDXVerifier.json @@ -0,0 +1,270 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "initialMaxTimeDiff", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "initialRootCaHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "initialRiscZeroVerifier", + "type": "address", + "internalType": "address" + }, + { + "name": "initialVerifierId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "maxTimeDiff", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "riscZeroVerifier", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "rootCaHash", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "verifierId", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "verify", + "inputs": [ + { + "name": "output", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "proofBytes", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "journal", + "type": "tuple", + "internalType": "struct TDXVerifierJournal", + "components": [ + { + "name": "success", + "type": "bool", + "internalType": "bool" + }, + { + "name": "tcbStatus", + "type": "uint8", + "internalType": "enum TDXTcbStatus" + }, + { + "name": "timestamp", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "collateralExpiration", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "rootCaHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "publicKey", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "signer", + "type": "address", + "internalType": "address" + }, + { + "name": "imageHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "reportDataPrefix", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "reportDataSuffix", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "version", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "pure" + }, + { + "type": "error", + "name": "CollateralExpired", + "inputs": [ + { + "name": "collateralExpiration", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTimestamp", + "inputs": [ + { + "name": "timestampSeconds", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "currentTimestamp", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ReportDataMismatch", + "inputs": [ + { + "name": "expected", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actual", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "RootCaHashMismatch", + "inputs": [ + { + "name": "expected", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "actual", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "SignerMismatch", + "inputs": [ + { + "name": "expected", + "type": "address", + "internalType": "address" + }, + { + "name": "actual", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "TDXVerificationFailed", + "inputs": [] + }, + { + "type": "error", + "name": "TcbStatusNotAllowed", + "inputs": [ + { + "name": "status", + "type": "uint8", + "internalType": "enum TDXTcbStatus" + } + ] + }, + { + "type": "error", + "name": "ZeroInput", + "inputs": [] + } +] diff --git a/snapshots/abi/TEEProverRegistry.json b/snapshots/abi/TEEProverRegistry.json index 510962c5d..5fcb635b8 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": [ { @@ -81,7 +99,13 @@ "type": "function" }, { - "inputs": [], + "inputs": [ + { + "internalType": "enum TEEProverRegistry.TEEType", + "name": "teeType", + "type": "uint8" + } + ], "name": "getExpectedImageHash", "outputs": [ { @@ -235,6 +259,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "output", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proofBytes", + "type": "bytes" + } + ], + "name": "registerTDXSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "renounceManagement", @@ -299,6 +341,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": [ { @@ -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 a0b3f4d4e..accd591f6 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -28,8 +28,8 @@ "sourceCodeHash": "0xf122a50487efe9bd5a620262ba20ef4adbca14eeec2af7fd32e6e16739001596" }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x90629c679e821a70553d899a68e818bcddb672a004a0d2e09078d17028a2f082", - "sourceCodeHash": "0x4835d3de998b2951898f0e427c61b0c330ac3feb5f7a8dc9fd0deb78912a9b7a" + "initCodeHash": "0xa01943ea100f6b04897860ebf21bb54fda9ec3b9b08b9aedc5496e6739a46920", + "sourceCodeHash": "0x6962d4835853937f304681679e43929a9a58f5873c80b658b0c3975e696736be" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", @@ -47,13 +47,17 @@ "initCodeHash": "0xf79cd59d23f6a5ad78960b664e20779592d73bfcf52527c3e03adda04edd9645", "sourceCodeHash": "0x03c164216b27f82ee13064ace6079d5e24e187d888f41bd01c8daffa60c575d6" }, + "src/L1/proofs/tee/TDXVerifier.sol:TDXVerifier": { + "initCodeHash": "0x14ec4c0f99e2c957fa5dc7284fd74c9e24347c68557b01719f0709f08f4bda0b", + "sourceCodeHash": "0x28bb714117e586feaafdc8846c82615fa3b106d256426fef12133f8c4a3dcd70" + }, "src/L1/proofs/tee/TEEProverRegistry.sol:TEEProverRegistry": { - "initCodeHash": "0xfd1942e1c2f59b0aa72b33d698a948a53b6e4cf1040106f173fb5d89f63f57b0", - "sourceCodeHash": "0x7dc91bb80f476c50d3e1e39fc29316a216dd03eebe25b79c936a5a3d2f595489" + "initCodeHash": "0xa21dc34428175e2781ba786b87542da46bb39318f7001b31606387df8a94fde9", + "sourceCodeHash": "0xcdbe2ba1aeac4e3dc6a4e138bda7bd3dd9d072b7d419f5a8af4f3e9a4a045217" }, "src/L1/proofs/tee/TEEVerifier.sol:TEEVerifier": { - "initCodeHash": "0x655576cc21cc5c603d55fb4dd2a2f0ef57b11deeaabd3e539b0a70a5f7e2c9af", - "sourceCodeHash": "0x6c1d839e48cc8497af7b0d74a9566e76000599b14d7db7b3163a69e774a91bb3" + "initCodeHash": "0x5f42455c6d41c0a49e05681a6fa7dbf21eb22c89705fd11b6030952b3227e559", + "sourceCodeHash": "0xcd8358734ff27b0c17af73713b7f3d2b0f348ba0646efc356345bad9fa8d6a8c" }, "src/L1/proofs/zk/ZKVerifier.sol:ZKVerifier": { "initCodeHash": "0xcdc5bd5fa8ffad76358b8f34a53b8c0cb36540176c31f82d52546373d0bd5245", @@ -135,4 +139,4 @@ "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } -} \ No newline at end of file +} diff --git a/snapshots/storageLayout/TDXVerifier.json b/snapshots/storageLayout/TDXVerifier.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/snapshots/storageLayout/TDXVerifier.json @@ -0,0 +1 @@ +[] 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/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index a468dc058..1ce56c675 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -66,6 +66,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The minimum number of proofs required to resolve the game. uint256 public constant PROOF_THRESHOLD = 1; + //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -81,8 +82,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; @@ -253,7 +257,8 @@ 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 teeNitroImageHash The hash of the Nitro TEE image. + /// @param teeTdxImageHash The hash of the TDX TEE image. /// @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. @@ -265,7 +270,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { IDelayedWETH delayedWETH, IVerifier teeVerifier, IVerifier zkVerifier, - bytes32 teeImageHash, + bytes32 teeNitroImageHash, + bytes32 teeTdxImageHash, ZkHashes memory zkHashes, bytes32 configHash, uint256 l2ChainId, @@ -284,7 +290,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { DELAYED_WETH = delayedWETH; TEE_VERIFIER = teeVerifier; ZK_VERIFIER = zkVerifier; - TEE_IMAGE_HASH = teeImageHash; + TEE_NITRO_IMAGE_HASH = teeNitroImageHash; + TEE_TDX_IMAGE_HASH = teeTdxImageHash; ZK_RANGE_HASH = zkHashes.rangeHash; ZK_AGGREGATE_HASH = zkHashes.aggregateHash; CONFIG_HASH = configHash; @@ -875,7 +882,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, @@ -889,23 +896,34 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { internal view { - bytes32 journal = keccak256( - abi.encodePacked( - proposer, - l1OriginHash, - startingRoot, - startingL2SequenceNumber, - endingRoot, - endingL2SequenceNumber, - intermediateRoots, - CONFIG_HASH, - TEE_IMAGE_HASH - ) + if (proofBytes.length != 130) revert InvalidProof(); + + bytes memory journalPrefix = abi.encodePacked( + proposer, + l1OriginHash, + startingRoot, + startingL2SequenceNumber, + endingRoot, + endingL2SequenceNumber, + intermediateRoots, + CONFIG_HASH ); - // Validate the proof. - bytes memory proof = abi.encodePacked(proposer, proofBytes); - if (!TEE_VERIFIER.verify(proof, TEE_IMAGE_HASH, journal)) revert InvalidProof(); + if (!TEE_VERIFIER.verify( + abi.encodePacked(proposer, proofBytes[:65]), + TEE_NITRO_IMAGE_HASH, + keccak256(abi.encodePacked(journalPrefix, TEE_NITRO_IMAGE_HASH)) + )) { + revert InvalidProof(); + } + + if (!TEE_VERIFIER.verify( + abi.encodePacked(proposer, proofBytes[65:130]), + TEE_TDX_IMAGE_HASH, + keccak256(abi.encodePacked(journalPrefix, TEE_TDX_IMAGE_HASH)) + )) { + revert InvalidProof(); + } } /// @notice Verifies a ZK proof for the current game. @@ -1034,8 +1052,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/L1/proofs/tee/TDXVerifier.sol b/src/L1/proofs/tee/TDXVerifier.sol new file mode 100644 index 000000000..b24f503fb --- /dev/null +++ b/src/L1/proofs/tee/TDXVerifier.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ITDXVerifier, TDXTcbStatus, TDXVerifierJournal } from "interfaces/L1/proofs/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 onchain policy over the verified journal. +contract TDXVerifier is ITDXVerifier, ISemver { + /// @notice Maximum accepted age of a TDX quote, in seconds. + uint64 public immutable maxTimeDiff; + + /// @notice Hash of the trusted Intel root CA used by the ZK verifier guest. + bytes32 public immutable rootCaHash; + + /// @notice RISC Zero verifier router. + IRiscZeroVerifier public immutable riscZeroVerifier; + + /// @notice RISC Zero image ID for the TDX DCAP verifier guest. + bytes32 public immutable verifierId; + + error ZeroInput(); + error RootCaHashMismatch(bytes32 expected, bytes32 actual); + error TcbStatusNotAllowed(TDXTcbStatus status); + error CollateralExpired(uint64 collateralExpiration); + error InvalidTimestamp(uint64 timestampSeconds, uint256 currentTimestamp); + error ReportDataMismatch(bytes32 expected, bytes32 actual); + + constructor( + uint64 initialMaxTimeDiff, + bytes32 initialRootCaHash, + address initialRiscZeroVerifier, + bytes32 initialVerifierId + ) { + if ( + initialMaxTimeDiff == 0 || initialRootCaHash == bytes32(0) || initialRiscZeroVerifier == address(0) + || initialVerifierId == bytes32(0) + ) revert ZeroInput(); + maxTimeDiff = initialMaxTimeDiff; + rootCaHash = initialRootCaHash; + riscZeroVerifier = IRiscZeroVerifier(initialRiscZeroVerifier); + verifierId = initialVerifierId; + } + + /// @inheritdoc ITDXVerifier + function verify( + bytes calldata output, + bytes calldata proofBytes + ) + external + view + returns (address signer, bytes32 imageHash) + { + riscZeroVerifier.verify(proofBytes, verifierId, sha256(output)); + TDXVerifierJournal memory journal = abi.decode(output, (TDXVerifierJournal)); + + if (journal.rootCaHash != rootCaHash) revert RootCaHashMismatch(rootCaHash, journal.rootCaHash); + if (journal.tcbStatus != TDXTcbStatus.UpToDate && journal.tcbStatus != TDXTcbStatus.SwHardeningNeeded) { + revert TcbStatusNotAllowed(journal.tcbStatus); + } + if (journal.collateralExpiration <= block.timestamp) revert CollateralExpired(journal.collateralExpiration); + + uint64 timestamp = journal.timestamp / 1000; + if (timestamp + maxTimeDiff <= block.timestamp || timestamp >= block.timestamp) { + revert InvalidTimestamp(timestamp, block.timestamp); + } + + bytes32 publicKeyHash = keccak256(abi.encodePacked(journal.publicKeyX, journal.publicKeyY)); + if (journal.reportDataPrefix != publicKeyHash) { + revert ReportDataMismatch(publicKeyHash, journal.reportDataPrefix); + } + + return (address(uint160(uint256(publicKeyHash))), journal.imageHash); + } + + /// @notice Semantic version. + /// @custom:semver 0.3.0 + function version() public pure virtual returns (string memory) { + return "0.3.0"; + } +} diff --git a/src/L1/proofs/tee/TEEProverRegistry.sol b/src/L1/proofs/tee/TEEProverRegistry.sol index 84d64efc5..228a80810 100644 --- a/src/L1/proofs/tee/TEEProverRegistry.sol +++ b/src/L1/proofs/tee/TEEProverRegistry.sol @@ -9,6 +9,7 @@ import { Pcr, Bytes48 } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { ITDXVerifier } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { OwnableManagedUpgradeable } from "src/universal/OwnableManagedUpgradeable.sol"; @@ -16,15 +17,22 @@ import { EnumerableSetLib } from "src/vendor/EnumerableSetLib.sol"; import { GameType } from "src/libraries/bridge/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 +/// 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. +/// 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 { + NONE, + NITRO, + TDX + } + /// @notice Maximum age of an attestation document (60 minutes), in seconds. uint256 public constant MAX_AGE = 60 minutes; @@ -36,7 +44,10 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @notice The external NitroEnclaveVerifier contract used for ZK attestation verification. INitroEnclaveVerifier public immutable NITRO_VERIFIER; - /// @notice The DisputeGameFactory used to look up the current AggregateVerifier and its TEE_IMAGE_HASH. + /// @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 hashes. IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY; /// @notice The game type used to look up the AggregateVerifier in the factory. @@ -48,10 +59,10 @@ 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. + /// this for offchain pre-submission checks. mapping(address => bytes32) public signerImageHash; /// @notice Mapping of whether an address is a valid proposer. @@ -59,9 +70,12 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { /// @notice Enumerable set of all currently registered signer addresses. /// @dev Kept in sync with `isRegisteredSigner`: add on register, remove on deregister. - /// Enables O(1) on-chain enumeration via `getRegisteredSigners()`. + /// Enables O(1) onchain 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); @@ -89,15 +103,20 @@ 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(); - 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), @@ -116,17 +135,14 @@ 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 { - // Validate the new game type points to a valid AggregateVerifier with a TEE_IMAGE_HASH - GameType oldGameType = gameType; + if ( + _getExpectedImageHash(gameType_, TEEType.NITRO) == bytes32(0) + || _getExpectedImageHash(gameType_, TEEType.TDX) == bytes32(0) + ) revert InvalidGameType(); gameType = gameType_; - bytes32 imageHash = _getExpectedImageHash(); - if (imageHash == bytes32(0)) { - gameType = oldGameType; - revert InvalidGameType(); - } emit GameTypeUpdated(gameType_); } @@ -136,9 +152,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 onchain 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. @@ -151,26 +167,28 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { // the attestation is generated and when it is submitted to this contract. if (journal.timestamp / MS_PER_SECOND + MAX_AGE <= block.timestamp) revert AttestationTooOld(); - // Extract the attestation's PCR0 and store it for TEEVerifier to check at - // proof-submission time. No comparison against the current TEE_IMAGE_HASH - // here — the registry accepts any valid attestation. bytes32 pcr0Hash = _extractPCR0Hash(journal.pcrs); - // The publicKey is encoded in ANSI X9.62 format: 0x04 || x || y (65 bytes). - // We skip the first byte (0x04 prefix) when hashing to derive the address. + // publicKey is ANSI X9.62 uncompressed: 0x04 || x || y (65 bytes). Skip the prefix and hash + // only the 64-byte x||y to derive the signer address. bytes memory pubKey = journal.publicKey; if (pubKey.length != 65) revert InvalidPublicKey(); bytes32 publicKeyHash; assembly { - // Length is hardcoded to 64 to skip the 0x04 prefix and hash only the x and y coordinates publicKeyHash := keccak256(add(pubKey, 0x21), 64) } address enclaveAddress = address(uint160(uint256(publicKeyHash))); - isRegisteredSigner[enclaveAddress] = true; - signerImageHash[enclaveAddress] = pcr0Hash; - _registeredSigners.add(enclaveAddress); - emit SignerRegistered(enclaveAddress); + _registerSigner(enclaveAddress, pcr0Hash, TEEType.NITRO); + } + + /// @notice Registers a signer using a ZK proof of Intel TDX DCAP quote verification. + /// @param output ABI-encoded TDXVerifierJournal public values from the ZK verifier guest. + /// @param proofBytes ZK proof bytes. + function registerTDXSigner(bytes calldata output, bytes calldata proofBytes) external onlyOwnerOrManager { + (address signer, bytes32 imageHash) = TDX_VERIFIER.verify(output, proofBytes); + + _registerSigner(signer, imageHash, TEEType.TDX); } /// @notice Deregisters a signer. @@ -178,32 +196,33 @@ 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); } /// @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) { - return isRegisteredSigner[signer] && signerImageHash[signer] == _getExpectedImageHash(); + TEEType teeType = signerTEEType[signer]; + return teeType != TEEType.NONE && signerImageHash[signer] == _getExpectedImageHash(gameType, teeType); } /// @notice Returns all currently registered signer addresses. - /// @dev Reads directly from the on-chain enumerable set — no event scanning required. + /// @dev Reads directly from the onchain enumerable set — no event scanning required. /// The order of addresses in the returned array is not guaranteed. /// @return An array of all registered signer addresses. function getRegisteredSigners() external view returns (address[] memory) { 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(); + /// @notice Returns the expected TEE image hash for the given TEE type from the current AggregateVerifier. + function getExpectedImageHash(TEEType teeType) external view returns (bytes32) { + return _getExpectedImageHash(gameType, teeType); } /// @notice Initializes the contract with owner, manager, proposers, and game type. @@ -233,16 +252,27 @@ contract TEEProverRegistry is OwnableManagedUpgradeable, ISemver { } /// @notice Semantic version. - /// @custom:semver 0.5.0 + /// @custom:semver 0.7.0 function version() public pure virtual returns (string memory) { - return "0.5.0"; + return "0.7.0"; + } + + /// @dev Registers a signer and stores the image hash enforced by TEEVerifier at proof-submission time. + function _registerSigner(address signer, bytes32 imageHash, TEEType teeType) internal { + isRegisteredSigner[signer] = true; + signerImageHash[signer] = imageHash; + signerTEEType[signer] = teeType; + _registeredSigners.add(signer); + emit SignerRegistered(signer); } - /// @dev Reads TEE_IMAGE_HASH from the AggregateVerifier registered in the factory. - function _getExpectedImageHash() internal view returns (bytes32) { - address impl = address(DISPUTE_GAME_FACTORY.gameImpls(gameType)); - // AggregateVerifier.TEE_IMAGE_HASH() selector - (bool success, bytes memory data) = impl.staticcall(abi.encodeWithSignature("TEE_IMAGE_HASH()")); + /// @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) { + address impl = address(DISPUTE_GAME_FACTORY.gameImpls(gameType_)); + 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 4fb94f1a8..e4aceb88c 100644 --- a/src/L1/proofs/tee/TEEVerifier.sol +++ b/src/L1/proofs/tee/TEEVerifier.sol @@ -10,19 +10,17 @@ 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 -/// 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 { /// @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 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. @@ -50,8 +48,7 @@ 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 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. + /// @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( @@ -65,30 +62,21 @@ contract TEEVerifier is Verifier, ISemver { notNullified returns (bool) { - if (proofBytes.length < 85) revert InvalidProofFormat(); + if (proofBytes.length != 85) revert InvalidProofFormat(); address proposer = address(bytes20(proofBytes[0:20])); - bytes calldata signature = proofBytes[20:85]; - - // Recover the signer from the signature - // The signature should be over the journal hash directly (not eth-signed-message prefixed) - (address signer, ECDSA.RecoverError err) = ECDSA.tryRecover(journal, signature); - - if (err != ECDSA.RecoverError.NoError) { - revert InvalidSignature(); - } - if (!TEE_PROVER_REGISTRY.isValidProposer(proposer)) { revert InvalidProposer(proposer); } - // Check that the signer is registered - if (!TEE_PROVER_REGISTRY.isRegisteredSigner(signer)) { - revert InvalidSigner(signer); - } + // The signature should be over the journal hash directly (not eth-signed-message prefixed). + bytes calldata signature = proofBytes[20:85]; + (address signer, ECDSA.RecoverError err) = ECDSA.tryRecover(journal, signature); + if (err != ECDSA.RecoverError.NoError) revert InvalidSignature(); + + if (!TEE_PROVER_REGISTRY.isRegisteredSigner(signer)) 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) { @@ -99,8 +87,8 @@ contract TEEVerifier is Verifier, ISemver { } /// @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/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index bf93e25f4..062c7c6fb 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -86,6 +86,7 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), bytes32(uint256(1)), + bytes32(uint256(1)), AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), bytes32(uint256(4)), deploy.cfg().l2ChainId(), diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index 30907b20e..1065a59e5 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -2,178 +2,133 @@ pragma solidity 0.8.15; import { BadExtraData, GameNotResolved } from "src/libraries/bridge/Errors.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 { GameStatus, GameTypes, Hash } from "src/libraries/bridge/Types.sol"; -import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; +import { GameStatus, GameTypes } from "src/libraries/bridge/Types.sol"; +import { Claim, Hash } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; -import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; - -import { LibClone } from "lib/solady/src/utils/LibClone.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { - using LibClone for address; - - AggregateVerifier private aggregateVerifierImpl; - - function setUp() public override { - super.setUp(); - aggregateVerifierImpl = AggregateVerifier(address(factory.gameImpls(GameTypes.AGGREGATE_VERIFIER))); - } - function testInitializeWithTEEProof() public { - _createAndAssertInitializedGame( - "tee-proof", AggregateVerifier.ProofType.TEE, TEE_PROVER, TEE_PROVER, address(0) - ); + AggregateVerifier game = + _createGame(TEE_PROVER, _advanceL2BlockAndClaim(), "tee-proof", AggregateVerifier.ProofType.TEE); + + assertEq(game.teeProver(), TEE_PROVER); + assertEq(game.zkProver(), address(0)); + assertEq(game.bondRecipient(), TEE_PROVER); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); + assertEq(game.proofCount(), 1); } function testInitializeWithZKProof() public { - _createAndAssertInitializedGame("zk-proof", AggregateVerifier.ProofType.ZK, ZK_PROVER, address(0), ZK_PROVER); + AggregateVerifier game = + _createGame(ZK_PROVER, _advanceL2BlockAndClaim(), "zk-proof", AggregateVerifier.ProofType.ZK); + + assertEq(game.teeProver(), address(0)); + assertEq(game.zkProver(), ZK_PROVER); + assertEq(game.bondRecipient(), ZK_PROVER); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); + assertEq(game.proofCount(), 1); } function testInitializeFailsIfInvalidCallDataSize() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - - vm.deal(TEE_PROVER, INIT_BOND); - bytes memory extraData = ""; - bytes memory initData = ""; - - vm.prank(TEE_PROVER); + hoax(TEE_PROVER, INIT_BOND); vm.expectRevert(BadExtraData.selector); - factory.createWithInitData{ value: INIT_BOND }(GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData, initData); + factory.createWithInitData{ value: INIT_BOND }( + GameTypes.AGGREGATE_VERIFIER, _advanceL2BlockAndClaim(), hex"", hex"" + ); } function testUpdatingAnchorStateRegistryWithTEEProof() public { Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof - ); + AggregateVerifier game = _createGame(TEE_PROVER, rootClaim, "tee-proof", AggregateVerifier.ProofType.TEE); vm.expectRevert(GameNotResolved.selector); game.claimCredit(); - vm.warp(block.timestamp + aggregateVerifierImpl.SLOW_FINALIZATION_DELAY()); - game.resolve(); - _assertStatus(game, GameStatus.DEFENDER_WINS); - - _claimCreditAfterDelay(game); - - vm.warp(block.timestamp + 1); - game.closeGame(); - _assertAnchorRoot(rootClaim); + _resolveSlowAndClose(game, rootClaim); } function testUpdatingAnchorStateRegistryWithZKProof() public { Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - - AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof - ); + AggregateVerifier game = _createGame(ZK_PROVER, rootClaim, "zk-proof", AggregateVerifier.ProofType.ZK); - vm.warp(block.timestamp + aggregateVerifierImpl.SLOW_FINALIZATION_DELAY()); - game.resolve(); - _assertStatus(game, GameStatus.DEFENDER_WINS); - - _claimCreditAfterDelay(game); - - vm.warp(block.timestamp + 1); - game.closeGame(); - _assertAnchorRoot(rootClaim); + _resolveSlowAndClose(game, rootClaim); } function testUpdatingAnchorStateRegistryWithBothProofs() public { Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); + AggregateVerifier game = _createGame(TEE_PROVER, rootClaim, "tee-proof", AggregateVerifier.ProofType.TEE); - _provideProof(game, ZK_PROVER, zkProof); + _provideProof(game, ZK_PROVER, _generateProof("zk-proof", AggregateVerifier.ProofType.ZK)); assertEq(game.proofCount(), 2); - vm.warp(block.timestamp + aggregateVerifierImpl.FAST_FINALIZATION_DELAY()); - game.resolve(); - _assertStatus(game, GameStatus.DEFENDER_WINS); + vm.warp(block.timestamp + game.FAST_FINALIZATION_DELAY()); + assertEq(uint8(game.resolve()), uint8(GameStatus.DEFENDER_WINS)); vm.warp(block.timestamp + 1); game.closeGame(); _assertAnchorRoot(rootClaim); - _claimCreditAfterDelay(game); + _claimCreditAfterDelay(game, game.gameCreator()); } function testProofCannotIncreaseExpectedResolution() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - uint256 slowDelay = aggregateVerifierImpl.SLOW_FINALIZATION_DELAY(); + AggregateVerifier game = + _createGame(TEE_PROVER, _advanceL2BlockAndClaim(), "tee-proof", AggregateVerifier.ProofType.TEE); + uint256 slowDelay = game.SLOW_FINALIZATION_DELAY(); - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); - - Timestamp originalExpectedResolution = game.expectedResolution(); - assertEq(originalExpectedResolution.raw(), block.timestamp + slowDelay); + uint256 originalExpectedResolution = game.expectedResolution().raw(); + assertEq(originalExpectedResolution, block.timestamp + slowDelay); vm.warp(block.timestamp + slowDelay - 1); vm.expectRevert(AggregateVerifier.GameNotOver.selector); game.resolve(); - _provideProof(game, ZK_PROVER, zkProof); - assertEq(game.expectedResolution().raw(), originalExpectedResolution.raw()); + _provideProof(game, ZK_PROVER, _generateProof("zk-proof", AggregateVerifier.ProofType.ZK)); + assertEq(game.expectedResolution().raw(), originalExpectedResolution); vm.warp(block.timestamp + 1); - game.resolve(); - _assertStatus(game, GameStatus.DEFENDER_WINS); + assertEq(uint8(game.resolve()), uint8(GameStatus.DEFENDER_WINS)); } function testCannotCreateSameProposal() public { Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); + AggregateVerifier game = _createGame(TEE_PROVER, rootClaim, "tee-proof", AggregateVerifier.ProofType.TEE); - Hash gameId = factory.getGameUUID(GameTypes.AGGREGATE_VERIFIER, rootClaim, game.extraData()); - vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, gameId)); - _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), zkProof); + vm.expectRevert( + abi.encodeWithSelector( + IDisputeGameFactory.GameAlreadyExists.selector, + factory.getGameUUID(GameTypes.AGGREGATE_VERIFIER, rootClaim, game.extraData()) + ) + ); + _createGame(ZK_PROVER, rootClaim, "zk-proof", AggregateVerifier.ProofType.ZK); } - /// @notice Reverts when the parent is not factory-registered: `_isValidGame` requires - /// `AnchorStateRegistry.isGameRegistered`, which checks `DisputeGameFactory.games(...) == parent`. - /// @dev Parent is a real `AggregateVerifier` clone initialized like a factory game, but deployed without - /// `_finalizeGameCreation`, so the factory UUID mapping has no entry. function testInitializeFailsIfParentGameNotFactoryRegistered() public { - currentL2BlockNumber += BLOCK_INTERVAL; + Claim parentRootClaim = _advanceL2BlockAndClaim(); + AggregateVerifier unregisteredParent = + _createGame(TEE_PROVER, parentRootClaim, "parent-tee", AggregateVerifier.ProofType.TEE); - Claim parentRootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "parent"))); - AggregateVerifier unregisteredParent = _deployAggregateVerifierCloneWithoutFactoryRegistration( - TEE_PROVER, - parentRootClaim, - currentL2BlockNumber, - address(anchorStateRegistry), - _generateProof("parent-tee", AggregateVerifier.ProofType.TEE) + vm.mockCall( + address(factory), + abi.encodeCall( + IDisputeGameFactory.games, + (GameTypes.AGGREGATE_VERIFIER, parentRootClaim, unregisteredParent.extraData()) + ), + abi.encode(address(0), uint64(0)) ); currentL2BlockNumber += BLOCK_INTERVAL; - Claim childRootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "child"))); vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); _createAggregateVerifierGame( TEE_PROVER, - childRootClaim, + Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "child"))), currentL2BlockNumber, address(unregisteredParent), _generateProof("child-tee", AggregateVerifier.ProofType.TEE) @@ -181,145 +136,74 @@ contract AggregateVerifierTest is BaseTest { } function testVerifyFailsWithL1OriginInFuture() public { - Claim rootClaim = _advanceL2BlockAndClaim(); uint256 l1OriginNumber = block.number + 1; - bytes32 l1OriginHash = bytes32(uint256(1)); - bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); - - _expectCreateGameRevertsForTeeProof( - rootClaim, - proofBytes, + vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) ); + _createTEEGameWithOrigin(_advanceL2BlockAndClaim(), bytes32(uint256(1)), l1OriginNumber); } function testVerifyFailsWithL1OriginTooOld() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - vm.roll(block.number + 300); uint256 l1OriginNumber = 1; - bytes32 l1OriginHash = bytes32(uint256(1)); - - bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); - _expectCreateGameRevertsForTeeProof( - rootClaim, - proofBytes, - abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number) - ); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); + _createTEEGameWithOrigin(_advanceL2BlockAndClaim(), bytes32(uint256(1)), l1OriginNumber); } function testVerifyFailsWithL1OriginHashMismatch() public { - Claim rootClaim = _advanceL2BlockAndClaim(); uint256 l1OriginNumber = block.number - 1; bytes32 wrongHash = bytes32(uint256(0xdeadbeef)); - bytes memory proofBytes = _teeProof(wrongHash, l1OriginNumber, rootClaim); - - bytes32 actualHash = blockhash(l1OriginNumber); - _expectCreateGameRevertsForTeeProof( - rootClaim, - proofBytes, - abi.encodeWithSelector(AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash) + vm.expectRevert( + abi.encodeWithSelector( + AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, blockhash(l1OriginNumber) + ) ); + _createTEEGameWithOrigin(_advanceL2BlockAndClaim(), wrongHash, l1OriginNumber); } function testVerifyWithBlockhashWindow() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - vm.roll(block.number + 100); uint256 l1OriginNumber = block.number - 50; - bytes32 l1OriginHash = blockhash(l1OriginNumber); - - bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes - ); + _createTEEGameWithOrigin(_advanceL2BlockAndClaim(), blockhash(l1OriginNumber), l1OriginNumber); } function testVerifyWithEIP2935Window() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - vm.roll(block.number + 300); uint256 l1OriginNumber = block.number - 260; bytes32 expectedHash = keccak256(abi.encodePacked("mock-blockhash", l1OriginNumber)); - vm.mockCall(aggregateVerifierImpl.EIP2935_CONTRACT(), abi.encode(l1OriginNumber), abi.encode(expectedHash)); - - bytes memory proofBytes = _teeProof(expectedHash, l1OriginNumber, rootClaim); - - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes + vm.mockCall( + AggregateVerifier(address(factory.gameImpls(GameTypes.AGGREGATE_VERIFIER))).EIP2935_CONTRACT(), + abi.encode(l1OriginNumber), + abi.encode(expectedHash) ); - } - function testDeployWithInvalidBlockIntervals() public { - _expectDeployWithInvalidBlockIntervalsReverts(0, INTERMEDIATE_BLOCK_INTERVAL); - _expectDeployWithInvalidBlockIntervalsReverts(BLOCK_INTERVAL, 0); - _expectDeployWithInvalidBlockIntervalsReverts(3, 2); + _createTEEGameWithOrigin(_advanceL2BlockAndClaim(), expectedHash, l1OriginNumber); } - function _advanceL2BlockAndClaim() private returns (Claim rootClaim) { - currentL2BlockNumber += BLOCK_INTERVAL; - return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - } - - function _createAndAssertInitializedGame( - bytes memory proofSalt, - AggregateVerifier.ProofType proofType, - address prover, - address expectedTeeProver, - address expectedZkProver - ) - private - { - Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory proof = _generateProof(proofSalt, proofType); - - AggregateVerifier game = _createAggregateVerifierGame( - prover, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof + function testDeployWithInvalidBlockIntervals() public { + vm.expectRevert( + abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 0, INTERMEDIATE_BLOCK_INTERVAL) ); + _newAggregateVerifier(0, INTERMEDIATE_BLOCK_INTERVAL); - _assertInitializedGame(game, rootClaim, prover, expectedTeeProver, expectedZkProver); - } + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, BLOCK_INTERVAL, 0)); + _newAggregateVerifier(BLOCK_INTERVAL, 0); - function _assertInitializedGame( - AggregateVerifier game, - Claim rootClaim, - address expectedCreator, - address expectedTeeProver, - address expectedZkProver - ) - private - view - { - assertTrue(game.wasRespectedGameTypeWhenCreated()); - assertEq(game.teeProver(), expectedTeeProver); - assertEq(game.zkProver(), expectedZkProver); - _assertStatus(game, GameStatus.IN_PROGRESS); - assertEq(game.l2SequenceNumber(), currentL2BlockNumber); - assertEq(game.rootClaim().raw(), rootClaim.raw()); - assertEq(game.parentAddress(), address(anchorStateRegistry)); - assertEq(game.gameType().raw(), GameTypes.AGGREGATE_VERIFIER.raw()); - assertEq(game.gameCreator(), expectedCreator); - bytes memory intermediateOutputRoots = game.intermediateOutputRoots(); - assertEq( - game.extraData(), - abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), intermediateOutputRoots) - ); - assertEq(game.bondRecipient(), expectedCreator); - assertTrue(anchorStateRegistry.isGameProper(IDisputeGame(address(game)))); - assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); - assertEq(game.proofCount(), 1); + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 3, 2)); + _newAggregateVerifier(3, 2); } - function _assertStatus(AggregateVerifier game, GameStatus expectedStatus) private view { - assertEq(uint8(game.status()), uint8(expectedStatus)); + function _advanceL2BlockAndClaim() private returns (Claim rootClaim) { + currentL2BlockNumber += BLOCK_INTERVAL; + return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); } function _assertAnchorRoot(Claim rootClaim) private view { @@ -328,96 +212,43 @@ contract AggregateVerifierTest is BaseTest { assertEq(l2SequenceNumber, currentL2BlockNumber); } - function _claimCreditAfterDelay(AggregateVerifier game) private { - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); - } - - function _expectCreateGameRevertsForTeeProof( - Claim rootClaim, - bytes memory proofBytes, - bytes memory revertData - ) - private - { - vm.expectRevert(revertData); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes - ); - } + function _resolveSlowAndClose(AggregateVerifier game, Claim rootClaim) private { + vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); + assertEq(uint8(game.resolve()), uint8(GameStatus.DEFENDER_WINS)); - function _teeProof( - bytes32 l1OriginHash, - uint256 l1OriginNumber, - Claim rootClaim - ) - private - pure - returns (bytes memory) - { - return abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); - } + _claimCreditAfterDelay(game, game.gameCreator()); - function _expectDeployWithInvalidBlockIntervalsReverts( - uint256 blockInterval, - uint256 intermediateBlockInterval - ) - private - { - vm.expectRevert( - abi.encodeWithSelector( - AggregateVerifier.InvalidBlockInterval.selector, blockInterval, intermediateBlockInterval - ) - ); - _deployAggregateVerifierWithIntervals(blockInterval, intermediateBlockInterval); + vm.warp(block.timestamp + 1); + game.closeGame(); + _assertAnchorRoot(rootClaim); } - /// @notice Clones the implementation like the factory, but skips `_finalizeGameCreation`. - function _deployAggregateVerifierCloneWithoutFactoryRegistration( + function _createGame( address creator, Claim rootClaim, - uint256 l2BlockNumber, - address parentAddress, - bytes memory proof + bytes memory proofSalt, + AggregateVerifier.ProofType proofType ) private returns (AggregateVerifier) { - IDisputeGame impl = factory.gameImpls(GameTypes.AGGREGATE_VERIFIER); - bytes memory extraData = _aggregateVerifierExtraData(rootClaim, l2BlockNumber, parentAddress); - bytes32 l1Head = blockhash(block.number - 1); - address clone = address(impl).clone(abi.encodePacked(creator, rootClaim, l1Head, extraData)); - - vm.deal(creator, INIT_BOND); - vm.prank(creator); - AggregateVerifier(payable(clone)).initializeWithInitData{ value: INIT_BOND }(proof); - - return AggregateVerifier(payable(clone)); + return _createAggregateVerifierGame( + creator, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), _generateProof(proofSalt, proofType) + ); } - function _deployAggregateVerifierWithIntervals( - uint256 blockInterval, - uint256 intermediateBlockInterval - ) - private - returns (AggregateVerifier) - { - return new AggregateVerifier( - GameTypes.AGGREGATE_VERIFIER, - IAnchorStateRegistry(address(anchorStateRegistry)), - IDelayedWETH(payable(address(delayedWETH))), - IVerifier(address(teeVerifier)), - IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, - AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), - CONFIG_HASH, - L2_CHAIN_ID, - blockInterval, - intermediateBlockInterval + function _createTEEGameWithOrigin(Claim rootClaim, bytes32 l1OriginHash, uint256 l1OriginNumber) private { + _createAggregateVerifierGame( + TEE_PROVER, + rootClaim, + currentL2BlockNumber, + address(anchorStateRegistry), + abi.encodePacked( + uint8(AggregateVerifier.ProofType.TEE), + l1OriginHash, + l1OriginNumber, + _generateProofBody("tee-proof", AggregateVerifier.ProofType.TEE) + ) ); } } diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 2016de959..0baa234b2 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -29,22 +29,16 @@ contract BaseTest is Test { uint256 internal constant BLOCK_INTERVAL = 100; uint256 internal constant INTERMEDIATE_BLOCK_INTERVAL = 10; uint256 private constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; + uint256 internal constant LAST_INTERMEDIATE_ROOT_INDEX = INTERMEDIATE_ROOTS_COUNT - 1; uint256 internal constant INIT_BOND = 1 ether; uint256 internal constant DELAYED_WETH_DELAY = 1 days; - // Finality delay handled by the AggregateVerifier - uint256 internal constant FINALITY_DELAY = 0 days; uint256 internal currentL2BlockNumber; address internal immutable TEE_PROVER = makeAddr("tee-prover"); address internal immutable ZK_PROVER = makeAddr("zk-prover"); - bytes32 internal immutable TEE_IMAGE_HASH = keccak256("tee-image"); - bytes32 internal immutable ZK_RANGE_HASH = keccak256("zk-range"); - bytes32 internal immutable ZK_AGGREGATE_HASH = keccak256("zk-aggregate"); - bytes32 internal immutable CONFIG_HASH = keccak256("config"); - ProxyAdmin internal proxyAdmin; ISystemConfig internal systemConfig; @@ -56,43 +50,19 @@ contract BaseTest is Test { MockVerifier internal zkVerifier; function setUp() public virtual { - _deployContractsAndProxies(); - _initializeProxies(); - - _deployAndSetAggregateVerifier(); - - anchorStateRegistry.setRespectedGameType(GameTypes.AGGREGATE_VERIFIER); - - // Games created at or before the registry's retirement timestamp are invalid. - vm.warp(block.timestamp + 1); - } - - function _deployContractsAndProxies() internal { systemConfig = ISystemConfig(makeAddr("system-config")); vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.guardian, ()), abi.encode(address(this))); vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.paused, ()), abi.encode(false)); - AnchorStateRegistry _anchorStateRegistry = new AnchorStateRegistry(FINALITY_DELAY); - DelayedWETH _delayedWETH = new DelayedWETH(DELAYED_WETH_DELAY); - DisputeGameFactory _factory = new DisputeGameFactory(); - proxyAdmin = new ProxyAdmin(address(this)); - anchorStateRegistry = AnchorStateRegistry(_deployProxy(address(_anchorStateRegistry))); - factory = DisputeGameFactory(_deployProxy(address(_factory))); - delayedWETH = DelayedWETH(payable(_deployProxy(address(_delayedWETH)))); + anchorStateRegistry = AnchorStateRegistry(_deployProxy(address(new AnchorStateRegistry(0)))); + factory = DisputeGameFactory(_deployProxy(address(new DisputeGameFactory()))); + delayedWETH = DelayedWETH(payable(_deployProxy(address(new DelayedWETH(DELAYED_WETH_DELAY))))); teeVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); zkVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); - } - function _deployProxy(address implementation) private returns (address) { - Proxy proxy = new Proxy(address(proxyAdmin)); - proxyAdmin.upgrade(payable(address(proxy)), implementation); - return address(proxy); - } - - function _initializeProxies() internal { anchorStateRegistry.initialize( systemConfig, IDisputeGameFactory(address(factory)), @@ -103,25 +73,46 @@ contract BaseTest is Test { ); factory.initialize(address(this)); delayedWETH.initialize(systemConfig); + + factory.setImplementation( + GameTypes.AGGREGATE_VERIFIER, + IDisputeGame(address(_newAggregateVerifier(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL))) + ); + factory.setInitBond(GameTypes.AGGREGATE_VERIFIER, INIT_BOND); + + anchorStateRegistry.setRespectedGameType(GameTypes.AGGREGATE_VERIFIER); + + // Games created at or before the registry's retirement timestamp are invalid. + vm.warp(block.timestamp + 1); } - function _deployAndSetAggregateVerifier() internal { - AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( + function _deployProxy(address implementation) private returns (address) { + Proxy proxy = new Proxy(address(proxyAdmin)); + proxyAdmin.upgrade(payable(address(proxy)), implementation); + return address(proxy); + } + + function _newAggregateVerifier( + uint256 blockInterval, + uint256 intermediateBlockInterval + ) + internal + returns (AggregateVerifier) + { + return new AggregateVerifier( GameTypes.AGGREGATE_VERIFIER, IAnchorStateRegistry(address(anchorStateRegistry)), IDelayedWETH(payable(address(delayedWETH))), IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, - AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), - CONFIG_HASH, + keccak256("tee-nitro-image"), + keccak256("tee-tdx-image"), + AggregateVerifier.ZkHashes(keccak256("zk-range"), keccak256("zk-aggregate")), + keccak256("config"), L2_CHAIN_ID, - BLOCK_INTERVAL, - INTERMEDIATE_BLOCK_INTERVAL + blockInterval, + intermediateBlockInterval ); - - factory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(aggregateVerifierImpl))); - factory.setInitBond(GameTypes.AGGREGATE_VERIFIER, INIT_BOND); } function _createAggregateVerifierGame( @@ -132,9 +123,10 @@ contract BaseTest is Test { bytes memory proof ) internal - returns (AggregateVerifier game) + returns (AggregateVerifier) { - bytes memory extraData = _aggregateVerifierExtraData(rootClaim, l2BlockNumber, parentAddress); + bytes memory extraData = + abi.encodePacked(l2BlockNumber, parentAddress, _generateIntermediateRoots(l2BlockNumber, rootClaim)); vm.deal(creator, INIT_BOND); vm.prank(creator); @@ -147,11 +139,40 @@ contract BaseTest is Test { ); } + function _createGame( + address prover, + bytes memory claimSalt, + bytes memory proofSalt, + AggregateVerifier.ProofType proofType, + address parent + ) + internal + returns (AggregateVerifier) + { + currentL2BlockNumber += BLOCK_INTERVAL; + return _createAggregateVerifierGame( + prover, _claim(claimSalt), currentL2BlockNumber, parent, _generateProof(proofSalt, proofType) + ); + } + + function _claim(bytes memory salt) internal view returns (Claim) { + return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); + } + function _provideProof(AggregateVerifier game, address prover, bytes memory proofBytes) internal { vm.prank(prover); game.verifyProposalProof(proofBytes); } + function _claimCreditAfterDelay(AggregateVerifier game, address recipient) internal { + uint256 balanceBefore = recipient.balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(recipient.balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + } + /// @dev Encodes proofType || l1OriginHash || l1OriginNumber || mock verifier payload. function _generateProof( bytes memory salt, @@ -162,30 +183,44 @@ contract BaseTest is Test { returns (bytes memory) { uint256 l1OriginNumber = block.number - 1; - bytes32 l1OriginHash = blockhash(l1OriginNumber); + return abi.encodePacked( + uint8(proofType), blockhash(l1OriginNumber), l1OriginNumber, _generateProofBody(salt, proofType) + ); + } - return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, salt, bytes32(0), bytes32(0), uint8(27)); + function _generateProposalProof( + bytes memory salt, + AggregateVerifier.ProofType proofType + ) + internal + pure + returns (bytes memory) + { + return abi.encodePacked(uint8(proofType), _generateProofBody(salt, proofType)); } - function _aggregateVerifierExtraData( - Claim rootClaim, - uint256 l2BlockNumber, - address parentAddress + function _generateProofBody( + bytes memory salt, + AggregateVerifier.ProofType proofType ) internal pure returns (bytes memory) { - return abi.encodePacked(l2BlockNumber, parentAddress, _generateIntermediateRoots(l2BlockNumber, rootClaim)); + 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 _generateIntermediateRoots(uint256 l2BlockNumber, Claim rootClaim) private pure returns (bytes memory) { + function _generateIntermediateRoots(uint256 l2BlockNumber, Claim rootClaim) internal pure returns (bytes memory) { bytes32[] memory intermediateRoots = new bytes32[](INTERMEDIATE_ROOTS_COUNT); uint256 startingL2BlockNumber = l2BlockNumber - BLOCK_INTERVAL; for (uint256 i = 1; i < INTERMEDIATE_ROOTS_COUNT; i++) { intermediateRoots[i - 1] = keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)); } - intermediateRoots[INTERMEDIATE_ROOTS_COUNT - 1] = rootClaim.raw(); + intermediateRoots[LAST_INTERMEDIATE_ROOT_INDEX] = rootClaim.raw(); return abi.encodePacked(intermediateRoots); } diff --git a/test/L1/proofs/Challenge.t.sol b/test/L1/proofs/Challenge.t.sol index 0aca7e4d2..1fcb36675 100644 --- a/test/L1/proofs/Challenge.t.sol +++ b/test/L1/proofs/Challenge.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/libraries/bridge/Errors.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { GameStatus } from "src/libraries/bridge/Types.sol"; -import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { Verifier } from "src/L1/proofs/Verifier.sol"; @@ -12,17 +11,14 @@ import { Verifier } from "src/L1/proofs/Verifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract ChallengeTest is BaseTest { - uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - function testChallengeTEEProofWithZKProof() public { - AggregateVerifier game = - _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); + AggregateVerifier game = _rootTee(TEE_PROVER, "tee", "tee-proof"); _challengeWithZk(game, "zk"); assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); assertEq(game.proofCount(), 2); - _resolveAfterSlowDelayAndClaim(game, GameStatus.CHALLENGER_WINS, ZK_PROVER); + _resolveSlow(game, GameStatus.CHALLENGER_WINS, ZK_PROVER); } function testChallengeFailsIfNoTEEProof() public { @@ -36,17 +32,14 @@ contract ChallengeTest is BaseTest { } function testChallengeFailsIfNotZKProof() public { - AggregateVerifier game = _createGame( - TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) - ); + AggregateVerifier game = _rootTee(TEE_PROVER, "tee1", "tee-proof-1"); vm.expectRevert(AggregateVerifier.InvalidProofType.selector); _challenge(game, AggregateVerifier.ProofType.TEE, bytes32(0)); } function testChallengeFailsIfGameAlreadyResolved() public { - AggregateVerifier game = - _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); + AggregateVerifier game = _rootTee(TEE_PROVER, "tee", "tee-proof"); vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY() + 1); game.resolve(); @@ -56,9 +49,7 @@ contract ChallengeTest is BaseTest { } function testChallengeFailsIfParentGameStatusIsChallenged() public { - AggregateVerifier parentGame = _createGame( - TEE_PROVER, "tee", "parent-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) - ); + AggregateVerifier parentGame = _rootTee(TEE_PROVER, "tee", "parent-proof"); AggregateVerifier childGame = _createGame(TEE_PROVER, "tee2", "child-proof", AggregateVerifier.ProofType.TEE, address(parentGame)); @@ -70,8 +61,7 @@ contract ChallengeTest is BaseTest { } function testChallengeFailsIfGameItselfIsBlacklisted() public { - AggregateVerifier game = - _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); + AggregateVerifier game = _rootTee(TEE_PROVER, "tee", "tee-proof"); anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); @@ -80,11 +70,10 @@ contract ChallengeTest is BaseTest { } function testChallengeFailsAfterTEENullification() public { - AggregateVerifier game = _createGame( - TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) - ); + AggregateVerifier game = _rootTee(TEE_PROVER, "tee1", "tee-proof-1"); _nullify(game, AggregateVerifier.ProofType.TEE, "tee2"); + vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); @@ -92,80 +81,69 @@ contract ChallengeTest is BaseTest { } function testChallengeFailsAfterZKNullification() public { - AggregateVerifier game = - _createGame(ZK_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); + AggregateVerifier game = _rootTee(ZK_PROVER, "tee", "tee-proof"); - _provideProof(game, ZK_PROVER, _proofOfType(AggregateVerifier.ProofType.ZK)); + _provideProof(game, ZK_PROVER, abi.encodePacked(uint8(AggregateVerifier.ProofType.ZK), bytes1(0))); _nullify(game, AggregateVerifier.ProofType.ZK, "zk2"); vm.expectRevert(Verifier.Nullified.selector); _challenge(game, AggregateVerifier.ProofType.ZK, _claim("zk3").raw()); } function testChallengeRemovedWhenZkVerifierNullifiedByOtherGame() public { - AggregateVerifier gameA = _createGame( - TEE_PROVER, "tee-challenge", "tee-ch-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) - ); + AggregateVerifier gameA = _rootTee(TEE_PROVER, "tee-challenge", "tee-ch-a"); _challengeWithZk(gameA, "zk-challenge"); - _assertChallengeRecorded(gameA); + assertEq(gameA.proofCount(), 2); + assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); AggregateVerifier gameB = _createGame(ZK_PROVER, "zk-only-b", "zk-init-b", AggregateVerifier.ProofType.ZK, address(gameA)); _nullify(gameB, AggregateVerifier.ProofType.ZK, "zk-nullify-b"); assertTrue(zkVerifier.nullified()); - _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); + assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); assertEq(gameA.proofCount(), 1); assertEq(gameA.counteredByIntermediateRootIndexPlusOne(), 0); - assertEq(address(gameA.zkProver()), address(0)); + assertEq(gameA.zkProver(), address(0)); - _resolveAfterSlowDelayAndClaim(gameA, GameStatus.DEFENDER_WINS, TEE_PROVER); + _resolveSlow(gameA, GameStatus.DEFENDER_WINS, TEE_PROVER); } function testChallengeWinsWhenSharedTeeVerifierNullifiedByOtherGame() public { - AggregateVerifier gameA = _createGame( - TEE_PROVER, - "tee-challenge-tee-null", - "tee-proof-a", - AggregateVerifier.ProofType.TEE, - address(anchorStateRegistry) - ); + AggregateVerifier gameA = _rootTee(TEE_PROVER, "tee-challenge-tee-null", "tee-proof-a"); _challengeWithZk(gameA, "zk-challenge"); - _assertChallengeRecorded(gameA); + assertEq(gameA.proofCount(), 2); + assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); AggregateVerifier gameB = - _createGame(TEE_PROVER, "tee-only-b", "tee-init-b", AggregateVerifier.ProofType.TEE, address(gameA)); - _nullify(gameB, AggregateVerifier.ProofType.TEE, "tee-nullify-b"); + _createGame(TEE_PROVER, "tee-nullify-b", "tee-proof-b", AggregateVerifier.ProofType.TEE, address(gameA)); + + _nullify(gameB, AggregateVerifier.ProofType.TEE, "tee-nullify-b-root"); assertTrue(teeVerifier.nullified()); - _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); + assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); assertEq(gameA.proofCount(), 1); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); - assertEq(address(gameA.teeProver()), address(0)); + assertEq(gameA.teeProver(), address(0)); assertEq(gameA.zkProver(), ZK_PROVER); - _resolveAfterSlowDelayAndClaim(gameA, GameStatus.CHALLENGER_WINS, ZK_PROVER); + _resolveSlow(gameA, GameStatus.CHALLENGER_WINS, ZK_PROVER); } - function _createGame( + function _rootTee( address prover, bytes memory claimSalt, - bytes memory proofSalt, - AggregateVerifier.ProofType proofType, - address parent + bytes memory proofSalt ) private returns (AggregateVerifier) { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = _claim(claimSalt); - bytes memory proof = _generateProof(proofSalt, proofType); - return _createAggregateVerifierGame(prover, rootClaim, currentL2BlockNumber, parent, proof); + return _createGame(prover, claimSalt, proofSalt, AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); } function _challenge(AggregateVerifier game, AggregateVerifier.ProofType proofType, bytes32 claimRoot) private { - game.challenge(_proofOfType(proofType), LAST_INTERMEDIATE_ROOT_INDEX, claimRoot); + game.challenge(abi.encodePacked(uint8(proofType), bytes1(0)), LAST_INTERMEDIATE_ROOT_INDEX, claimRoot); } function _challengeWithZk(AggregateVerifier game, bytes memory claimSalt) private { @@ -174,45 +152,15 @@ contract ChallengeTest is BaseTest { } function _nullify(AggregateVerifier game, AggregateVerifier.ProofType proofType, bytes memory claimSalt) private { - game.nullify(_proofOfType(proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw()); - } - - function _proofOfType(AggregateVerifier.ProofType proofType) private pure returns (bytes memory) { - return abi.encodePacked(uint8(proofType), bytes1(0)); - } - - function _claim(bytes memory salt) private view returns (Claim) { - return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); - } - - function _assertChallengeRecorded(AggregateVerifier game) private view { - assertEq(game.proofCount(), 2); - assertGt(game.counteredByIntermediateRootIndexPlusOne(), 0); - } - - function _resolveAndAssertStatus(AggregateVerifier game, GameStatus expectedStatus) private { - assertEq(uint8(game.resolve()), uint8(expectedStatus)); + game.nullify( + _generateProposalProof(claimSalt, proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw() + ); } - function _resolveAfterSlowDelayAndClaim( - AggregateVerifier game, - GameStatus expectedStatus, - address recipient - ) - private - { + function _resolveSlow(AggregateVerifier game, GameStatus expectedStatus, address expectedBondRecipient) private { vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); - _resolveAndAssertStatus(game, expectedStatus); - assertEq(game.bondRecipient(), recipient); - _claimCreditAfterDelay(game, recipient); - } - - function _claimCreditAfterDelay(AggregateVerifier game, address recipient) private { - uint256 balanceBefore = recipient.balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(recipient.balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); + assertEq(uint8(game.resolve()), uint8(expectedStatus)); + assertEq(game.bondRecipient(), expectedBondRecipient); + _claimCreditAfterDelay(game, expectedBondRecipient); } } diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index 6cab78c26..835ee9f8b 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -226,6 +226,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { IVerifier(address(teeVerifier)), IVerifier(address(zkVerifier)), bytes32(uint256(1)), + bytes32(uint256(1)), AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), bytes32(uint256(4)), L2_CHAIN_ID, diff --git a/test/L1/proofs/Nullify.t.sol b/test/L1/proofs/Nullify.t.sol index 841bc8cca..141837b34 100644 --- a/test/L1/proofs/Nullify.t.sol +++ b/test/L1/proofs/Nullify.t.sol @@ -3,37 +3,18 @@ pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/libraries/bridge/Errors.sol"; import { GameStatus } from "src/libraries/bridge/Types.sol"; -import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract NullifyTest is BaseTest { - uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - uint256 private constant NO_PROOF_CREDIT_CLAIM_DELAY = 14 days; - function testNullifyWithTEEProof() public { - AggregateVerifier game = _createGame( - TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) - ); - - _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "tee2"); - _assertNullifiedToNoProofs(game, TEE_PROVER); - - vm.warp(block.timestamp + NO_PROOF_CREDIT_CLAIM_DELAY); - _claimCreditAfterDelay(game); + _assertNullifyWithProof(TEE_PROVER, AggregateVerifier.ProofType.TEE); } function testNullifyWithZKProof() public { - AggregateVerifier game = - _createGame(ZK_PROVER, "zk1", "zk-proof-1", AggregateVerifier.ProofType.ZK, address(anchorStateRegistry)); - - _nullify(game, "zk-proof-2", AggregateVerifier.ProofType.ZK, "zk2"); - _assertNullifiedToNoProofs(game, ZK_PROVER); - - vm.warp(block.timestamp + NO_PROOF_CREDIT_CLAIM_DELAY); - _claimCreditAfterDelay(game); + _assertNullifyWithProof(ZK_PROVER, AggregateVerifier.ProofType.ZK); } function testNullifyWithTEEProofWhenTEEAndZKProofsAreProvided() public { @@ -47,7 +28,7 @@ contract NullifyTest is BaseTest { _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "tee2"); - _assertStatus(game, GameStatus.IN_PROGRESS); + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); assertEq(game.bondRecipient(), TEE_PROVER); assertEq(game.proofCount(), 1); assertEq(game.expectedResolution().raw(), block.timestamp + game.SLOW_FINALIZATION_DELAY()); @@ -90,20 +71,17 @@ contract NullifyTest is BaseTest { vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); game.resolve(); - _claimCreditAfterDelay(game); + _claimCreditAfterDelay(game, game.gameCreator()); } function testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame() public { - _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(TEE_PROVER, AggregateVerifier.ProofType.TEE); + _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(AggregateVerifier.ProofType.TEE); } function testResolveEarlyReturnWhenSharedZkVerifierNullifiedByAnotherGame() public { - _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(ZK_PROVER, AggregateVerifier.ProofType.ZK); + _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(AggregateVerifier.ProofType.ZK); } - /// @notice With TEE + ZK, the fast window is 1 day. Another game nullifies the shared ZK verifier; the first - /// `resolve` persists the ZK refutation and returns `IN_PROGRESS`. After `SLOW_FINALIZATION_DELAY` - /// from that moment, a second `resolve` finalizes with only the TEE proof. function testTwoProofsResolveDelayedAfterExternalVerifierNullify() public { AggregateVerifier gameA = _createGame( TEE_PROVER, "dual-a", "tee-dual-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) @@ -111,17 +89,12 @@ contract NullifyTest is BaseTest { _provideProof(gameA, ZK_PROVER, _generateProof("zk-dual-a", AggregateVerifier.ProofType.ZK)); - assertEq(gameA.proofCount(), 2); - assertEq(gameA.expectedResolution().raw(), block.timestamp + gameA.FAST_FINALIZATION_DELAY()); - vm.warp(block.timestamp + gameA.FAST_FINALIZATION_DELAY()); - assertTrue(gameA.gameOver()); AggregateVerifier gameB = _createGame(ZK_PROVER, "dual-b", "zk-dual-b", AggregateVerifier.ProofType.ZK, address(gameA)); _nullify(gameB, "zk-nullify-dual", AggregateVerifier.ProofType.ZK, "dual-nullify-b"); - assertTrue(zkVerifier.nullified()); assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); assertEq(gameA.proofCount(), 1); @@ -129,27 +102,19 @@ contract NullifyTest is BaseTest { vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS)); - _assertStatus(gameA, GameStatus.DEFENDER_WINS); } - function _createGame( - address prover, - bytes memory claimSalt, - bytes memory proofSalt, - AggregateVerifier.ProofType proofType, - address parent - ) - private - returns (AggregateVerifier) - { - currentL2BlockNumber += BLOCK_INTERVAL; - return _createAggregateVerifierGame( - prover, _claim(claimSalt), currentL2BlockNumber, parent, _generateProof(proofSalt, proofType) - ); - } + function _assertNullifyWithProof(address prover, AggregateVerifier.ProofType proofType) private { + AggregateVerifier game = _createGame(prover, "claim", "proof-1", proofType, address(anchorStateRegistry)); - function _claim(bytes memory salt) private view returns (Claim) { - return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); + _nullify(game, "proof-2", proofType, "nullify-claim"); + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + assertEq(game.bondRecipient(), prover); + assertEq(game.proofCount(), 0); + assertEq(game.expectedResolution().raw(), type(uint64).max); + + vm.warp(block.timestamp + 14 days); + _claimCreditAfterDelay(game, game.gameCreator()); } function _nullify( @@ -160,44 +125,22 @@ contract NullifyTest is BaseTest { ) private { - game.nullify(_generateProof(proofSalt, proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw()); - } - - function _assertNullifiedToNoProofs(AggregateVerifier game, address expectedBondRecipient) private view { - _assertStatus(game, GameStatus.IN_PROGRESS); - assertEq(game.bondRecipient(), expectedBondRecipient); - assertEq(game.proofCount(), 0); - assertEq(game.expectedResolution().raw(), type(uint64).max); - } - - function _assertStatus(AggregateVerifier game, GameStatus expectedStatus) private view { - assertEq(uint8(game.status()), uint8(expectedStatus)); + game.nullify( + _generateProposalProof(proofSalt, proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw() + ); } - /// @notice When a shared verifier is nullified by another game, `resolve` persists the refutation and returns - /// early `IN_PROGRESS` instead of reverting. - function _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame( - address prover, - AggregateVerifier.ProofType proofType - ) + function _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(AggregateVerifier.ProofType proofType) private { + address prover = proofType == AggregateVerifier.ProofType.TEE ? TEE_PROVER : ZK_PROVER; AggregateVerifier gameA = _createGame(prover, "game-a", "proof-a", proofType, address(anchorStateRegistry)); AggregateVerifier gameB = _createGame(prover, "game-b", "proof-b", proofType, address(gameA)); vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - assertTrue(gameA.gameOver()); - assertEq(gameA.proofCount(), 1); _nullify(gameB, "nullify-proof", proofType, "nullify-claim"); - if (proofType == AggregateVerifier.ProofType.TEE) { - assertTrue(teeVerifier.nullified()); - } else { - assertTrue(zkVerifier.nullified()); - } - assertEq(gameA.proofCount(), 1); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); assertEq(gameA.proofCount(), 0); assertEq(gameA.expectedResolution().raw(), type(uint64).max); @@ -205,14 +148,4 @@ contract NullifyTest is BaseTest { vm.expectRevert(AggregateVerifier.GameNotOver.selector); gameA.resolve(); } - - function _claimCreditAfterDelay(AggregateVerifier game) private { - address recipient = game.gameCreator(); - uint256 balanceBefore = recipient.balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(recipient.balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); - } } diff --git a/test/L1/proofs/TDXVerifier.t.sol b/test/L1/proofs/TDXVerifier.t.sol new file mode 100644 index 000000000..479a8ddbe --- /dev/null +++ b/test/L1/proofs/TDXVerifier.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; + +import { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; + +import { TDXTcbStatus, TDXVerifierJournal } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; + +import { TDXVerifier } from "src/L1/proofs/tee/TDXVerifier.sol"; + +contract TDXVerifierTest is Test { + TDXVerifier internal verifier; + + address internal constant MOCK_RISC_ZERO_VERIFIER = address(0x1234); + + bytes32 internal constant ROOT_CA_HASH = keccak256("intel-root-ca"); + bytes32 internal constant IMAGE_HASH = keccak256("tdx-image"); + bytes32 internal constant PUBLIC_KEY_X = bytes32(uint256(1)); + bytes32 internal constant PUBLIC_KEY_Y = bytes32(uint256(2)); + + uint64 internal constant MAX_TIME_DIFF = 3600; + + function setUp() public { + vm.warp(1_700_000_000); + + verifier = new TDXVerifier(MAX_TIME_DIFF, ROOT_CA_HASH, MOCK_RISC_ZERO_VERIFIER, keccak256("tdx-verifier-id")); + vm.mockCall(MOCK_RISC_ZERO_VERIFIER, abi.encodeWithSelector(IRiscZeroVerifier.verify.selector), ""); + } + + function testVerifySucceedsWithRiscZeroProofAndAllowedJournal() public view { + (address signer, bytes32 imageHash) = verifier.verify(abi.encode(_successJournal()), hex"1234"); + + assertEq(signer, address(uint160(uint256(_publicKeyHash())))); + assertEq(imageHash, IMAGE_HASH); + } + + function testConstructorRevertsIfZeroInput() public { + vm.expectRevert(TDXVerifier.ZeroInput.selector); + new TDXVerifier(MAX_TIME_DIFF, ROOT_CA_HASH, address(0), keccak256("tdx-verifier-id")); + } + + function testVerifyRevertsWhenRootCaHashMismatches() public { + TDXVerifierJournal memory journal = _successJournal(); + journal.rootCaHash = keccak256("wrong-root-ca"); + + _expectVerifyRevert(journal, TDXVerifier.RootCaHashMismatch.selector); + } + + function testVerifyRevertsWhenTcbStatusIsNotAllowed() public { + TDXVerifierJournal memory journal = _successJournal(); + journal.tcbStatus = TDXTcbStatus.ConfigurationNeeded; + + _expectVerifyRevert(journal, TDXVerifier.TcbStatusNotAllowed.selector); + } + + function testVerifyRevertsWhenCollateralExpired() public { + TDXVerifierJournal memory journal = _successJournal(); + journal.collateralExpiration = uint64(block.timestamp); + + _expectVerifyRevert(journal, TDXVerifier.CollateralExpired.selector); + } + + function testVerifyRevertsWhenTimestampTooOld() public { + TDXVerifierJournal memory journal = _successJournal(); + journal.timestamp = uint64(block.timestamp - MAX_TIME_DIFF) * 1000; + + _expectVerifyRevert(journal, TDXVerifier.InvalidTimestamp.selector); + } + + function testVerifyRevertsWhenTimestampIsFromFuture() public { + TDXVerifierJournal memory journal = _successJournal(); + journal.timestamp = uint64(block.timestamp) * 1000; + + _expectVerifyRevert(journal, TDXVerifier.InvalidTimestamp.selector); + } + + function testVerifyRevertsWhenReportDataDoesNotBindPublicKey() public { + TDXVerifierJournal memory journal = _successJournal(); + journal.reportDataPrefix = keccak256("wrong-report-data"); + + _expectVerifyRevert(journal, TDXVerifier.ReportDataMismatch.selector); + } + + function _successJournal() internal view returns (TDXVerifierJournal memory journal) { + journal = TDXVerifierJournal({ + tcbStatus: TDXTcbStatus.UpToDate, + timestamp: uint64(block.timestamp - 1) * 1000, + collateralExpiration: uint64(block.timestamp + 1 days), + rootCaHash: ROOT_CA_HASH, + publicKeyX: PUBLIC_KEY_X, + publicKeyY: PUBLIC_KEY_Y, + imageHash: IMAGE_HASH, + reportDataPrefix: _publicKeyHash() + }); + } + + function _publicKeyHash() internal pure returns (bytes32) { + return keccak256(abi.encodePacked(PUBLIC_KEY_X, PUBLIC_KEY_Y)); + } + + function _expectVerifyRevert(TDXVerifierJournal memory journal, bytes4 expectedRevert) internal { + vm.expectPartialRevert(expectedRevert); + verifier.verify(abi.encode(journal), hex"1234"); + } +} diff --git a/test/L1/proofs/TEEProverRegistry.t.sol b/test/L1/proofs/TEEProverRegistry.t.sol index 079f4635f..047c98cae 100644 --- a/test/L1/proofs/TEEProverRegistry.t.sol +++ b/test/L1/proofs/TEEProverRegistry.t.sol @@ -6,73 +6,44 @@ import { Test } from "lib/forge-std/src/Test.sol"; import { Proxy } from "src/universal/Proxy.sol"; import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; +import { ITDXVerifier } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { GameType } from "src/libraries/bridge/Types.sol"; -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; - import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; -contract MockAggregateVerifierForRegistry { - bytes32 public immutable TEE_IMAGE_HASH; - - constructor(bytes32 imageHash) { - TEE_IMAGE_HASH = imageHash; - } -} - -contract MockDisputeGameFactoryForRegistry { - IDisputeGame internal immutable _impl; - - constructor(address impl) { - _impl = IDisputeGame(impl); - } - - function gameImpls(GameType) external view returns (IDisputeGame) { - return _impl; - } -} +bytes32 constant TEST_IMAGE_HASH = keccak256("test-image-hash"); /// @dev Uses DevTEEProverRegistry because production signer registration requires a Nitro attestation proof. contract TEEProverRegistryTest is Test { - DevTEEProverRegistry public teeProverRegistry; - - address public owner; - address public manager; - address public unauthorized; + DevTEEProverRegistry internal teeProverRegistry; - bytes32 public constant TEST_IMAGE_HASH = keccak256("test-image-hash"); - GameType public constant TEST_GAME_TYPE = GameType.wrap(621); - string internal constant NOT_OWNER = "OwnableManaged: caller is not the owner"; - string internal constant NOT_OWNER_OR_MANAGER = "OwnableManaged: caller is not the owner or the manager"; + address internal immutable owner = makeAddr("owner"); + address internal immutable manager = makeAddr("manager"); - // Events must be redeclared here because Solidity 0.8.15 doesn't support - // referencing events from other contracts via qualified names (requires 0.8.21+) - event SignerRegistered(address indexed signer); - event SignerDeregistered(address indexed signer); - event ProposerSet(address indexed proposer, bool isValid); + GameType internal constant TEST_GAME_TYPE = GameType.wrap(621); function setUp() public { - owner = makeAddr("owner"); - manager = makeAddr("manager"); - unauthorized = makeAddr("unauthorized"); - - teeProverRegistry = _deployRegistry(new address[](0), TEST_GAME_TYPE); + teeProverRegistry = _deployRegistry(new address[](0)); } - function _deployRegistry(address[] memory proposers, GameType gameType) internal returns (DevTEEProverRegistry) { - MockAggregateVerifierForRegistry verifier = new MockAggregateVerifierForRegistry(TEST_IMAGE_HASH); - MockDisputeGameFactoryForRegistry factory = new MockDisputeGameFactoryForRegistry(address(verifier)); + function _deployRegistry(address[] memory proposers) internal returns (DevTEEProverRegistry) { + address verifier = makeAddr("verifier"); + vm.mockCall(verifier, abi.encodeWithSignature("TEE_NITRO_IMAGE_HASH()"), abi.encode(TEST_IMAGE_HASH)); + + address factory = makeAddr("factory"); + vm.mockCall(factory, abi.encodeCall(IDisputeGameFactory.gameImpls, (TEST_GAME_TYPE)), abi.encode(verifier)); - DevTEEProverRegistry impl = - new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(factory))); + DevTEEProverRegistry impl = new DevTEEProverRegistry( + INitroEnclaveVerifier(address(0)), ITDXVerifier(address(1)), IDisputeGameFactory(factory) + ); address proxyAdmin = makeAddr("proxy-admin"); Proxy proxy = new Proxy(proxyAdmin); vm.prank(proxyAdmin); proxy.upgradeToAndCall( - address(impl), abi.encodeCall(TEEProverRegistry.initialize, (owner, manager, proposers, gameType)) + address(impl), abi.encodeCall(TEEProverRegistry.initialize, (owner, manager, proposers, TEST_GAME_TYPE)) ); return DevTEEProverRegistry(address(proxy)); @@ -80,98 +51,48 @@ contract TEEProverRegistryTest is Test { function _addDevSigner(address signer) internal { vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); - } - - function _addDevSigners(address signer1, address signer2, address signer3) internal { - vm.startPrank(owner); - teeProverRegistry.addDevSigner(signer1, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer2, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer3, TEST_IMAGE_HASH); - vm.stopPrank(); - } - - function _expectNotOwnerRevert(address caller) internal { - vm.prank(caller); - vm.expectRevert(bytes(NOT_OWNER)); - } - - function _expectNotOwnerOrManagerRevert(address caller) internal { - vm.prank(caller); - vm.expectRevert(bytes(NOT_OWNER_OR_MANAGER)); - } - - function _assertContains(address[] memory values, address expected) internal { - for (uint256 i = 0; i < values.length; i++) { - if (values[i] == expected) return; - } - - fail(); + teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH, TEEProverRegistry.TEEType.NITRO); } function testInitialization() public view { assertEq(teeProverRegistry.owner(), owner); assertEq(teeProverRegistry.manager(), manager); - assertEq(teeProverRegistry.version(), "0.5.0"); + assertEq(teeProverRegistry.version(), "0.7.0"); } function testInitializationWithProposers() public { - address proposer1 = makeAddr("proposer1"); - address proposer2 = makeAddr("proposer2"); - address proposer3 = makeAddr("proposer3"); - address[] memory proposers = new address[](3); - proposers[0] = proposer1; - proposers[1] = proposer2; - proposers[2] = proposer3; - - DevTEEProverRegistry registry2 = _deployRegistry(proposers, TEST_GAME_TYPE); - assertTrue(registry2.isValidProposer(proposer1)); - assertTrue(registry2.isValidProposer(proposer2)); - assertTrue(registry2.isValidProposer(proposer3)); - } + address[] memory proposers = new address[](1); + proposers[0] = makeAddr("proposer"); - function testInitializationWithEmptyProposers() public view { - assertFalse(teeProverRegistry.isValidProposer(address(0))); + DevTEEProverRegistry registry2 = _deployRegistry(proposers); + assertTrue(registry2.isValidProposer(proposers[0])); } - function testDeregisterSignerAsOwner() public { - address signer = makeAddr("signer"); - - _addDevSigner(signer); + function testDeregisterSignerAsOwnerOrManager() public { + address ownerSigner = makeAddr("owner-signer"); + address managerSigner = makeAddr("manager-signer"); - vm.expectEmit(true, false, false, false, address(teeProverRegistry)); - emit SignerDeregistered(signer); + _addDevSigner(ownerSigner); + _addDevSigner(managerSigner); vm.prank(owner); - teeProverRegistry.deregisterSigner(signer); - - assertFalse(teeProverRegistry.isValidSigner(signer)); - } - - function testDeregisterSignerAsManager() public { - address signer = makeAddr("signer"); - - _addDevSigner(signer); - + teeProverRegistry.deregisterSigner(ownerSigner); vm.prank(manager); - teeProverRegistry.deregisterSigner(signer); + teeProverRegistry.deregisterSigner(managerSigner); - assertFalse(teeProverRegistry.isValidSigner(signer)); + assertFalse(teeProverRegistry.isValidSigner(ownerSigner)); + assertFalse(teeProverRegistry.isValidSigner(managerSigner)); } function testDeregisterSignerFailsIfUnauthorized() public { - address signer = makeAddr("signer"); - - _expectNotOwnerOrManagerRevert(unauthorized); - teeProverRegistry.deregisterSigner(signer); + vm.prank(makeAddr("unauthorized")); + vm.expectRevert(bytes("OwnableManaged: caller is not the owner or the manager")); + teeProverRegistry.deregisterSigner(makeAddr("signer")); } function testSetProposer() public { address newProposer = makeAddr("proposer"); - vm.expectEmit(true, false, false, false, address(teeProverRegistry)); - emit ProposerSet(newProposer, true); - vm.prank(owner); teeProverRegistry.setProposer(newProposer, true); @@ -179,220 +100,77 @@ contract TEEProverRegistryTest is Test { } function testSetProposerFailsIfNotOwner() public { - address newProposer = makeAddr("proposer"); - - _expectNotOwnerRevert(manager); - teeProverRegistry.setProposer(newProposer, true); - - _expectNotOwnerRevert(unauthorized); - teeProverRegistry.setProposer(newProposer, true); - } - - function testIsValidSignerReturnsFalseForUnregistered() public { - address unregistered = makeAddr("unregistered"); - assertFalse(teeProverRegistry.isValidSigner(unregistered)); + vm.prank(manager); + vm.expectRevert(bytes("OwnableManaged: caller is not the owner")); + teeProverRegistry.setProposer(makeAddr("proposer"), true); } - function testIsValidSignerReturnsTrueForRegistered() public { - address signer = makeAddr("signer"); + function testRegisteringSameSignerWithDifferentTEETypeOverwritesTEEType() public { + address signer = makeAddr("dev-signer"); _addDevSigner(signer); - assertTrue(teeProverRegistry.isValidSigner(signer)); - } - - function testMaxAgeConstant() public view { - assertEq(teeProverRegistry.MAX_AGE(), 60 minutes); - } - - function testTransferOwnership() public { - address newOwner = makeAddr("newOwner"); - vm.prank(owner); - teeProverRegistry.transferOwnership(newOwner); - - assertEq(teeProverRegistry.owner(), newOwner); - } - - function testTransferOwnershipFailsIfNotOwner() public { - address newOwner = makeAddr("newOwner"); - - _expectNotOwnerRevert(manager); - teeProverRegistry.transferOwnership(newOwner); - - _expectNotOwnerRevert(unauthorized); - teeProverRegistry.transferOwnership(newOwner); - } - - function testTransferOwnershipFailsForZeroAddress() public { - vm.prank(owner); - vm.expectRevert("OwnableManaged: new owner is the zero address"); - teeProverRegistry.transferOwnership(address(0)); - } - - function testTransferManagementAsOwner() public { - address newManager = makeAddr("newManager"); - - vm.prank(owner); - teeProverRegistry.transferManagement(newManager); - - assertEq(teeProverRegistry.manager(), newManager); - } - - function testTransferManagementAsManager() public { - address newManager = makeAddr("newManager"); - - vm.prank(manager); - teeProverRegistry.transferManagement(newManager); + teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH, TEEProverRegistry.TEEType.TDX); - assertEq(teeProverRegistry.manager(), newManager); + assertTrue(teeProverRegistry.signerTEEType(signer) == TEEProverRegistry.TEEType.TDX); } - function testTransferManagementFailsIfUnauthorized() public { - address newManager = makeAddr("newManager"); + function testRegisterTDXSignerStoresImageHash() public { + address signer = makeAddr("tdx-signer"); + address tdxVerifier = makeAddr("tdx-verifier"); - _expectNotOwnerOrManagerRevert(unauthorized); - teeProverRegistry.transferManagement(newManager); - } + vm.mockCall( + tdxVerifier, abi.encodeCall(ITDXVerifier.verify, (bytes(""), bytes(""))), abi.encode(signer, TEST_IMAGE_HASH) + ); - function testTransferManagementFailsForZeroAddress() public { - vm.prank(owner); - vm.expectRevert("OwnableManaged: new manager is the zero address"); - teeProverRegistry.transferManagement(address(0)); - } + TEEProverRegistry registry = new TEEProverRegistry( + INitroEnclaveVerifier(address(0)), ITDXVerifier(tdxVerifier), IDisputeGameFactory(makeAddr("factory")) + ); - function testRenounceOwnership() public { - vm.prank(owner); - teeProverRegistry.renounceOwnership(); + vm.prank(address(0xdEaD)); + registry.registerTDXSigner("", ""); - assertEq(teeProverRegistry.owner(), address(0)); + assertTrue(registry.isRegisteredSigner(signer)); + assertEq(registry.signerImageHash(signer), TEST_IMAGE_HASH); + assertTrue(registry.signerTEEType(signer) == TEEProverRegistry.TEEType.TDX); } - function testRenounceManagementAsOwner() public { - vm.prank(owner); - teeProverRegistry.renounceManagement(); - - assertEq(teeProverRegistry.manager(), address(0)); - } - - function testRenounceManagementAsManager() public { - vm.prank(manager); - teeProverRegistry.renounceManagement(); - - assertEq(teeProverRegistry.manager(), address(0)); + function testConstructorRevertsIfTDXVerifierNotSet() public { + vm.expectRevert(TEEProverRegistry.TDXVerifierNotSet.selector); + new TEEProverRegistry( + INitroEnclaveVerifier(address(0)), ITDXVerifier(address(0)), IDisputeGameFactory(makeAddr("factory")) + ); } function testAddDevSigner() public { address signer = makeAddr("dev-signer"); - vm.expectEmit(true, false, false, false, address(teeProverRegistry)); - emit SignerRegistered(signer); - - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); - - assertTrue(teeProverRegistry.isValidSigner(signer)); - } - - function testAddDevSignerFailsIfNotOwner() public { - address signer = makeAddr("dev-signer"); - - _expectNotOwnerRevert(manager); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); - - _expectNotOwnerRevert(unauthorized); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); - } - - function testAddDevSignerIdempotent() public { - address signer = makeAddr("dev-signer"); - - _addDevSigner(signer); _addDevSigner(signer); assertTrue(teeProverRegistry.isValidSigner(signer)); + assertTrue(teeProverRegistry.signerTEEType(signer) == TEEProverRegistry.TEEType.NITRO); } - function testAddMultipleDevSigners() public { - address signer1 = makeAddr("dev-signer-1"); - address signer2 = makeAddr("dev-signer-2"); - address signer3 = makeAddr("dev-signer-3"); - - _addDevSigners(signer1, signer2, signer3); - - assertTrue(teeProverRegistry.isValidSigner(signer1)); - assertTrue(teeProverRegistry.isValidSigner(signer2)); - assertTrue(teeProverRegistry.isValidSigner(signer3)); - } - - function testGetRegisteredSignersEmpty() public view { - assertEq(teeProverRegistry.getRegisteredSigners().length, 0); - } - - function testGetRegisteredSignersAfterRegister() public { - address signer = makeAddr("signer"); - - _addDevSigner(signer); - - address[] memory signers = teeProverRegistry.getRegisteredSigners(); - assertEq(signers.length, 1); - assertEq(signers[0], signer); - } - - function testGetRegisteredSignersAfterDeregister() public { - address signer = makeAddr("signer"); - - _addDevSigner(signer); - - vm.prank(owner); - teeProverRegistry.deregisterSigner(signer); - - assertEq(teeProverRegistry.getRegisteredSigners().length, 0); - } - - function testGetRegisteredSignersMultiple() public { - address signer1 = makeAddr("signer-1"); - address signer2 = makeAddr("signer-2"); - address signer3 = makeAddr("signer-3"); - - _addDevSigners(signer1, signer2, signer3); - - address[] memory signers = teeProverRegistry.getRegisteredSigners(); - assertEq(signers.length, 3); - - _assertContains(signers, signer1); - _assertContains(signers, signer2); - _assertContains(signers, signer3); + function testAddDevSignerFailsIfNotOwner() public { + vm.prank(manager); + vm.expectRevert(bytes("OwnableManaged: caller is not the owner")); + teeProverRegistry.addDevSigner(makeAddr("dev-signer"), TEST_IMAGE_HASH, TEEProverRegistry.TEEType.NITRO); } function testGetRegisteredSignersConsistencyAfterMixedOperations() public { address signer1 = makeAddr("signer-1"); address signer2 = makeAddr("signer-2"); - address signer3 = makeAddr("signer-3"); - _addDevSigners(signer1, signer2, signer3); + _addDevSigner(signer1); + _addDevSigner(signer2); vm.prank(manager); teeProverRegistry.deregisterSigner(signer2); address[] memory signers = teeProverRegistry.getRegisteredSigners(); - assertEq(signers.length, 2); - _assertContains(signers, signer1); - _assertContains(signers, signer3); + assertEq(signers.length, 1); + assertEq(signers[0], signer1); assertFalse(teeProverRegistry.isValidSigner(signer2)); } - - function testGetRegisteredSignersDeregisterIdempotent() public { - address signer = makeAddr("signer"); - - _addDevSigner(signer); - - vm.prank(owner); - teeProverRegistry.deregisterSigner(signer); - - vm.prank(owner); - teeProverRegistry.deregisterSigner(signer); - - assertEq(teeProverRegistry.getRegisteredSigners().length, 0); - } } diff --git a/test/L1/proofs/TEEVerifier.t.sol b/test/L1/proofs/TEEVerifier.t.sol index 5059cb840..f931df1cb 100644 --- a/test/L1/proofs/TEEVerifier.t.sol +++ b/test/L1/proofs/TEEVerifier.t.sol @@ -3,123 +3,75 @@ pragma solidity 0.8.15; import { Test } from "lib/forge-std/src/Test.sol"; -import { Proxy } from "src/universal/Proxy.sol"; - -import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { GameType } from "src/libraries/bridge/Types.sol"; -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { MockAnchorStateRegistry } from "scripts/multiproof/mocks/MockAnchorStateRegistry.sol"; -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"; -contract MockAggregateVerifierForVerifier { - bytes32 public immutable TEE_IMAGE_HASH; - - constructor(bytes32 imageHash) { - TEE_IMAGE_HASH = imageHash; - } -} - -contract MockDisputeGameFactoryForVerifier { - IDisputeGame internal immutable _impl; - - constructor(address impl) { - _impl = IDisputeGame(impl); - } - - function gameImpls(GameType) external view returns (IDisputeGame) { - return _impl; - } -} - contract TEEVerifierTest is Test { - TEEVerifier public verifier; - DevTEEProverRegistry public teeProverRegistry; + TEEVerifier internal verifier; + address internal immutable teeProverRegistry = makeAddr("tee-prover-registry"); - uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; - bytes32 internal constant IMAGE_ID = keccak256("test-image-id"); - bytes32 internal constant TEST_JOURNAL = keccak256("test-journal"); - GameType internal constant TEST_GAME_TYPE = GameType.wrap(621); + bytes32 internal constant NITRO_IMAGE_ID = keccak256("test-nitro-image-id"); + bytes32 internal constant JOURNAL = keccak256("test-journal"); address internal immutable PROPOSER = makeAddr("proposer"); function setUp() public { - address signerAddress = vm.addr(SIGNER_PRIVATE_KEY); - MockAggregateVerifierForVerifier mockVerifier = new MockAggregateVerifierForVerifier(IMAGE_ID); - MockDisputeGameFactoryForVerifier mockFactory = new MockDisputeGameFactoryForVerifier(address(mockVerifier)); - - // DevTEEProverRegistry keeps these tests focused on verifier behavior without Nitro attestation setup. - DevTEEProverRegistry impl = - new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(mockFactory))); - - address proxyAdmin = makeAddr("proxy-admin"); - Proxy proxy = new Proxy(proxyAdmin); - vm.prank(proxyAdmin); - proxy.upgradeToAndCall( - address(impl), - abi.encodeCall( - TEEProverRegistry.initialize, (address(this), address(this), new address[](0), TEST_GAME_TYPE) - ) + vm.mockCall(teeProverRegistry, abi.encodeWithSignature("isValidProposer(address)", PROPOSER), abi.encode(true)); + vm.mockCall( + teeProverRegistry, abi.encodeWithSignature("isRegisteredSigner(address)", vm.addr(1)), abi.encode(true) ); - - teeProverRegistry = DevTEEProverRegistry(address(proxy)); - teeProverRegistry.addDevSigner(signerAddress, IMAGE_ID); - teeProverRegistry.setProposer(PROPOSER, true); - - MockAnchorStateRegistry anchorStateRegistry = new MockAnchorStateRegistry(); - verifier = new TEEVerifier( - TEEProverRegistry(address(teeProverRegistry)), IAnchorStateRegistry(address(anchorStateRegistry)) + vm.mockCall( + teeProverRegistry, + abi.encodeWithSignature("signerImageHash(address)", vm.addr(1)), + abi.encode(NITRO_IMAGE_ID) ); + + verifier = new TEEVerifier(TEEProverRegistry(teeProverRegistry), IAnchorStateRegistry(address(0))); } - function testVerifyValidSignature() public view { - assertTrue(verifier.verify(_proofFor(PROPOSER, SIGNER_PRIVATE_KEY, TEST_JOURNAL), IMAGE_ID, TEST_JOURNAL)); + function testVerifyValidNitroSignature() public view { + assertTrue(verifier.verify(_proof(1), NITRO_IMAGE_ID, JOURNAL)); } function testVerifyFailsWithInvalidSignature() public { - bytes memory proofBytes = abi.encodePacked(PROPOSER, bytes32(0), bytes32(0), uint8(27)); - vm.expectRevert(TEEVerifier.InvalidSignature.selector); - verifier.verify(proofBytes, IMAGE_ID, TEST_JOURNAL); + verifier.verify(abi.encodePacked(PROPOSER, new bytes(65)), NITRO_IMAGE_ID, JOURNAL); } function testVerifyFailsWithInvalidProposer() public { - bytes memory proofBytes = _proofFor(address(0), SIGNER_PRIVATE_KEY, TEST_JOURNAL); - + vm.mockCall( + teeProverRegistry, abi.encodeWithSignature("isValidProposer(address)", address(0)), abi.encode(false) + ); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidProposer.selector, address(0))); - verifier.verify(proofBytes, IMAGE_ID, TEST_JOURNAL); + verifier.verify(abi.encodePacked(address(0), new bytes(65)), NITRO_IMAGE_ID, JOURNAL); } function testVerifyFailsWithUnregisteredSigner() public { - uint256 unregisteredKey = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef; - address unregisteredSigner = vm.addr(unregisteredKey); + address unregisteredSigner = vm.addr(2); + vm.mockCall( + teeProverRegistry, + abi.encodeWithSignature("isRegisteredSigner(address)", unregisteredSigner), + abi.encode(false) + ); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); - verifier.verify(_proofFor(PROPOSER, unregisteredKey, TEST_JOURNAL), IMAGE_ID, TEST_JOURNAL); + verifier.verify(_proof(2), NITRO_IMAGE_ID, JOURNAL); } function testVerifyFailsWithImageIdMismatch() public { bytes32 wrongImageId = keccak256("different-image"); - vm.expectRevert(abi.encodeWithSelector(TEEVerifier.ImageIdMismatch.selector, IMAGE_ID, wrongImageId)); - verifier.verify(_proofFor(PROPOSER, SIGNER_PRIVATE_KEY, TEST_JOURNAL), wrongImageId, TEST_JOURNAL); + vm.expectRevert(abi.encodeWithSelector(TEEVerifier.ImageIdMismatch.selector, NITRO_IMAGE_ID, wrongImageId)); + verifier.verify(_proof(1), wrongImageId, JOURNAL); } - function testVerifyFailsWithInvalidProofFormat() public { - bytes memory shortProof = new bytes(50); - + function testVerifyFailsWhenTEETypeIsIncludedInProof() public { vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); - verifier.verify(shortProof, IMAGE_ID, TEST_JOURNAL); - } - - function testConstants() public view { - assertEq(address(verifier.TEE_PROVER_REGISTRY()), address(teeProverRegistry)); + verifier.verify(new bytes(86), NITRO_IMAGE_ID, JOURNAL); } - function _proofFor(address proposer, uint256 privateKey, bytes32 journal) internal pure returns (bytes memory) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, journal); - return abi.encodePacked(proposer, r, s, v); + function _proof(uint256 privateKey) internal view returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, JOURNAL); + return abi.encodePacked(PROPOSER, r, s, v); } } diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index 6a94439d6..8175d8af8 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -23,6 +23,10 @@ contract MockNitroEnclaveVerifier { } } +contract MockTDXVerifier { + address public proofSubmitter; +} + contract MockSP1Verifier { function verifyProof(bytes32, bytes calldata, bytes calldata) external pure { } } @@ -40,6 +44,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { address internal proposer = makeAddr("proposer"); address internal challenger = makeAddr("challenger"); MockNitroEnclaveVerifier internal nitroEnclaveVerifier; + MockTDXVerifier internal tdxVerifier; MockSP1Verifier internal sp1Verifier; uint256 internal l2ChainId = 901; @@ -47,6 +52,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { function setUp() public { systemDeploy = new SystemDeploy(); nitroEnclaveVerifier = new MockNitroEnclaveVerifier(); + tdxVerifier = new MockTDXVerifier(); sp1Verifier = new MockSP1Verifier(); } @@ -184,12 +190,14 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { withdrawalDelaySeconds: 100, proofMaturityDelaySeconds: 400, disputeGameFinalityDelaySeconds: 500, - teeImageHash: bytes32(uint256(1)), + teeNitroImageHash: bytes32(uint256(1)), + teeTdxImageHash: bytes32(uint256(11)), zkRangeHash: bytes32(uint256(2)), zkAggregationHash: bytes32(uint256(3)), multiproofConfigHash: bytes32(uint256(4)), multiproofGameType: 621, nitroEnclaveVerifier: address(nitroEnclaveVerifier), + tdxVerifier: address(tdxVerifier), multiproofBlockInterval: 100, multiproofIntermediateBlockInterval: 10, sp1Verifier: ISP1Verifier(address(sp1Verifier)), @@ -288,7 +296,8 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { ethLockbox: _output.opChain.ethLockboxProxy, proxyAdminOwner: _input.opChainInput.roles.opChainProxyAdminOwner, multiproofGameType: GameType.wrap(uint32(_input.implementationsInput.multiproofGameType)), - teeImageHash: _input.implementationsInput.teeImageHash, + teeNitroImageHash: _input.implementationsInput.teeNitroImageHash, + teeTdxImageHash: _input.implementationsInput.teeTdxImageHash, zkRangeHash: _input.implementationsInput.zkRangeHash, zkAggregationHash: _input.implementationsInput.zkAggregationHash, multiproofConfigHash: _input.implementationsInput.multiproofConfigHash, diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index fb3aa1c1e..b92d0fa5c 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -39,7 +39,8 @@ abstract contract SystemDeployAssertions is Test { IETHLockbox ethLockbox; address proxyAdminOwner; GameType multiproofGameType; - bytes32 teeImageHash; + bytes32 teeNitroImageHash; + bytes32 teeTdxImageHash; bytes32 zkRangeHash; bytes32 zkAggregationHash; bytes32 multiproofConfigHash; @@ -250,7 +251,8 @@ abstract contract SystemDeployAssertions is Test { assertEq(address(_aggregateVerifier.DELAYED_WETH()), address(_expected.delayedWETH), "AV-60"); assertEq(address(_aggregateVerifier.TEE_VERIFIER()), _expected.implementations.teeVerifierImpl, "AV-70"); assertEq(address(_aggregateVerifier.ZK_VERIFIER()), _expected.implementations.zkVerifierImpl, "AV-80"); - assertEq(_aggregateVerifier.TEE_IMAGE_HASH(), _expected.teeImageHash, "AV-90"); + assertEq(_aggregateVerifier.TEE_NITRO_IMAGE_HASH(), _expected.teeNitroImageHash, "AV-90"); + assertEq(_aggregateVerifier.TEE_TDX_IMAGE_HASH(), _expected.teeTdxImageHash, "AV-95"); assertEq(_aggregateVerifier.ZK_RANGE_HASH(), _expected.zkRangeHash, "AV-100"); assertEq(_aggregateVerifier.ZK_AGGREGATE_HASH(), _expected.zkAggregationHash, "AV-110"); assertEq(_aggregateVerifier.CONFIG_HASH(), _expected.multiproofConfigHash, "AV-120"); diff --git a/test/mocks/MockDevTEEProverRegistry.sol b/test/mocks/MockDevTEEProverRegistry.sol index eea6d9a2b..a41b8fe93 100644 --- a/test/mocks/MockDevTEEProverRegistry.sol +++ b/test/mocks/MockDevTEEProverRegistry.sol @@ -3,31 +3,23 @@ pragma solidity 0.8.15; import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; +import { ITDXVerifier } from "interfaces/L1/proofs/tee/ITDXVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; -import { EnumerableSetLib } from "src/vendor/EnumerableSetLib.sol"; /// @title DevTEEProverRegistry /// @notice Test/development registry that can register signers without Nitro attestation verification. /// @dev 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 and image hash without 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 addDevSigner(address signer, bytes32 imageHash) external onlyOwner { - isRegisteredSigner[signer] = true; - signerImageHash[signer] = imageHash; - _registeredSigners.add(signer); - emit SignerRegistered(signer); + function addDevSigner(address signer, bytes32 imageHash, TEEType teeType) external onlyOwner { + _registerSigner(signer, imageHash, teeType); } }