From 96b132077b86bdc77f3f96dd40e09dad363df32e Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Thu, 12 Mar 2026 11:19:50 -0400 Subject: [PATCH 1/5] Add custom NitroEnclaveVerifier contract and update multiproof contracts - Add NitroEnclaveVerifier with ZK-based AWS Nitro attestation verification - Add tests for NitroEnclaveVerifier - Add proof submitter configurability to NitroEnclaveVerifier - Update TEEVerifier, MockVerifier, and deploy scripts accordingly - Update snapshots and semver-lock Made-with: Cursor --- src/multiproof/tee/NitroEnclaveVerifier.sol | 660 ++++++++++++++++++++ test/multiproof/NitroEnclaveVerifier.t.sol | 614 ++++++++++++++++++ 2 files changed, 1274 insertions(+) create mode 100644 src/multiproof/tee/NitroEnclaveVerifier.sol create mode 100644 test/multiproof/NitroEnclaveVerifier.t.sol diff --git a/src/multiproof/tee/NitroEnclaveVerifier.sol b/src/multiproof/tee/NitroEnclaveVerifier.sol new file mode 100644 index 00000000..ad18ccfc --- /dev/null +++ b/src/multiproof/tee/NitroEnclaveVerifier.sol @@ -0,0 +1,660 @@ +//SPDX-License-Identifier: Apache2.0 +pragma solidity ^0.8.0; + +import { Ownable } from "@solady/auth/Ownable.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { + INitroEnclaveVerifier, + ZkCoProcessorType, + ZkCoProcessorConfig, + VerifierJournal, + BatchVerifierJournal, + VerificationResult +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +import { + IRiscZeroVerifier +} from "lib/aws-nitro-enclave-attestation/contracts/lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import { + ISP1Verifier +} from "lib/aws-nitro-enclave-attestation/contracts/lib/sp1-contracts/contracts/src/ISP1Verifier.sol"; + +/** + * @title NitroEnclaveVerifier + * @dev Implementation contract for AWS Nitro Enclave attestation verification using zero-knowledge proofs + * @dev Custom version of Automata's NitroEnclaveVerifier contract at + * https://github.com/automata-network/aws-nitro-enclave-attestation/tree/26c90565cb009e6539643a0956f9502a12ade672 + * + * Differences: + * - Verification of ZK proofs is now a privileged action + * - All privileged actions are monitored + * - Removes verification with Program ID and Pico logic + * + * This contract provides on-chain verification of AWS Nitro Enclave attestation reports by validating + * zero-knowledge proofs generated off-chain. It supports both single and batch verification modes + * and can work with multiple ZK proof systems (RISC Zero and Succinct SP1). + * + * Key features: + * - Certificate chain management with automatic caching of newly discovered certificates + * - Timestamp validation with configurable time tolerance + * - Certificate revocation capabilities for compromised intermediate certificates + * - Gas-efficient batch verification for multiple attestations + * - Support for both RISC Zero and SP1 proving systems + * + * Security considerations: + * - Only the contract owner can manage certificates and configurations + * - Root certificate is immutable once set (requires owner to change) + * - Intermediate certificates are automatically cached but can be revoked + * - Timestamp validation prevents replay attacks within the configured time window + */ +contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { + using EnumerableSet for EnumerableSet.Bytes32Set; + + /// @dev Sentinel address to indicate a route has been permanently frozen + address private constant FROZEN = address(0xdead); + + /// @dev Address that can submit proofs + address public proofSubmitter; + + /// @dev Configuration mapping for each supported ZK coprocessor type + mapping(ZkCoProcessorType => ZkCoProcessorConfig) public zkConfig; + + /// @dev Mapping of trusted intermediate certificate hashes (excludes root certificate) + mapping(bytes32 trustedCertHash => bool) public trustedIntermediateCerts; + + /// @dev Maximum allowed time difference in seconds for attestation timestamp validation + uint64 public maxTimeDiff; + + /// @dev Hash of the trusted AWS Nitro Enclave root certificate + bytes32 public rootCert; + + /// @dev Set of all supported verifier program IDs per coprocessor + mapping(ZkCoProcessorType => EnumerableSet.Bytes32Set) private _verifierIdSet; + + /// @dev Set of all supported aggregator program IDs per coprocessor + mapping(ZkCoProcessorType => EnumerableSet.Bytes32Set) private _aggregatorIdSet; + + /// @dev Route-specific verifier overrides (selector -> verifier address) + mapping(ZkCoProcessorType => mapping(bytes4 selector => address zkVerifier)) private _zkVerifierRoutes; + + /// @dev Mapping from verifierId to its corresponding verifierProofId representation + mapping(ZkCoProcessorType => mapping(bytes32 verifierId => bytes32 verifierProofId)) private _verifierProofIds; + + /// @dev Event emitted when the proof submitter address is changed + event ProofSubmitterChanged(address newProofSubmitter); + + /// @dev Event emitted when the root certificate is changed + event RootCertChanged(bytes32 newRootCert); + + /// @dev Event emitted when the ZK configuration is updated + event ZKConfigurationUpdated(ZkCoProcessorType zkCoProcessor, ZkCoProcessorConfig config, bytes32 verifierProofId); + + /// @dev Event emitted when a certificate is revoked + event CertRevoked(bytes32 certHash); + + /** + * @dev Initializes the contract with owner, time tolerance and initial trusted certificates + * @param _owner Address to be set as the contract owner + * @param _maxTimeDiff Maximum time difference in seconds for timestamp validation + * @param _initializeTrustedCerts Array of initial trusted intermediate certificate hashes + * + * Sets the provided address as the contract owner and initializes the trusted certificate set. + * The root certificate must be set separately after deployment. + */ + constructor(address _owner, uint64 _maxTimeDiff, bytes32[] memory _initializeTrustedCerts) { + maxTimeDiff = _maxTimeDiff; + for (uint256 i = 0; i < _initializeTrustedCerts.length; i++) { + trustedIntermediateCerts[_initializeTrustedCerts[i]] = true; + } + _initializeOwner(_owner); + } + + // ============ Query Functions ============ + + /** + * @dev Retrieves the configuration for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @return ZkCoProcessorConfig Configuration parameters including program IDs and verifier address + */ + function getZkConfig(ZkCoProcessorType _zkCoProcessor) external view returns (ZkCoProcessorConfig memory) { + return zkConfig[_zkCoProcessor]; + } + + /** + * @dev Returns all supported verifier program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported verifier program IDs + */ + function getVerifierIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory) { + return _verifierIdSet[_zkCoProcessor].values(); + } + + /** + * @dev Returns all supported aggregator program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported aggregator program IDs + */ + function getAggregatorIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory) { + return _aggregatorIdSet[_zkCoProcessor].values(); + } + + /** + * @dev Checks if a verifier program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to check + * @return True if the ID is supported + */ + function isVerifierIdSupported(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bool) { + return _verifierIdSet[_zkCoProcessor].contains(_verifierId); + } + + /** + * @dev Checks if an aggregator program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to check + * @return True if the ID is supported + */ + function isAggregatorIdSupported( + ZkCoProcessorType _zkCoProcessor, + bytes32 _aggregatorId + ) + external + view + returns (bool) + { + return _aggregatorIdSet[_zkCoProcessor].contains(_aggregatorId); + } + + /** + * @dev Gets the verifier address for a specific route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector + * @return Verifier address (route-specific or default fallback) + */ + function getZkVerifier(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external view returns (address) { + address verifier = _zkVerifierRoutes[_zkCoProcessor][_selector]; + + if (verifier == FROZEN) { + revert ZkRouteFrozen(_zkCoProcessor, _selector); + } + + if (verifier == address(0)) { + return zkConfig[_zkCoProcessor].zkVerifier; + } + + return verifier; + } + + /** + * @dev Returns the verifierProofId for a given verifierId + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId The verifier program ID + * @return The corresponding verifierProofId + */ + function getVerifierProofId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bytes32) { + return _verifierProofIds[_zkCoProcessor][_verifierId]; + } + + /** + * @dev Checks the prefix length of trusted certificates in each provided certificate chain for reports + * @param _report_certs Array of certificate chains, each containing certificate hashes + * @return Array indicating the prefix length of trusted certificates in each chain + * + * For each certificate chain: + * 1. Validates that the first certificate matches the stored root certificate + * 2. Counts consecutive trusted certificates starting from the root + * 3. Stops counting when an untrusted certificate is encountered + * + * This function is used to pre-validate certificate chains before generating proofs, + * helping to optimize the proving process by determining trusted certificate lengths. + * Usually called from off-chain + */ + function checkTrustedIntermediateCerts(bytes32[][] calldata _report_certs) public view returns (uint8[] memory) { + uint8[] memory results = new uint8[](_report_certs.length); + bytes32 rootCertHash = rootCert; + for (uint256 i = 0; i < _report_certs.length; i++) { + bytes32[] calldata certs = _report_certs[i]; + uint8 trustedCertPrefixLen = 1; + if (certs[0] != rootCertHash) { + revert("First certificate must be the root certificate"); + } + for (uint256 j = 1; j < certs.length; j++) { + if (!trustedIntermediateCerts[certs[j]]) { + break; + } + trustedCertPrefixLen += 1; + } + results[i] = trustedCertPrefixLen; + } + return results; + } + + // ============ Admin Functions ============ + + /** + * @dev Sets the trusted root certificate hash + * @param _rootCert Hash of the AWS Nitro Enclave root certificate + * + * Requirements: + * - Only callable by contract owner + * + * The root certificate serves as the trust anchor for all certificate chain validations. + * This should be set to the hash of AWS's root certificate for Nitro Enclaves. + */ + function setRootCert(bytes32 _rootCert) external onlyOwner { + rootCert = _rootCert; + emit RootCertChanged(_rootCert); + } + + /** + * @dev Configures zero-knowledge verification parameters for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @param _config Configuration parameters including program IDs and verifier address + * @param _verifierProofId The verifierProofId corresponding to the verifierId in config + * + * Requirements: + * - Only callable by contract owner + * + * This function sets up the necessary parameters for ZK proof verification: + * - verifierId: Program ID for single attestation verification + * - aggregatorId: Program ID for batch/aggregated verification + * - zkVerifier: Address of the deployed ZK verifier contract + * + * Note: Program IDs are automatically added to the supported version sets + * The verifierProofId is stored in a separate mapping (verifierId => verifierProofId) + */ + function setZkConfiguration( + ZkCoProcessorType _zkCoProcessor, + ZkCoProcessorConfig memory _config, + bytes32 _verifierProofId + ) + external + onlyOwner + { + zkConfig[_zkCoProcessor] = _config; + + // Auto-add program IDs to the version sets and store verifierProofId mapping + if (_config.verifierId != bytes32(0)) { + _verifierIdSet[_zkCoProcessor].add(_config.verifierId); + _verifierProofIds[_zkCoProcessor][_config.verifierId] = _verifierProofId; + } + if (_config.aggregatorId != bytes32(0)) { + _aggregatorIdSet[_zkCoProcessor].add(_config.aggregatorId); + } + emit ZKConfigurationUpdated(_zkCoProcessor, _config, _verifierProofId); + } + + /** + * @dev Revokes a trusted intermediate certificate + * @param _certHash Hash of the certificate to revoke + * + * Requirements: + * - Only callable by contract owner + * - Certificate must exist in the trusted intermediate certificates set + * + * This function allows the owner to revoke compromised intermediate certificates + * without affecting the root certificate or other trusted certificates. + */ + function revokeCert(bytes32 _certHash) external onlyOwner { + if (!trustedIntermediateCerts[_certHash]) { + revert("Certificate not found in trusted certs"); + } + delete trustedIntermediateCerts[_certHash]; + emit CertRevoked(_certHash); + } + + /** + * @dev Updates the verifier program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newVerifierId New verifier program ID to set as latest + * @param _newVerifierProofId New verifier proof ID (stored in mapping, used in batch verification) + */ + function updateVerifierId( + ZkCoProcessorType _zkCoProcessor, + bytes32 _newVerifierId, + bytes32 _newVerifierProofId + ) + external + onlyOwner + { + require(_newVerifierId != bytes32(0), "Verifier ID cannot be zero"); + require(zkConfig[_zkCoProcessor].verifierId != _newVerifierId, "Verifier ID is already the latest"); + + zkConfig[_zkCoProcessor].verifierId = _newVerifierId; + _verifierIdSet[_zkCoProcessor].add(_newVerifierId); + _verifierProofIds[_zkCoProcessor][_newVerifierId] = _newVerifierProofId; + + emit VerifierIdUpdated(_zkCoProcessor, _newVerifierId, _newVerifierProofId); + } + + /** + * @dev Updates the aggregator program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newAggregatorId New aggregator program ID to set as latest + */ + function updateAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _newAggregatorId) external onlyOwner { + require(_newAggregatorId != bytes32(0), "Aggregator ID cannot be zero"); + require(zkConfig[_zkCoProcessor].aggregatorId != _newAggregatorId, "Aggregator ID is already the latest"); + + zkConfig[_zkCoProcessor].aggregatorId = _newAggregatorId; + _aggregatorIdSet[_zkCoProcessor].add(_newAggregatorId); + + emit AggregatorIdUpdated(_zkCoProcessor, _newAggregatorId); + } + + /** + * @dev Removes a verifier program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to remove + */ + function removeVerifierId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external onlyOwner { + require(_verifierIdSet[_zkCoProcessor].contains(_verifierId), "Verifier ID does not exist"); + + // Cannot remove the latest verifier ID - must update to a new one first + if (zkConfig[_zkCoProcessor].verifierId == _verifierId) { + revert CannotRemoveLatestProgramId(_zkCoProcessor, _verifierId); + } + + _verifierIdSet[_zkCoProcessor].remove(_verifierId); + delete _verifierProofIds[_zkCoProcessor][_verifierId]; + emit ProgramIdRemoved(_zkCoProcessor, _verifierId, false); + } + + /** + * @dev Removes an aggregator program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to remove + */ + function removeAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _aggregatorId) external onlyOwner { + require(_aggregatorIdSet[_zkCoProcessor].contains(_aggregatorId), "Aggregator ID does not exist"); + + // Cannot remove the latest aggregator ID - must update to a new one first + if (zkConfig[_zkCoProcessor].aggregatorId == _aggregatorId) { + revert CannotRemoveLatestProgramId(_zkCoProcessor, _aggregatorId); + } + + _aggregatorIdSet[_zkCoProcessor].remove(_aggregatorId); + emit ProgramIdRemoved(_zkCoProcessor, _aggregatorId, true); + } + + /** + * @dev Adds a route-specific verifier override + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector (first 4 bytes of proof data) + * @param _verifier Address of the verifier contract for this route + */ + function addVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector, address _verifier) external onlyOwner { + require(_verifier != address(0), "Verifier cannot be zero address"); + + if (_zkVerifierRoutes[_zkCoProcessor][_selector] == FROZEN) { + revert ZkRouteFrozen(_zkCoProcessor, _selector); + } + + _zkVerifierRoutes[_zkCoProcessor][_selector] = _verifier; + emit ZkRouteAdded(_zkCoProcessor, _selector, _verifier); + } + + /** + * @dev Permanently freezes a verification route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector to freeze + * + * WARNING: This action is IRREVERSIBLE + */ + function freezeVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external onlyOwner { + address currentVerifier = _zkVerifierRoutes[_zkCoProcessor][_selector]; + + if (currentVerifier == FROZEN) { + revert ZkRouteFrozen(_zkCoProcessor, _selector); + } + + _zkVerifierRoutes[_zkCoProcessor][_selector] = FROZEN; + emit ZkRouteWasFrozen(_zkCoProcessor, _selector); + } + + /** + * @dev Sets the proof submitter address + * @param _proofSubmitter The address of the proof submitter + */ + function setProofSubmitter(address _proofSubmitter) external onlyOwner { + proofSubmitter = _proofSubmitter; + emit ProofSubmitterChanged(_proofSubmitter); + } + + // ============ Verification Functions ============ + + /** + * @dev Verifies a single attestation report using zero-knowledge proof + * @param output Encoded VerifierJournal containing the verification result + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for the attestation + * @return journal VerifierJournal containing the verification result and extracted data + * + * This function performs end-to-end verification of a single attestation: + * 1. Retrieves the single verification program ID from configuration + * 2. Verifies the zero-knowledge proof using the specified coprocessor + * 3. Decodes the verification journal from the output + * 4. Validates the journal through comprehensive checks + * 5. Returns the final verification result + * + * The returned journal contains all extracted attestation data including: + * - Verification status and any error conditions + * - Certificate chain information and trust levels + * - User data, nonce, and public key from the attestation + * - Platform Configuration Registers (PCRs) for integrity measurement + * - Module ID and timestamp information + */ + function verify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal memory journal) + { + require(msg.sender == proofSubmitter, "Only the proof submitter can verify proofs"); + bytes32 programId = zkConfig[zkCoprocessor].verifierId; + _verifyZk(zkCoprocessor, programId, output, proofBytes); + journal = abi.decode(output, (VerifierJournal)); + journal = _verifyJournal(journal); + emit AttestationSubmitted(journal.result, zkCoprocessor, output); + } + + /** + * @dev Verifies multiple attestation reports in a single batch operation + * @param output Encoded BatchVerifierJournal containing aggregated verification results + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for batch verification + * @return results Array of VerifierJournal results, one for each attestation in the batch + * + * This function provides gas-efficient batch verification by: + * 1. Using the aggregator program ID for ZK proof verification + * 2. Validating the batch verifier key matches the expected value + * 3. Processing each individual attestation through standard validation + * 4. Returning comprehensive results for all attestations + * + * Batch verification is recommended when processing multiple attestations + * as it significantly reduces gas costs compared to individual verifications. + */ + function batchVerify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal[] memory results) + { + require(msg.sender == proofSubmitter, "Only the proof submitter can verify proofs"); + bytes32 aggregatorId = zkConfig[zkCoprocessor].aggregatorId; + bytes32 verifierId = zkConfig[zkCoprocessor].verifierId; + bytes32 verifierProofId = _verifierProofIds[zkCoprocessor][verifierId]; + + _verifyZk(zkCoprocessor, aggregatorId, output, proofBytes); + BatchVerifierJournal memory batchJournal = abi.decode(output, (BatchVerifierJournal)); + if (batchJournal.verifierVk != verifierProofId) { + revert("Verifier VK does not match the expected verifier proof ID"); + } + uint256 n = batchJournal.outputs.length; + results = new VerifierJournal[](n); + for (uint256 i = 0; i < n; i++) { + results[i] = _verifyJournal(batchJournal.outputs[i]); + } + emit BatchAttestationSubmitted(verifierId, zkCoprocessor, abi.encode(results)); + } + + // To meet interface requirements + function verifyWithProgramId( + bytes calldata, + ZkCoProcessorType, + bytes32, + bytes calldata + ) + external + pure + returns (VerifierJournal memory) + { + revert("Not implemented"); + } + + // To meet interface requirements + function batchVerifyWithProgramId( + bytes calldata, + ZkCoProcessorType, + bytes32, + bytes32, + bytes calldata + ) + external + pure + returns (VerifierJournal[] memory) + { + revert("Not implemented"); + } + + // ============ Internal Functions ============ + + /** + * @dev Internal function to cache newly discovered trusted certificates + * @param journal Verification journal containing certificate chain information + * + * This function automatically adds any certificates beyond the trusted length + * to the trusted intermediate certificates set. This optimizes future verifications + * by expanding the known trusted certificate set based on successful verifications. + */ + function _cacheNewCert(VerifierJournal memory journal) internal { + for (uint256 i = journal.trustedCertsPrefixLen; i < journal.certs.length; i++) { + bytes32 certHash = journal.certs[i]; + trustedIntermediateCerts[certHash] = true; + } + } + + /** + * @dev Internal function to verify and validate a journal entry + * @param journal Verification journal to validate + * @return Updated journal with final verification result + * + * This function performs comprehensive validation: + * 1. Checks if the initial ZK verification was successful + * 2. Validates the root certificate matches the trusted root + * 3. Ensures all trusted certificates are still valid (not revoked) + * 4. Validates the attestation timestamp is within acceptable range + * 5. Caches newly discovered certificates for future use + * + * The timestamp validation converts milliseconds to seconds and checks: + * - Attestation is not too old (timestamp + maxTimeDiff >= block.timestamp) + * - Attestation is not from the future (timestamp <= block.timestamp) + */ + function _verifyJournal(VerifierJournal memory journal) internal returns (VerifierJournal memory) { + if (journal.result != VerificationResult.Success) { + return journal; + } + if (journal.trustedCertsPrefixLen == 0) { + journal.result = VerificationResult.RootCertNotTrusted; + return journal; + } + // Check every trusted certificate to ensure none have been revoked + for (uint256 i = 0; i < journal.trustedCertsPrefixLen; i++) { + bytes32 certHash = journal.certs[i]; + if (i == 0) { + if (certHash != rootCert) { + journal.result = VerificationResult.RootCertNotTrusted; + return journal; + } + continue; + } + if (!trustedIntermediateCerts[certHash]) { + journal.result = VerificationResult.IntermediateCertsNotTrusted; + return journal; + } + } + uint64 timestamp = journal.timestamp / 1000; + if (timestamp + maxTimeDiff < block.timestamp || timestamp > block.timestamp) { + journal.result = VerificationResult.InvalidTimestamp; + return journal; + } + _cacheNewCert(journal); + return journal; + } + + /** + * @dev Internal function to verify zero-knowledge proofs using the appropriate coprocessor + * @param zkCoprocessor Type of ZK coprocessor (RiscZero or Succinct) + * @param programId Program identifier for the verification program + * @param output Encoded output data to verify + * @param proofBytes Zero-knowledge proof data + */ + function _verifyZk( + ZkCoProcessorType zkCoprocessor, + bytes32 programId, + bytes calldata output, + bytes calldata proofBytes + ) + internal + view + { + // Resolve the verifier address (route-specific or default) + address verifier = _resolveZkVerifier(zkCoprocessor, proofBytes); + + if (zkCoprocessor == ZkCoProcessorType.RiscZero) { + IRiscZeroVerifier(verifier).verify(proofBytes, programId, sha256(output)); + } else if (zkCoprocessor == ZkCoProcessorType.Succinct) { + ISP1Verifier(verifier).verifyProof(programId, output, proofBytes); + } else { + revert Unknown_Zk_Coprocessor(); + } + } + + /** + * @dev Internal function to resolve the ZK verifier address based on route configuration + * @param zkCoprocessor Type of ZK coprocessor + * @param proofBytes Proof data (selector extracted from first 4 bytes) + * @return Resolved verifier address + */ + function _resolveZkVerifier( + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + internal + view + returns (address) + { + bytes4 selector = bytes4(proofBytes[0:4]); + address verifier = _zkVerifierRoutes[zkCoprocessor][selector]; + + // Check if route is frozen + if (verifier == FROZEN) { + revert ZkRouteFrozen(zkCoprocessor, selector); + } + + // Fall back to default verifier if no route-specific one configured + if (verifier == address(0)) { + verifier = zkConfig[zkCoprocessor].zkVerifier; + } + + // Ensure verifier is configured + if (verifier == address(0)) { + revert ZkVerifierNotConfigured(zkCoprocessor); + } + + return verifier; + } +} diff --git a/test/multiproof/NitroEnclaveVerifier.t.sol b/test/multiproof/NitroEnclaveVerifier.t.sol new file mode 100644 index 00000000..143fb666 --- /dev/null +++ b/test/multiproof/NitroEnclaveVerifier.t.sol @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { Test } from "forge-std/Test.sol"; + +import { + INitroEnclaveVerifier, + ZkCoProcessorType, + ZkCoProcessorConfig, + VerifierJournal, + BatchVerifierJournal, + VerificationResult, + Pcr, + Bytes48 +} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; + +import { NitroEnclaveVerifier } from "src/multiproof/tee/NitroEnclaveVerifier.sol"; + +contract NitroEnclaveVerifierTest is Test { + NitroEnclaveVerifier public verifier; + + address public owner; + address public submitter; + address public mockZkVerifier; + + bytes32 public constant ROOT_CERT = keccak256("root-cert"); + bytes32 public constant INTERMEDIATE_CERT_1 = keccak256("intermediate-cert-1"); + bytes32 public constant INTERMEDIATE_CERT_2 = keccak256("intermediate-cert-2"); + bytes32 public constant VERIFIER_ID = keccak256("verifier-id"); + bytes32 public constant AGGREGATOR_ID = keccak256("aggregator-id"); + bytes32 public constant VERIFIER_PROOF_ID = keccak256("verifier-proof-id"); + + uint64 public constant MAX_TIME_DIFF = 3600; // 1 hour + + function setUp() public { + owner = address(this); + submitter = makeAddr("submitter"); + mockZkVerifier = makeAddr("mock-zk-verifier"); + + bytes32[] memory trustedCerts = new bytes32[](1); + trustedCerts[0] = INTERMEDIATE_CERT_1; + + verifier = new NitroEnclaveVerifier(owner, MAX_TIME_DIFF, trustedCerts); + verifier.setRootCert(ROOT_CERT); + verifier.setProofSubmitter(submitter); + } + + // ============ Constructor Tests ============ + + function testConstructorSetsOwner() public view { + assertEq(verifier.owner(), owner); + } + + function testConstructorSetsMaxTimeDiff() public view { + assertEq(verifier.maxTimeDiff(), MAX_TIME_DIFF); + } + + function testConstructorSetsTrustedCerts() public view { + assertTrue(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1)); + assertFalse(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_2)); + } + + // ============ setRootCert Tests ============ + + function testSetRootCert() public { + bytes32 newRoot = keccak256("new-root"); + verifier.setRootCert(newRoot); + assertEq(verifier.rootCert(), newRoot); + } + + function testSetRootCertRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.setRootCert(keccak256("bad")); + } + + // ============ setProofSubmitter Tests ============ + + function testSetProofSubmitter() public { + address newSubmitter = makeAddr("new-submitter"); + verifier.setProofSubmitter(newSubmitter); + assertEq(verifier.proofSubmitter(), newSubmitter); + } + + function testSetProofSubmitterRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.setProofSubmitter(address(0)); + } + + // ============ setZkConfiguration Tests ============ + + function testSetZkConfiguration() public { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockZkVerifier }); + + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + + ZkCoProcessorConfig memory stored = verifier.getZkConfig(ZkCoProcessorType.RiscZero); + assertEq(stored.verifierId, VERIFIER_ID); + assertEq(stored.aggregatorId, AGGREGATOR_ID); + assertEq(stored.zkVerifier, mockZkVerifier); + + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); + assertEq(verifier.getVerifierProofId(ZkCoProcessorType.RiscZero, VERIFIER_ID), VERIFIER_PROOF_ID); + } + + function testSetZkConfigurationRevertsIfNotOwner() public { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockZkVerifier }); + + vm.prank(submitter); + vm.expectRevert(); + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + } + + // ============ revokeCert Tests ============ + + function testRevokeCert() public { + assertTrue(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1)); + verifier.revokeCert(INTERMEDIATE_CERT_1); + assertFalse(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1)); + } + + function testRevokeCertRevertsIfNotTrusted() public { + vm.expectRevert("Certificate not found in trusted certs"); + verifier.revokeCert(keccak256("unknown-cert")); + } + + function testRevokeCertRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.revokeCert(INTERMEDIATE_CERT_1); + } + + // ============ updateVerifierId Tests ============ + + function testUpdateVerifierId() public { + _setUpZkConfig(); + + bytes32 newVerifierId = keccak256("new-verifier-id"); + bytes32 newVerifierProofId = keccak256("new-verifier-proof-id"); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, newVerifierId, newVerifierProofId); + + ZkCoProcessorConfig memory config = verifier.getZkConfig(ZkCoProcessorType.RiscZero); + assertEq(config.verifierId, newVerifierId); + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, newVerifierId)); + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); + assertEq(verifier.getVerifierProofId(ZkCoProcessorType.RiscZero, newVerifierId), newVerifierProofId); + } + + function testUpdateVerifierIdRevertsIfZero() public { + _setUpZkConfig(); + vm.expectRevert("Verifier ID cannot be zero"); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, bytes32(0), bytes32(0)); + } + + function testUpdateVerifierIdRevertsIfSame() public { + _setUpZkConfig(); + vm.expectRevert("Verifier ID is already the latest"); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID, VERIFIER_PROOF_ID); + } + + // ============ updateAggregatorId Tests ============ + + function testUpdateAggregatorId() public { + _setUpZkConfig(); + + bytes32 newAggregatorId = keccak256("new-aggregator-id"); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, newAggregatorId); + + ZkCoProcessorConfig memory config = verifier.getZkConfig(ZkCoProcessorType.RiscZero); + assertEq(config.aggregatorId, newAggregatorId); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, newAggregatorId)); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); + } + + function testUpdateAggregatorIdRevertsIfZero() public { + _setUpZkConfig(); + vm.expectRevert("Aggregator ID cannot be zero"); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, bytes32(0)); + } + + function testUpdateAggregatorIdRevertsIfSame() public { + _setUpZkConfig(); + vm.expectRevert("Aggregator ID is already the latest"); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + } + + // ============ removeVerifierId Tests ============ + + function testRemoveVerifierId() public { + _setUpZkConfig(); + + bytes32 newId = keccak256("new-verifier-id"); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, newId, keccak256("proof")); + + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); + assertFalse(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); + assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, newId)); + } + + function testRemoveVerifierIdRevertsIfLatest() public { + _setUpZkConfig(); + + vm.expectRevert( + abi.encodeWithSelector( + INitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, VERIFIER_ID + ) + ); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); + } + + function testRemoveVerifierIdRevertsIfNotExists() public { + _setUpZkConfig(); + vm.expectRevert("Verifier ID does not exist"); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, keccak256("nonexistent")); + } + + // ============ removeAggregatorId Tests ============ + + function testRemoveAggregatorId() public { + _setUpZkConfig(); + + bytes32 newId = keccak256("new-aggregator-id"); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, newId); + + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + assertFalse(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); + assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, newId)); + } + + function testRemoveAggregatorIdRevertsIfLatest() public { + _setUpZkConfig(); + + vm.expectRevert( + abi.encodeWithSelector( + INitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, AGGREGATOR_ID + ) + ); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); + } + + function testRemoveAggregatorIdRevertsIfNotExists() public { + _setUpZkConfig(); + vm.expectRevert("Aggregator ID does not exist"); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, keccak256("nonexistent")); + } + + // ============ addVerifyRoute / freezeVerifyRoute Tests ============ + + function testAddVerifyRoute() public { + bytes4 selector = bytes4(keccak256("test")); + address routeVerifier = makeAddr("route-verifier"); + + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector), routeVerifier); + } + + function testAddVerifyRouteRevertsIfZeroAddress() public { + vm.expectRevert("Verifier cannot be zero address"); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(uint32(0x01)), address(0)); + } + + function testFreezeVerifyRoute() public { + bytes4 selector = bytes4(keccak256("test")); + address routeVerifier = makeAddr("route-verifier"); + + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + vm.expectRevert( + abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector); + } + + function testAddVerifyRouteRevertsIfFrozen() public { + bytes4 selector = bytes4(keccak256("test")); + address routeVerifier = makeAddr("route-verifier"); + + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + vm.expectRevert( + abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + } + + function testFreezeVerifyRouteRevertsIfAlreadyFrozen() public { + bytes4 selector = bytes4(keccak256("test")); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("v")); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + vm.expectRevert( + abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + } + + // ============ getZkVerifier Tests ============ + + function testGetZkVerifierFallsBackToDefault() public { + _setUpZkConfig(); + + bytes4 unknownSelector = bytes4(0xdeadbeef); + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, unknownSelector), mockZkVerifier); + } + + function testGetZkVerifierReturnsRouteSpecific() public { + _setUpZkConfig(); + + bytes4 selector = bytes4(keccak256("special")); + address routeVerifier = makeAddr("route-verifier"); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector), routeVerifier); + } + + // ============ checkTrustedIntermediateCerts Tests ============ + + function testCheckTrustedIntermediateCerts() public view { + bytes32[][] memory reportCerts = new bytes32[][](1); + reportCerts[0] = new bytes32[](3); + reportCerts[0][0] = ROOT_CERT; + reportCerts[0][1] = INTERMEDIATE_CERT_1; + reportCerts[0][2] = INTERMEDIATE_CERT_2; // not trusted + + uint8[] memory results = verifier.checkTrustedIntermediateCerts(reportCerts); + assertEq(results[0], 2); // root + 1 intermediate trusted + } + + function testCheckTrustedIntermediateCertsRevertsIfWrongRoot() public { + bytes32[][] memory reportCerts = new bytes32[][](1); + reportCerts[0] = new bytes32[](1); + reportCerts[0][0] = keccak256("wrong-root"); + + vm.expectRevert("First certificate must be the root certificate"); + verifier.checkTrustedIntermediateCerts(reportCerts); + } + + // ============ verify Tests ============ + + function testVerifyRevertsIfNotProofSubmitter() public { + vm.expectRevert("Only the proof submitter can verify proofs"); + verifier.verify("", ZkCoProcessorType.RiscZero, ""); + } + + function testVerifySuccessfulJournal() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.Success)); + } + + function testVerifyJournalRootCertNotTrusted() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + journal.certs[0] = keccak256("wrong-root"); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.RootCertNotTrusted)); + } + + function testVerifyJournalRootCertNotTrustedZeroPrefixLen() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + journal.trustedCertsPrefixLen = 0; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.RootCertNotTrusted)); + } + + function testVerifyJournalIntermediateCertNotTrusted() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + // Replace trusted intermediate with untrusted one, but keep trustedCertsPrefixLen = 2 + journal.certs[1] = keccak256("untrusted-intermediate"); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + } + + function testVerifyJournalInvalidTimestampTooOld() public { + _setUpZkConfig(); + + vm.warp(100_000); + + VerifierJournal memory journal = _createSuccessJournal(); + // Set timestamp far in the past — exceeds maxTimeDiff + journal.timestamp = 1000; // 1 second in ms, way too old relative to block.timestamp=100000 + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + } + + function testVerifyJournalInvalidTimestampFuture() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + // Set timestamp in the future (converted to ms) + journal.timestamp = uint64(block.timestamp + 100) * 1000; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + } + + function testVerifyCachesNewCerts() public { + _setUpZkConfig(); + + bytes32 newCert = keccak256("new-leaf-cert"); + assertFalse(verifier.trustedIntermediateCerts(newCert)); + + VerifierJournal memory journal = _createSuccessJournal(); + // Add a new cert beyond the trusted prefix that will get cached + bytes32[] memory certs = new bytes32[](3); + certs[0] = ROOT_CERT; + certs[1] = INTERMEDIATE_CERT_1; + certs[2] = newCert; + journal.certs = certs; + journal.trustedCertsPrefixLen = 2; // only root + 1 intermediate are pre-trusted + + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertTrue(verifier.trustedIntermediateCerts(newCert)); + } + + function testVerifyJournalPassesThroughFailedResult() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + journal.result = VerificationResult.IntermediateCertsNotTrusted; + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + } + + // ============ batchVerify Tests ============ + + function testBatchVerifyRevertsIfNotProofSubmitter() public { + vm.expectRevert("Only the proof submitter can verify proofs"); + verifier.batchVerify("", ZkCoProcessorType.RiscZero, ""); + } + + function testBatchVerifySuccess() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + VerifierJournal[] memory outputs = new VerifierJournal[](2); + outputs[0] = journal; + outputs[1] = journal; + + BatchVerifierJournal memory batchJournal = + BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); + + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(results.length, 2); + assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); + assertEq(uint8(results[1].result), uint8(VerificationResult.Success)); + } + + function testBatchVerifyRevertsIfVerifierVkMismatch() public { + _setUpZkConfig(); + + VerifierJournal[] memory outputs = new VerifierJournal[](1); + outputs[0] = _createSuccessJournal(); + + BatchVerifierJournal memory batchJournal = + BatchVerifierJournal({ verifierVk: keccak256("wrong-vk"), outputs: outputs }); + + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + vm.expectRevert("Verifier VK does not match the expected verifier proof ID"); + verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + // ============ verifyWithProgramId / batchVerifyWithProgramId Tests ============ + + function testVerifyWithProgramIdReverts() public { + vm.expectRevert("Not implemented"); + verifier.verifyWithProgramId("", ZkCoProcessorType.RiscZero, bytes32(0), ""); + } + + function testBatchVerifyWithProgramIdReverts() public { + vm.expectRevert("Not implemented"); + verifier.batchVerifyWithProgramId("", ZkCoProcessorType.RiscZero, bytes32(0), bytes32(0), ""); + } + + // ============ Revoked Cert Invalidates Journal ============ + + function testRevokedCertInvalidatesVerification() public { + _setUpZkConfig(); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + // Revoke the intermediate cert before verification + verifier.revokeCert(INTERMEDIATE_CERT_1); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + } + + // ============ Helpers ============ + + function _setUpZkConfig() internal { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockZkVerifier }); + + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + } + + function _createSuccessJournal() internal view returns (VerifierJournal memory) { + bytes32[] memory certs = new bytes32[](2); + certs[0] = ROOT_CERT; + certs[1] = INTERMEDIATE_CERT_1; + + Pcr[] memory pcrs = new Pcr[](0); + + return VerifierJournal({ + result: VerificationResult.Success, + trustedCertsPrefixLen: 2, + timestamp: uint64(block.timestamp) * 1000, + certs: certs, + userData: "", + nonce: "", + publicKey: "", + pcrs: pcrs, + moduleId: "test-module" + }); + } + + function _mockRiscZeroVerify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { + // IRiscZeroVerifier.verify(proofBytes, programId, sha256(output)) + vm.mockCall( + mockZkVerifier, + abi.encodeWithSelector( + bytes4(keccak256("verify(bytes,bytes32,bytes32)")), proofBytes, programId, sha256(output) + ), + "" + ); + } +} From 0d27dd357d347583833086878994248e353c9cc7 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 13 Mar 2026 10:28:20 -0400 Subject: [PATCH 2/5] pr feedback --- src/multiproof/tee/NitroEnclaveVerifier.sol | 116 +++++-- test/multiproof/NitroEnclaveVerifier.t.sol | 364 ++++++++++++++++---- 2 files changed, 395 insertions(+), 85 deletions(-) diff --git a/src/multiproof/tee/NitroEnclaveVerifier.sol b/src/multiproof/tee/NitroEnclaveVerifier.sol index ad18ccfc..8f2b6cde 100644 --- a/src/multiproof/tee/NitroEnclaveVerifier.sol +++ b/src/multiproof/tee/NitroEnclaveVerifier.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: Apache2.0 +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { Ownable } from "@solady/auth/Ownable.sol"; @@ -24,10 +24,10 @@ import { * @dev Custom version of Automata's NitroEnclaveVerifier contract at * https://github.com/automata-network/aws-nitro-enclave-attestation/tree/26c90565cb009e6539643a0956f9502a12ade672 * - * Differences: - * - Verification of ZK proofs is now a privileged action - * - All privileged actions are monitored - * - Removes verification with Program ID and Pico logic + * Differences from the upstream Automata contract: + * - Verification of ZK proofs is restricted to an authorized proof submitter address + * - All privileged actions emit events for monitoring + * - Removes verification-with-explicit-program-ID and Pico logic * * This contract provides on-chain verification of AWS Nitro Enclave attestation reports by validating * zero-knowledge proofs generated off-chain. It supports both single and batch verification modes @@ -79,6 +79,43 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { /// @dev Mapping from verifierId to its corresponding verifierProofId representation mapping(ZkCoProcessorType => mapping(bytes32 verifierId => bytes32 verifierProofId)) private _verifierProofIds; + // ============ Custom Errors ============ + + /// @dev Thrown when a caller other than the authorized proof submitter calls verify or batchVerify + error CallerNotProofSubmitter(); + + /// @dev Thrown when a certificate hash is not found in the trusted intermediate certificates set + error CertificateNotFound(bytes32 certHash); + + /// @dev Thrown when a program ID argument is bytes32(0) + error ZeroProgramId(); + + /// @dev Thrown when attempting to set a program ID that is already the latest + error ProgramIdAlreadyLatest(ZkCoProcessorType zkCoProcessor, bytes32 identifier); + + /// @dev Thrown when attempting to remove or operate on a program ID that does not exist in the set + error ProgramIdNotFound(ZkCoProcessorType zkCoProcessor, bytes32 identifier); + + /// @dev Thrown when a zero address is provided where a verifier address is required + error ZeroVerifierAddress(); + + /// @dev Thrown when a zero address is provided for the proof submitter + error ZeroProofSubmitter(); + + /// @dev Thrown when the batch journal's verifier VK does not match the expected verifier proof ID + error VerifierVkMismatch(bytes32 expected, bytes32 actual); + + /// @dev Thrown when the first certificate in a chain does not match the stored root certificate + error RootCertMismatch(bytes32 expected, bytes32 actual); + + /// @dev Thrown when calling verifyWithProgramId or batchVerifyWithProgramId, which are intentionally disabled + error NotImplemented(); + + /// @dev Thrown when a zero or zero-equivalent maxTimeDiff is provided + error ZeroMaxTimeDiff(); + + // ============ Events ============ + /// @dev Event emitted when the proof submitter address is changed event ProofSubmitterChanged(address newProofSubmitter); @@ -91,6 +128,9 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { /// @dev Event emitted when a certificate is revoked event CertRevoked(bytes32 certHash); + /// @dev Event emitted when the maximum time difference is updated + event MaxTimeDiffUpdated(uint64 newMaxTimeDiff); + /** * @dev Initializes the contract with owner, time tolerance and initial trusted certificates * @param _owner Address to be set as the contract owner @@ -101,6 +141,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { * The root certificate must be set separately after deployment. */ constructor(address _owner, uint64 _maxTimeDiff, bytes32[] memory _initializeTrustedCerts) { + if (_maxTimeDiff == 0) revert ZeroMaxTimeDiff(); maxTimeDiff = _maxTimeDiff; for (uint256 i = 0; i < _initializeTrustedCerts.length; i++) { trustedIntermediateCerts[_initializeTrustedCerts[i]] = true; @@ -215,7 +256,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { bytes32[] calldata certs = _report_certs[i]; uint8 trustedCertPrefixLen = 1; if (certs[0] != rootCertHash) { - revert("First certificate must be the root certificate"); + revert RootCertMismatch(rootCertHash, certs[0]); } for (uint256 j = 1; j < certs.length; j++) { if (!trustedIntermediateCerts[certs[j]]) { @@ -245,6 +286,20 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { emit RootCertChanged(_rootCert); } + /** + * @dev Updates the maximum allowed time difference for attestation timestamp validation + * @param _maxTimeDiff New maximum time difference in seconds + * + * Requirements: + * - Only callable by contract owner + * - Must be greater than zero + */ + function setMaxTimeDiff(uint64 _maxTimeDiff) external onlyOwner { + if (_maxTimeDiff == 0) revert ZeroMaxTimeDiff(); + maxTimeDiff = _maxTimeDiff; + emit MaxTimeDiffUpdated(_maxTimeDiff); + } + /** * @dev Configures zero-knowledge verification parameters for a specific coprocessor * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) @@ -296,7 +351,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { */ function revokeCert(bytes32 _certHash) external onlyOwner { if (!trustedIntermediateCerts[_certHash]) { - revert("Certificate not found in trusted certs"); + revert CertificateNotFound(_certHash); } delete trustedIntermediateCerts[_certHash]; emit CertRevoked(_certHash); @@ -316,8 +371,10 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { external onlyOwner { - require(_newVerifierId != bytes32(0), "Verifier ID cannot be zero"); - require(zkConfig[_zkCoProcessor].verifierId != _newVerifierId, "Verifier ID is already the latest"); + if (_newVerifierId == bytes32(0)) revert ZeroProgramId(); + if (zkConfig[_zkCoProcessor].verifierId == _newVerifierId) { + revert ProgramIdAlreadyLatest(_zkCoProcessor, _newVerifierId); + } zkConfig[_zkCoProcessor].verifierId = _newVerifierId; _verifierIdSet[_zkCoProcessor].add(_newVerifierId); @@ -332,8 +389,10 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { * @param _newAggregatorId New aggregator program ID to set as latest */ function updateAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _newAggregatorId) external onlyOwner { - require(_newAggregatorId != bytes32(0), "Aggregator ID cannot be zero"); - require(zkConfig[_zkCoProcessor].aggregatorId != _newAggregatorId, "Aggregator ID is already the latest"); + if (_newAggregatorId == bytes32(0)) revert ZeroProgramId(); + if (zkConfig[_zkCoProcessor].aggregatorId == _newAggregatorId) { + revert ProgramIdAlreadyLatest(_zkCoProcessor, _newAggregatorId); + } zkConfig[_zkCoProcessor].aggregatorId = _newAggregatorId; _aggregatorIdSet[_zkCoProcessor].add(_newAggregatorId); @@ -347,7 +406,9 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { * @param _verifierId Verifier program ID to remove */ function removeVerifierId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external onlyOwner { - require(_verifierIdSet[_zkCoProcessor].contains(_verifierId), "Verifier ID does not exist"); + if (!_verifierIdSet[_zkCoProcessor].contains(_verifierId)) { + revert ProgramIdNotFound(_zkCoProcessor, _verifierId); + } // Cannot remove the latest verifier ID - must update to a new one first if (zkConfig[_zkCoProcessor].verifierId == _verifierId) { @@ -365,7 +426,9 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { * @param _aggregatorId Aggregator program ID to remove */ function removeAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _aggregatorId) external onlyOwner { - require(_aggregatorIdSet[_zkCoProcessor].contains(_aggregatorId), "Aggregator ID does not exist"); + if (!_aggregatorIdSet[_zkCoProcessor].contains(_aggregatorId)) { + revert ProgramIdNotFound(_zkCoProcessor, _aggregatorId); + } // Cannot remove the latest aggregator ID - must update to a new one first if (zkConfig[_zkCoProcessor].aggregatorId == _aggregatorId) { @@ -383,7 +446,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { * @param _verifier Address of the verifier contract for this route */ function addVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector, address _verifier) external onlyOwner { - require(_verifier != address(0), "Verifier cannot be zero address"); + if (_verifier == address(0)) revert ZeroVerifierAddress(); if (_zkVerifierRoutes[_zkCoProcessor][_selector] == FROZEN) { revert ZkRouteFrozen(_zkCoProcessor, _selector); @@ -414,8 +477,13 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { /** * @dev Sets the proof submitter address * @param _proofSubmitter The address of the proof submitter + * + * Requirements: + * - Only callable by contract owner + * - Address must not be zero */ function setProofSubmitter(address _proofSubmitter) external onlyOwner { + if (_proofSubmitter == address(0)) revert ZeroProofSubmitter(); proofSubmitter = _proofSubmitter; emit ProofSubmitterChanged(_proofSubmitter); } @@ -451,7 +519,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { external returns (VerifierJournal memory journal) { - require(msg.sender == proofSubmitter, "Only the proof submitter can verify proofs"); + if (msg.sender != proofSubmitter) revert CallerNotProofSubmitter(); bytes32 programId = zkConfig[zkCoprocessor].verifierId; _verifyZk(zkCoprocessor, programId, output, proofBytes); journal = abi.decode(output, (VerifierJournal)); @@ -483,7 +551,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { external returns (VerifierJournal[] memory results) { - require(msg.sender == proofSubmitter, "Only the proof submitter can verify proofs"); + if (msg.sender != proofSubmitter) revert CallerNotProofSubmitter(); bytes32 aggregatorId = zkConfig[zkCoprocessor].aggregatorId; bytes32 verifierId = zkConfig[zkCoprocessor].verifierId; bytes32 verifierProofId = _verifierProofIds[zkCoprocessor][verifierId]; @@ -491,7 +559,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { _verifyZk(zkCoprocessor, aggregatorId, output, proofBytes); BatchVerifierJournal memory batchJournal = abi.decode(output, (BatchVerifierJournal)); if (batchJournal.verifierVk != verifierProofId) { - revert("Verifier VK does not match the expected verifier proof ID"); + revert VerifierVkMismatch(verifierProofId, batchJournal.verifierVk); } uint256 n = batchJournal.outputs.length; results = new VerifierJournal[](n); @@ -501,7 +569,10 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { emit BatchAttestationSubmitted(verifierId, zkCoprocessor, abi.encode(results)); } - // To meet interface requirements + /** + * @notice Not implemented — explicit program ID verification is intentionally disabled. + * @dev This function exists solely to satisfy the INitroEnclaveVerifier interface. + */ function verifyWithProgramId( bytes calldata, ZkCoProcessorType, @@ -512,10 +583,13 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { pure returns (VerifierJournal memory) { - revert("Not implemented"); + revert NotImplemented(); } - // To meet interface requirements + /** + * @notice Not implemented — explicit program ID verification is intentionally disabled. + * @dev This function exists solely to satisfy the INitroEnclaveVerifier interface. + */ function batchVerifyWithProgramId( bytes calldata, ZkCoProcessorType, @@ -527,7 +601,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { pure returns (VerifierJournal[] memory) { - revert("Not implemented"); + revert NotImplemented(); } // ============ Internal Functions ============ diff --git a/test/multiproof/NitroEnclaveVerifier.t.sol b/test/multiproof/NitroEnclaveVerifier.t.sol index 143fb666..50a8281a 100644 --- a/test/multiproof/NitroEnclaveVerifier.t.sol +++ b/test/multiproof/NitroEnclaveVerifier.t.sol @@ -21,7 +21,8 @@ contract NitroEnclaveVerifierTest is Test { address public owner; address public submitter; - address public mockZkVerifier; + address public mockRiscZeroVerifier; + address public mockSP1Verifier; bytes32 public constant ROOT_CERT = keccak256("root-cert"); bytes32 public constant INTERMEDIATE_CERT_1 = keccak256("intermediate-cert-1"); @@ -32,10 +33,16 @@ contract NitroEnclaveVerifierTest is Test { uint64 public constant MAX_TIME_DIFF = 3600; // 1 hour + // Realistic timestamp so timestamp validation tests work correctly + uint256 internal constant REALISTIC_TIMESTAMP = 1_700_000_000; + function setUp() public { + vm.warp(REALISTIC_TIMESTAMP); + owner = address(this); submitter = makeAddr("submitter"); - mockZkVerifier = makeAddr("mock-zk-verifier"); + mockRiscZeroVerifier = makeAddr("mock-riscZero-verifier"); + mockSP1Verifier = makeAddr("mock-sp1-verifier"); bytes32[] memory trustedCerts = new bytes32[](1); trustedCerts[0] = INTERMEDIATE_CERT_1; @@ -60,6 +67,12 @@ contract NitroEnclaveVerifierTest is Test { assertFalse(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_2)); } + function testConstructorRevertsIfZeroMaxTimeDiff() public { + bytes32[] memory certs = new bytes32[](0); + vm.expectRevert(NitroEnclaveVerifier.ZeroMaxTimeDiff.selector); + new NitroEnclaveVerifier(owner, 0, certs); + } + // ============ setRootCert Tests ============ function testSetRootCert() public { @@ -74,6 +87,25 @@ contract NitroEnclaveVerifierTest is Test { verifier.setRootCert(keccak256("bad")); } + // ============ setMaxTimeDiff Tests ============ + + function testSetMaxTimeDiff() public { + uint64 newTimeDiff = 7200; + verifier.setMaxTimeDiff(newTimeDiff); + assertEq(verifier.maxTimeDiff(), newTimeDiff); + } + + function testSetMaxTimeDiffRevertsIfZero() public { + vm.expectRevert(NitroEnclaveVerifier.ZeroMaxTimeDiff.selector); + verifier.setMaxTimeDiff(0); + } + + function testSetMaxTimeDiffRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.setMaxTimeDiff(7200); + } + // ============ setProofSubmitter Tests ============ function testSetProofSubmitter() public { @@ -82,24 +114,30 @@ contract NitroEnclaveVerifierTest is Test { assertEq(verifier.proofSubmitter(), newSubmitter); } + function testSetProofSubmitterRevertsIfZeroAddress() public { + vm.expectRevert(NitroEnclaveVerifier.ZeroProofSubmitter.selector); + verifier.setProofSubmitter(address(0)); + } + function testSetProofSubmitterRevertsIfNotOwner() public { vm.prank(submitter); vm.expectRevert(); - verifier.setProofSubmitter(address(0)); + verifier.setProofSubmitter(makeAddr("anyone")); } // ============ setZkConfiguration Tests ============ function testSetZkConfiguration() public { - ZkCoProcessorConfig memory config = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockZkVerifier }); + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); ZkCoProcessorConfig memory stored = verifier.getZkConfig(ZkCoProcessorType.RiscZero); assertEq(stored.verifierId, VERIFIER_ID); assertEq(stored.aggregatorId, AGGREGATOR_ID); - assertEq(stored.zkVerifier, mockZkVerifier); + assertEq(stored.zkVerifier, mockRiscZeroVerifier); assertTrue(verifier.isVerifierIdSupported(ZkCoProcessorType.RiscZero, VERIFIER_ID)); assertTrue(verifier.isAggregatorIdSupported(ZkCoProcessorType.RiscZero, AGGREGATOR_ID)); @@ -107,8 +145,9 @@ contract NitroEnclaveVerifierTest is Test { } function testSetZkConfigurationRevertsIfNotOwner() public { - ZkCoProcessorConfig memory config = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockZkVerifier }); + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); vm.prank(submitter); vm.expectRevert(); @@ -124,8 +163,9 @@ contract NitroEnclaveVerifierTest is Test { } function testRevokeCertRevertsIfNotTrusted() public { - vm.expectRevert("Certificate not found in trusted certs"); - verifier.revokeCert(keccak256("unknown-cert")); + bytes32 unknown = keccak256("unknown-cert"); + vm.expectRevert(abi.encodeWithSelector(NitroEnclaveVerifier.CertificateNotFound.selector, unknown)); + verifier.revokeCert(unknown); } function testRevokeCertRevertsIfNotOwner() public { @@ -137,7 +177,7 @@ contract NitroEnclaveVerifierTest is Test { // ============ updateVerifierId Tests ============ function testUpdateVerifierId() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes32 newVerifierId = keccak256("new-verifier-id"); bytes32 newVerifierProofId = keccak256("new-verifier-proof-id"); @@ -151,21 +191,32 @@ contract NitroEnclaveVerifierTest is Test { } function testUpdateVerifierIdRevertsIfZero() public { - _setUpZkConfig(); - vm.expectRevert("Verifier ID cannot be zero"); + _setUpRiscZeroConfig(); + vm.expectRevert(NitroEnclaveVerifier.ZeroProgramId.selector); verifier.updateVerifierId(ZkCoProcessorType.RiscZero, bytes32(0), bytes32(0)); } function testUpdateVerifierIdRevertsIfSame() public { - _setUpZkConfig(); - vm.expectRevert("Verifier ID is already the latest"); + _setUpRiscZeroConfig(); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdAlreadyLatest.selector, ZkCoProcessorType.RiscZero, VERIFIER_ID + ) + ); verifier.updateVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID, VERIFIER_PROOF_ID); } + function testUpdateVerifierIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.updateVerifierId(ZkCoProcessorType.RiscZero, keccak256("new"), keccak256("proof")); + } + // ============ updateAggregatorId Tests ============ function testUpdateAggregatorId() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes32 newAggregatorId = keccak256("new-aggregator-id"); verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, newAggregatorId); @@ -177,21 +228,32 @@ contract NitroEnclaveVerifierTest is Test { } function testUpdateAggregatorIdRevertsIfZero() public { - _setUpZkConfig(); - vm.expectRevert("Aggregator ID cannot be zero"); + _setUpRiscZeroConfig(); + vm.expectRevert(NitroEnclaveVerifier.ZeroProgramId.selector); verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, bytes32(0)); } function testUpdateAggregatorIdRevertsIfSame() public { - _setUpZkConfig(); - vm.expectRevert("Aggregator ID is already the latest"); + _setUpRiscZeroConfig(); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdAlreadyLatest.selector, ZkCoProcessorType.RiscZero, AGGREGATOR_ID + ) + ); verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); } + function testUpdateAggregatorIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, keccak256("new")); + } + // ============ removeVerifierId Tests ============ function testRemoveVerifierId() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes32 newId = keccak256("new-verifier-id"); verifier.updateVerifierId(ZkCoProcessorType.RiscZero, newId, keccak256("proof")); @@ -202,7 +264,7 @@ contract NitroEnclaveVerifierTest is Test { } function testRemoveVerifierIdRevertsIfLatest() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); vm.expectRevert( abi.encodeWithSelector( @@ -213,15 +275,27 @@ contract NitroEnclaveVerifierTest is Test { } function testRemoveVerifierIdRevertsIfNotExists() public { - _setUpZkConfig(); - vm.expectRevert("Verifier ID does not exist"); - verifier.removeVerifierId(ZkCoProcessorType.RiscZero, keccak256("nonexistent")); + _setUpRiscZeroConfig(); + bytes32 nonexistent = keccak256("nonexistent"); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdNotFound.selector, ZkCoProcessorType.RiscZero, nonexistent + ) + ); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, nonexistent); + } + + function testRemoveVerifierIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); } // ============ removeAggregatorId Tests ============ function testRemoveAggregatorId() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes32 newId = keccak256("new-aggregator-id"); verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, newId); @@ -232,7 +306,7 @@ contract NitroEnclaveVerifierTest is Test { } function testRemoveAggregatorIdRevertsIfLatest() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); vm.expectRevert( abi.encodeWithSelector( @@ -243,9 +317,21 @@ contract NitroEnclaveVerifierTest is Test { } function testRemoveAggregatorIdRevertsIfNotExists() public { - _setUpZkConfig(); - vm.expectRevert("Aggregator ID does not exist"); - verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, keccak256("nonexistent")); + _setUpRiscZeroConfig(); + bytes32 nonexistent = keccak256("nonexistent"); + vm.expectRevert( + abi.encodeWithSelector( + NitroEnclaveVerifier.ProgramIdNotFound.selector, ZkCoProcessorType.RiscZero, nonexistent + ) + ); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, nonexistent); + } + + function testRemoveAggregatorIdRevertsIfNotOwner() public { + _setUpRiscZeroConfig(); + vm.prank(submitter); + vm.expectRevert(); + verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); } // ============ addVerifyRoute / freezeVerifyRoute Tests ============ @@ -259,10 +345,16 @@ contract NitroEnclaveVerifierTest is Test { } function testAddVerifyRouteRevertsIfZeroAddress() public { - vm.expectRevert("Verifier cannot be zero address"); + vm.expectRevert(NitroEnclaveVerifier.ZeroVerifierAddress.selector); verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(uint32(0x01)), address(0)); } + function testAddVerifyRouteRevertsIfNotOwner() public { + vm.prank(submitter); + vm.expectRevert(); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(keccak256("test")), makeAddr("v")); + } + function testFreezeVerifyRoute() public { bytes4 selector = bytes4(keccak256("test")); address routeVerifier = makeAddr("route-verifier"); @@ -300,17 +392,26 @@ contract NitroEnclaveVerifierTest is Test { verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); } + function testFreezeVerifyRouteRevertsIfNotOwner() public { + bytes4 selector = bytes4(keccak256("test")); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("v")); + + vm.prank(submitter); + vm.expectRevert(); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + } + // ============ getZkVerifier Tests ============ function testGetZkVerifierFallsBackToDefault() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes4 unknownSelector = bytes4(0xdeadbeef); - assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, unknownSelector), mockZkVerifier); + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, unknownSelector), mockRiscZeroVerifier); } function testGetZkVerifierReturnsRouteSpecific() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes4 selector = bytes4(keccak256("special")); address routeVerifier = makeAddr("route-verifier"); @@ -333,23 +434,83 @@ contract NitroEnclaveVerifierTest is Test { } function testCheckTrustedIntermediateCertsRevertsIfWrongRoot() public { + bytes32 wrongRoot = keccak256("wrong-root"); bytes32[][] memory reportCerts = new bytes32[][](1); reportCerts[0] = new bytes32[](1); - reportCerts[0][0] = keccak256("wrong-root"); + reportCerts[0][0] = wrongRoot; - vm.expectRevert("First certificate must be the root certificate"); + vm.expectRevert(abi.encodeWithSelector(NitroEnclaveVerifier.RootCertMismatch.selector, ROOT_CERT, wrongRoot)); verifier.checkTrustedIntermediateCerts(reportCerts); } - // ============ verify Tests ============ + // ============ verify — access control ============ function testVerifyRevertsIfNotProofSubmitter() public { - vm.expectRevert("Only the proof submitter can verify proofs"); + vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); verifier.verify("", ZkCoProcessorType.RiscZero, ""); } + // ============ verify — ZkVerifierNotConfigured ============ + + function testVerifyRevertsIfZkVerifierNotConfigured() public { + // Set up config WITHOUT a zkVerifier address (zero) + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(INitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.RiscZero) + ); + verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + // ============ verify — Unknown_Zk_Coprocessor ============ + + function testVerifyRevertsForUnknownCoprocessor() public { + // Use ZkCoProcessorType.Unknown (0) — not RiscZero or Succinct + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); + verifier.setZkConfiguration(ZkCoProcessorType.Unknown, config, VERIFIER_PROOF_ID); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert(INitroEnclaveVerifier.Unknown_Zk_Coprocessor.selector); + verifier.verify(output, ZkCoProcessorType.Unknown, proofBytes); + } + + // ============ verify — ZkRouteFrozen during verify() ============ + + function testVerifyRevertsIfRouteFrozen() public { + _setUpRiscZeroConfig(); + + bytes4 selector = bytes4(0); // matches the selector in our proofBytes + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("route-v")); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + ); + verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + // ============ verify — RiscZero happy path ============ + function testVerifySuccessfulJournal() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); @@ -364,7 +525,7 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyJournalRootCertNotTrusted() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); journal.certs[0] = keccak256("wrong-root"); @@ -380,7 +541,7 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyJournalRootCertNotTrustedZeroPrefixLen() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); journal.trustedCertsPrefixLen = 0; @@ -396,7 +557,7 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyJournalIntermediateCertNotTrusted() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); // Replace trusted intermediate with untrusted one, but keep trustedCertsPrefixLen = 2 @@ -413,13 +574,11 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyJournalInvalidTimestampTooOld() public { - _setUpZkConfig(); - - vm.warp(100_000); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); - // Set timestamp far in the past — exceeds maxTimeDiff - journal.timestamp = 1000; // 1 second in ms, way too old relative to block.timestamp=100000 + // Set timestamp far in the past — more than maxTimeDiff seconds ago (in ms) + journal.timestamp = uint64(block.timestamp - MAX_TIME_DIFF - 1) * 1000; bytes memory output = abi.encode(journal); bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); @@ -432,7 +591,7 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyJournalInvalidTimestampFuture() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); // Set timestamp in the future (converted to ms) @@ -449,7 +608,7 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyCachesNewCerts() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); bytes32 newCert = keccak256("new-leaf-cert"); assertFalse(verifier.trustedIntermediateCerts(newCert)); @@ -475,7 +634,7 @@ contract NitroEnclaveVerifierTest is Test { } function testVerifyJournalPassesThroughFailedResult() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); journal.result = VerificationResult.IntermediateCertsNotTrusted; @@ -490,15 +649,51 @@ contract NitroEnclaveVerifierTest is Test { assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); } + // ============ verify — Succinct SP1 happy path ============ + + function testVerifySuccessfulJournalSP1() public { + _setUpSP1Config(); + + VerifierJournal memory journal = _createSuccessJournal(); + bytes memory output = abi.encode(journal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockSP1Verify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes); + + assertEq(uint8(result.result), uint8(VerificationResult.Success)); + } + + function testVerifyRevertsIfNotProofSubmitterSP1() public { + vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); + verifier.verify("", ZkCoProcessorType.Succinct, ""); + } + + function testVerifyRevertsIfZkVerifierNotConfiguredSP1() public { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); + verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); + + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + vm.prank(submitter); + vm.expectRevert( + abi.encodeWithSelector(INitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.Succinct) + ); + verifier.verify(abi.encode(_createSuccessJournal()), ZkCoProcessorType.Succinct, proofBytes); + } + // ============ batchVerify Tests ============ function testBatchVerifyRevertsIfNotProofSubmitter() public { - vm.expectRevert("Only the proof submitter can verify proofs"); + vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); verifier.batchVerify("", ZkCoProcessorType.RiscZero, ""); } function testBatchVerifySuccess() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); VerifierJournal[] memory outputs = new VerifierJournal[](2); @@ -522,13 +717,13 @@ contract NitroEnclaveVerifierTest is Test { } function testBatchVerifyRevertsIfVerifierVkMismatch() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); + bytes32 wrongVk = keccak256("wrong-vk"); VerifierJournal[] memory outputs = new VerifierJournal[](1); outputs[0] = _createSuccessJournal(); - BatchVerifierJournal memory batchJournal = - BatchVerifierJournal({ verifierVk: keccak256("wrong-vk"), outputs: outputs }); + BatchVerifierJournal memory batchJournal = BatchVerifierJournal({ verifierVk: wrongVk, outputs: outputs }); bytes memory output = abi.encode(batchJournal); bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); @@ -536,26 +731,50 @@ contract NitroEnclaveVerifierTest is Test { _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); vm.prank(submitter); - vm.expectRevert("Verifier VK does not match the expected verifier proof ID"); + vm.expectRevert( + abi.encodeWithSelector(NitroEnclaveVerifier.VerifierVkMismatch.selector, VERIFIER_PROOF_ID, wrongVk) + ); verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); } + function testBatchVerifySuccessSP1() public { + _setUpSP1Config(); + + VerifierJournal memory journal = _createSuccessJournal(); + VerifierJournal[] memory outputs = new VerifierJournal[](1); + outputs[0] = journal; + + BatchVerifierJournal memory batchJournal = + BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); + + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + + _mockSP1Verify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.Succinct, proofBytes); + + assertEq(results.length, 1); + assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); + } + // ============ verifyWithProgramId / batchVerifyWithProgramId Tests ============ function testVerifyWithProgramIdReverts() public { - vm.expectRevert("Not implemented"); + vm.expectRevert(NitroEnclaveVerifier.NotImplemented.selector); verifier.verifyWithProgramId("", ZkCoProcessorType.RiscZero, bytes32(0), ""); } function testBatchVerifyWithProgramIdReverts() public { - vm.expectRevert("Not implemented"); + vm.expectRevert(NitroEnclaveVerifier.NotImplemented.selector); verifier.batchVerifyWithProgramId("", ZkCoProcessorType.RiscZero, bytes32(0), bytes32(0), ""); } // ============ Revoked Cert Invalidates Journal ============ function testRevokedCertInvalidatesVerification() public { - _setUpZkConfig(); + _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); @@ -574,13 +793,19 @@ contract NitroEnclaveVerifierTest is Test { // ============ Helpers ============ - function _setUpZkConfig() internal { - ZkCoProcessorConfig memory config = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockZkVerifier }); - + function _setUpRiscZeroConfig() internal { + ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ + verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier + }); verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); } + function _setUpSP1Config() internal { + ZkCoProcessorConfig memory config = + ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier }); + verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); + } + function _createSuccessJournal() internal view returns (VerifierJournal memory) { bytes32[] memory certs = new bytes32[](2); certs[0] = ROOT_CERT; @@ -604,11 +829,22 @@ contract NitroEnclaveVerifierTest is Test { function _mockRiscZeroVerify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { // IRiscZeroVerifier.verify(proofBytes, programId, sha256(output)) vm.mockCall( - mockZkVerifier, + mockRiscZeroVerifier, abi.encodeWithSelector( bytes4(keccak256("verify(bytes,bytes32,bytes32)")), proofBytes, programId, sha256(output) ), "" ); } + + function _mockSP1Verify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { + // ISP1Verifier.verifyProof(programVKey, publicValues, proofBytes) + vm.mockCall( + mockSP1Verifier, + abi.encodeWithSelector( + bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")), programId, output, proofBytes + ), + "" + ); + } } From 4d5cdaa692a9689def1d1651631cccd9dbbd0f85 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 13 Mar 2026 11:24:03 -0400 Subject: [PATCH 3/5] add INitroEnclaveVerifier --- interfaces/multiproof/IVerifier.sol | 1 - .../multiproof/tee/INitroEnclaveVerifier.sol | 410 ++++++++++++++++++ src/multiproof/tee/NitroEnclaveVerifier.sol | 72 ++- test/multiproof/NitroEnclaveVerifier.t.sol | 36 +- 4 files changed, 456 insertions(+), 63 deletions(-) create mode 100644 interfaces/multiproof/tee/INitroEnclaveVerifier.sol diff --git a/interfaces/multiproof/IVerifier.sol b/interfaces/multiproof/IVerifier.sol index e1ae29a5..4f5fbd38 100644 --- a/interfaces/multiproof/IVerifier.sol +++ b/interfaces/multiproof/IVerifier.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.15; interface IVerifier { - /// @notice Verifies a proof. /// @param proofBytes The proof. /// @param imageId The image ID. diff --git a/interfaces/multiproof/tee/INitroEnclaveVerifier.sol b/interfaces/multiproof/tee/INitroEnclaveVerifier.sol new file mode 100644 index 00000000..f9f6306d --- /dev/null +++ b/interfaces/multiproof/tee/INitroEnclaveVerifier.sol @@ -0,0 +1,410 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @dev Custom version of Automata's NitroEnclaveVerifier contract at +/// https://github.com/automata-network/aws-nitro-enclave-attestation/tree/26c90565cb009e6539643a0956f9502a12ade672 +/// +/// Differences from the upstream Automata contract: +/// - Removes verification-with-explicit-program-ID and Pico logic +/// - Errors and events moved to the implementation contract +/// - Adds new admin functions + +/** + * @dev Enumeration of supported zero-knowledge proof coprocessor types + * Used to specify which proving system to use for attestation verification + */ +enum ZkCoProcessorType { + Unknown, + // RISC Zero zkVM proving system + RiscZero, + // Succinct SP1 proving system + Succinct +} + +/** + * @dev Configuration parameters for a specific zero-knowledge coprocessor + * Contains all necessary identifiers and addresses for ZK proof verification + * + * Note: This struct stores the "latest" (active) program identifiers. + * Multiple versions can be supported simultaneously via the version management functions. + */ +struct ZkCoProcessorConfig { + // Latest program ID for single attestation verification + bytes32 verifierId; + // Latest program ID for batch/aggregated verification + bytes32 aggregatorId; + // Default ZK verifier contract address (can be overridden per route) + address zkVerifier; +} + +/** + * @dev Input structure for attestation report verification + * Contains the raw attestation data and trusted certificate chain length + */ +struct VerifierInput { + // Number of trusted certificates in the chain + uint8 trustedCertsPrefixLen; + // Raw AWS Nitro Enclave attestation report (COSE_Sign1 format) + bytes attestationReport; +} + +/** + * @dev Output structure containing verified attestation data and metadata + * This represents the journal/output from zero-knowledge proof verification + */ +struct VerifierJournal { + // Overall verification result status + VerificationResult result; + // Number of certificates that were trusted during verification + uint8 trustedCertsPrefixLen; + // Attestation timestamp (Unix timestamp in milliseconds) + uint64 timestamp; + // Array of certificate hashes in the chain (root to leaf) + bytes32[] certs; + // User-defined data embedded in the attestation + bytes userData; + // Cryptographic nonce used for replay protection + bytes nonce; + // Public key extracted from the attestation + bytes publicKey; + // Platform Configuration Registers (integrity measurements) + Pcr[] pcrs; + // AWS Nitro Enclave module identifier + string moduleId; +} + +/** + * @dev Public value (journal) structure for batch verification operations + * Contains the aggregated results of multiple attestation verifications + */ +struct BatchVerifierJournal { + // Verification key that was used for batch verification + bytes32 verifierVk; + // Array of verified attestation results + VerifierJournal[] outputs; +} + +/** + * @dev 48-byte data structure for storing PCR values + * Split into two parts due to Solidity's 32-byte word limitation + */ +struct Bytes48 { + bytes32 first; + bytes16 second; +} + +/** + * @dev Platform Configuration Register (PCR) entry + * PCRs contain cryptographic measurements of the enclave's runtime state + */ +struct Pcr { + // PCR index number (0-23 for AWS Nitro Enclaves) + uint64 index; + // 48-byte PCR measurement value (SHA-384 hash) + Bytes48 value; +} + +/** + * @dev Enumeration of possible attestation verification results + * Indicates the outcome of the verification process + */ +enum VerificationResult { + // Attestation successfully verified + Success, + // Root certificate is not in the trusted set + RootCertNotTrusted, + // One or more intermediate certificates are not trusted + IntermediateCertsNotTrusted, + // Attestation timestamp is outside acceptable range + InvalidTimestamp +} + +/** + * @title INitroEnclaveVerifier + * @dev Interface for AWS Nitro Enclave attestation verification using zero-knowledge proofs + * + * This interface defines the contract for verifying AWS Nitro Enclave attestation reports + * on-chain using zero-knowledge proof systems (RISC Zero or Succinct SP1). The verifier + * validates the cryptographic integrity of attestation reports while maintaining privacy + * and reducing gas costs through ZK proofs. + * + * Key features: + * - Single and batch attestation verification + * - Support for multiple ZK proving systems + * - Multi-version program support for seamless upgrades + * - Route-based verifier configuration + * - Certificate chain management and revocation + * - Timestamp validation with configurable tolerance + * - Platform Configuration Register (PCR) verification + */ +interface INitroEnclaveVerifier { + // ============ Query Functions ============ + + /** + * @dev Returns the maximum allowed time difference for attestation timestamp validation + * @return Maximum time difference in seconds between attestation time and current block time + */ + function maxTimeDiff() external view returns (uint64); + + /** + * @dev Returns the hash of the trusted root certificate + * @return Hash of the AWS Nitro Enclave root certificate + */ + function rootCert() external view returns (bytes32); + + /** + * @dev Retrieves the configuration for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @return ZkCoProcessorConfig Configuration parameters including program IDs and verifier address + */ + function getZkConfig(ZkCoProcessorType _zkCoProcessor) external view returns (ZkCoProcessorConfig memory); + + /** + * @dev Returns all supported verifier program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported verifier program IDs + */ + function getVerifierIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory); + + /** + * @dev Returns all supported aggregator program IDs for a coprocessor + * @param _zkCoProcessor Type of ZK coprocessor + * @return Array of all supported aggregator program IDs + */ + function getAggregatorIds(ZkCoProcessorType _zkCoProcessor) external view returns (bytes32[] memory); + + /** + * @dev Checks if a verifier program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to check + * @return True if the ID is supported + */ + function isVerifierIdSupported(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bool); + + /** + * @dev Checks if an aggregator program ID is in the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to check + * @return True if the ID is supported + */ + function isAggregatorIdSupported( + ZkCoProcessorType _zkCoProcessor, + bytes32 _aggregatorId + ) + external + view + returns (bool); + + /** + * @dev Gets the verifier address for a specific route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector + * @return Verifier address (route-specific or default fallback) + * + * Note: Reverts if the route is frozen + */ + function getZkVerifier(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external view returns (address); + + /** + * @dev Returns the verifierProofId for a given verifierId + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId The verifier program ID + * @return The corresponding verifierProofId + */ + function getVerifierProofId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external view returns (bytes32); + + /** + * @dev Checks how many certificates in each report are trusted + * @param _report_certs Array of certificate chains, each containing certificate hashes + * @return Array indicating the number of trusted certificates in each chain + * + * For each certificate chain: + * - Validates that the first certificate matches the root certificate + * - Counts consecutive trusted certificates starting from the root + * - Returns the count of trusted certificates for each chain + */ + function checkTrustedIntermediateCerts(bytes32[][] calldata _report_certs) external view returns (uint8[] memory); + + // ============ Admin Functions ============ + + /** + * @dev Sets the trusted root certificate hash + * @param _rootCert Hash of the new root certificate + * + * Requirements: + * - Only callable by contract owner + */ + function setRootCert(bytes32 _rootCert) external; + + /** + * @dev Updates the maximum allowed time difference for attestation timestamp validation + * @param _maxTimeDiff New maximum time difference in seconds + * + * Requirements: + * - Only callable by contract owner + * - Must be greater than zero + */ + function setMaxTimeDiff(uint64 _maxTimeDiff) external; + + /** + * @dev Sets the proof submitter address + * @param _proofSubmitter The address of the proof submitter + * + * Requirements: + * - Only callable by contract owner + * - Address must not be zero + */ + function setProofSubmitter(address _proofSubmitter) external; + + /** + * @dev Configures the zero-knowledge verification parameters for a specific coprocessor + * @param _zkCoProcessor Type of ZK coprocessor (RiscZero or Succinct) + * @param _config Configuration parameters including program IDs and verifier address + * @param _verifierProofId The verifierProofId corresponding to the verifierId in config + * + * Requirements: + * - Only callable by contract owner + * - Must specify valid coprocessor type and configuration + */ + function setZkConfiguration( + ZkCoProcessorType _zkCoProcessor, + ZkCoProcessorConfig memory _config, + bytes32 _verifierProofId + ) + external; + + /** + * @dev Revokes a trusted intermediate certificate + * @param _certHash Hash of the certificate to revoke + * + * Requirements: + * - Only callable by contract owner + * - Certificate must exist in the trusted set + */ + function revokeCert(bytes32 _certHash) external; + + /** + * @dev Updates the verifier program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newVerifierId New verifier program ID to set as latest + * @param _newVerifierProofId New verifier proof ID (used in batch verification) + * + * Requirements: + * - Only callable by contract owner + * - New ID must be different from current latest + */ + function updateVerifierId( + ZkCoProcessorType _zkCoProcessor, + bytes32 _newVerifierId, + bytes32 _newVerifierProofId + ) + external; + + /** + * @dev Updates the aggregator program ID, adding the new version to the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _newAggregatorId New aggregator program ID to set as latest + * + * Requirements: + * - Only callable by contract owner + * - New ID must be different from current latest + */ + function updateAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _newAggregatorId) external; + + /** + * @dev Removes a verifier program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _verifierId Verifier program ID to remove + * + * Requirements: + * - Only callable by contract owner + * - Cannot remove the currently active (latest) verifier ID + * - ID must exist in the supported set + */ + function removeVerifierId(ZkCoProcessorType _zkCoProcessor, bytes32 _verifierId) external; + + /** + * @dev Removes an aggregator program ID from the supported set + * @param _zkCoProcessor Type of ZK coprocessor + * @param _aggregatorId Aggregator program ID to remove + * + * Requirements: + * - Only callable by contract owner + * - Cannot remove the currently active (latest) aggregator ID + * - ID must exist in the supported set + */ + function removeAggregatorId(ZkCoProcessorType _zkCoProcessor, bytes32 _aggregatorId) external; + + /** + * @dev Adds a route-specific verifier override + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector (first 4 bytes of proof data) + * @param _verifier Address of the verifier contract for this route + * + * Requirements: + * - Only callable by contract owner + * - Route must not be frozen + * - Verifier address must not be zero + */ + function addVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector, address _verifier) external; + + /** + * @dev Permanently freezes a verification route + * @param _zkCoProcessor Type of ZK coprocessor + * @param _selector Proof selector to freeze + * + * Requirements: + * - Only callable by contract owner + * - Route must not already be frozen + * + * WARNING: This action is IRREVERSIBLE + */ + function freezeVerifyRoute(ZkCoProcessorType _zkCoProcessor, bytes4 _selector) external; + + // ============ Verification Functions ============ + + /** + * @dev Verifies a single attestation report using zero-knowledge proof + * @param output Encoded VerifierJournal containing the verification result + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for the attestation + * @return VerifierJournal containing the verification result and extracted data + * + * This function: + * 1. Verifies the ZK proof using the specified coprocessor + * 2. Decodes the verification result + * 3. Validates the certificate chain against trusted certificates + * 4. Checks timestamp validity within the allowed time difference + * 5. Caches newly discovered trusted certificates + * 6. Returns the complete verification result + */ + function verify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal memory); + + /** + * @dev Verifies multiple attestation reports in a single batch operation + * @param output Encoded BatchVerifierJournal containing aggregated verification results + * @param zkCoprocessor Type of ZK coprocessor used to generate the proof + * @param proofBytes Zero-knowledge proof data for batch verification + * @return Array of VerifierJournal results, one for each attestation in the batch + * + * This function: + * 1. Verifies the ZK proof using the specified coprocessor + * 2. Decodes the batch verification results + * 3. Validates each attestation's certificate chain and timestamp + * 4. Caches newly discovered trusted certificates + * 5. Returns the verification results for all attestations + */ + function batchVerify( + bytes calldata output, + ZkCoProcessorType zkCoprocessor, + bytes calldata proofBytes + ) + external + returns (VerifierJournal[] memory); +} diff --git a/src/multiproof/tee/NitroEnclaveVerifier.sol b/src/multiproof/tee/NitroEnclaveVerifier.sol index 8f2b6cde..e8a27364 100644 --- a/src/multiproof/tee/NitroEnclaveVerifier.sol +++ b/src/multiproof/tee/NitroEnclaveVerifier.sol @@ -10,7 +10,7 @@ import { VerifierJournal, BatchVerifierJournal, VerificationResult -} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; +} from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; import { IRiscZeroVerifier } from "lib/aws-nitro-enclave-attestation/contracts/lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; @@ -81,6 +81,18 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { // ============ Custom Errors ============ + /// @dev Error thrown when an unsupported or unknown ZK coprocessor type is used + error Unknown_Zk_Coprocessor(); + + /// @dev Error thrown when attempting to remove the currently active (latest) program ID + error CannotRemoveLatestProgramId(ZkCoProcessorType zkCoProcessor, bytes32 identifier); + + /// @dev Error thrown when a ZK route has been permanently frozen + error ZkRouteFrozen(ZkCoProcessorType zkCoProcessor, bytes4 selector); + + /// @dev Error thrown when no ZK verifier is configured for the coprocessor + error ZkVerifierNotConfigured(ZkCoProcessorType zkCoProcessor); + /// @dev Thrown when a caller other than the authorized proof submitter calls verify or batchVerify error CallerNotProofSubmitter(); @@ -111,11 +123,32 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { /// @dev Thrown when calling verifyWithProgramId or batchVerifyWithProgramId, which are intentionally disabled error NotImplemented(); - /// @dev Thrown when a zero or zero-equivalent maxTimeDiff is provided + /// @dev Error thrown when a zero maxTimeDiff is provided error ZeroMaxTimeDiff(); // ============ Events ============ + /// @dev Emitted when a new verifier program ID is added/updated + event VerifierIdUpdated(ZkCoProcessorType indexed zkCoProcessor, bytes32 indexed newId, bytes32 newProofId); + + /// @dev Emitted when a new aggregator program ID is added/updated + event AggregatorIdUpdated(ZkCoProcessorType indexed zkCoProcessor, bytes32 indexed newId); + + /// @dev Emitted when a program ID is removed from the supported set + event ProgramIdRemoved(ZkCoProcessorType indexed zkCoProcessor, bytes32 indexed programId, bool isAggregator); + + /// @dev Emitted when a route-specific verifier is added + event ZkRouteAdded(ZkCoProcessorType indexed zkCoProcessor, bytes4 indexed selector, address verifier); + + /// @dev Emitted when a route is permanently frozen + event ZkRouteWasFrozen(ZkCoProcessorType indexed zkCoProcessor, bytes4 indexed selector); + + /// @dev Emitted when the proof of attestation has been successfully verified + event AttestationSubmitted(VerificationResult result, ZkCoProcessorType zkCoProcessor, bytes output); + + /// @dev Emitted when a batched proof has been successfully verified; encodedBatched = abi.encode(VerifierJournal[]) + event BatchAttestationSubmitted(bytes32 verifierId, ZkCoProcessorType zkCoProcessor, bytes encodedBatch); + /// @dev Event emitted when the proof submitter address is changed event ProofSubmitterChanged(address newProofSubmitter); @@ -569,41 +602,6 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier { emit BatchAttestationSubmitted(verifierId, zkCoprocessor, abi.encode(results)); } - /** - * @notice Not implemented — explicit program ID verification is intentionally disabled. - * @dev This function exists solely to satisfy the INitroEnclaveVerifier interface. - */ - function verifyWithProgramId( - bytes calldata, - ZkCoProcessorType, - bytes32, - bytes calldata - ) - external - pure - returns (VerifierJournal memory) - { - revert NotImplemented(); - } - - /** - * @notice Not implemented — explicit program ID verification is intentionally disabled. - * @dev This function exists solely to satisfy the INitroEnclaveVerifier interface. - */ - function batchVerifyWithProgramId( - bytes calldata, - ZkCoProcessorType, - bytes32, - bytes32, - bytes calldata - ) - external - pure - returns (VerifierJournal[] memory) - { - revert NotImplemented(); - } - // ============ Internal Functions ============ /** diff --git a/test/multiproof/NitroEnclaveVerifier.t.sol b/test/multiproof/NitroEnclaveVerifier.t.sol index 50a8281a..1b20467b 100644 --- a/test/multiproof/NitroEnclaveVerifier.t.sol +++ b/test/multiproof/NitroEnclaveVerifier.t.sol @@ -4,15 +4,13 @@ pragma solidity ^0.8.20; import { Test } from "forge-std/Test.sol"; import { - INitroEnclaveVerifier, ZkCoProcessorType, ZkCoProcessorConfig, VerifierJournal, BatchVerifierJournal, VerificationResult, - Pcr, - Bytes48 -} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol"; + Pcr +} from "interfaces/multiproof/tee/INitroEnclaveVerifier.sol"; import { NitroEnclaveVerifier } from "src/multiproof/tee/NitroEnclaveVerifier.sol"; @@ -268,7 +266,7 @@ contract NitroEnclaveVerifierTest is Test { vm.expectRevert( abi.encodeWithSelector( - INitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, VERIFIER_ID + NitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, VERIFIER_ID ) ); verifier.removeVerifierId(ZkCoProcessorType.RiscZero, VERIFIER_ID); @@ -310,7 +308,7 @@ contract NitroEnclaveVerifierTest is Test { vm.expectRevert( abi.encodeWithSelector( - INitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, AGGREGATOR_ID + NitroEnclaveVerifier.CannotRemoveLatestProgramId.selector, ZkCoProcessorType.RiscZero, AGGREGATOR_ID ) ); verifier.removeAggregatorId(ZkCoProcessorType.RiscZero, AGGREGATOR_ID); @@ -363,7 +361,7 @@ contract NitroEnclaveVerifierTest is Test { verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); vm.expectRevert( - abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) ); verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector); } @@ -376,7 +374,7 @@ contract NitroEnclaveVerifierTest is Test { verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); vm.expectRevert( - abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) ); verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); } @@ -387,7 +385,7 @@ contract NitroEnclaveVerifierTest is Test { verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); vm.expectRevert( - abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) ); verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); } @@ -464,7 +462,7 @@ contract NitroEnclaveVerifierTest is Test { vm.prank(submitter); vm.expectRevert( - abi.encodeWithSelector(INitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.RiscZero) + abi.encodeWithSelector(NitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.RiscZero) ); verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); } @@ -483,7 +481,7 @@ contract NitroEnclaveVerifierTest is Test { bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); vm.prank(submitter); - vm.expectRevert(INitroEnclaveVerifier.Unknown_Zk_Coprocessor.selector); + vm.expectRevert(NitroEnclaveVerifier.Unknown_Zk_Coprocessor.selector); verifier.verify(output, ZkCoProcessorType.Unknown, proofBytes); } @@ -502,7 +500,7 @@ contract NitroEnclaveVerifierTest is Test { vm.prank(submitter); vm.expectRevert( - abi.encodeWithSelector(INitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) ); verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); } @@ -680,7 +678,7 @@ contract NitroEnclaveVerifierTest is Test { vm.prank(submitter); vm.expectRevert( - abi.encodeWithSelector(INitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.Succinct) + abi.encodeWithSelector(NitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.Succinct) ); verifier.verify(abi.encode(_createSuccessJournal()), ZkCoProcessorType.Succinct, proofBytes); } @@ -759,18 +757,6 @@ contract NitroEnclaveVerifierTest is Test { assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); } - // ============ verifyWithProgramId / batchVerifyWithProgramId Tests ============ - - function testVerifyWithProgramIdReverts() public { - vm.expectRevert(NitroEnclaveVerifier.NotImplemented.selector); - verifier.verifyWithProgramId("", ZkCoProcessorType.RiscZero, bytes32(0), ""); - } - - function testBatchVerifyWithProgramIdReverts() public { - vm.expectRevert(NitroEnclaveVerifier.NotImplemented.selector); - verifier.batchVerifyWithProgramId("", ZkCoProcessorType.RiscZero, bytes32(0), bytes32(0), ""); - } - // ============ Revoked Cert Invalidates Journal ============ function testRevokedCertInvalidatesVerification() public { From 4dcc201f7f058634ab4582ab723978edb007e687 Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 13 Mar 2026 16:12:41 -0400 Subject: [PATCH 4/5] update license --- interfaces/multiproof/tee/INitroEnclaveVerifier.sol | 2 +- src/multiproof/tee/NitroEnclaveVerifier.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interfaces/multiproof/tee/INitroEnclaveVerifier.sol b/interfaces/multiproof/tee/INitroEnclaveVerifier.sol index f9f6306d..452e3db7 100644 --- a/interfaces/multiproof/tee/INitroEnclaveVerifier.sol +++ b/interfaces/multiproof/tee/INitroEnclaveVerifier.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: MIT +//SPDX-License-Identifier: Apache2.0 pragma solidity ^0.8.0; /// @dev Custom version of Automata's NitroEnclaveVerifier contract at diff --git a/src/multiproof/tee/NitroEnclaveVerifier.sol b/src/multiproof/tee/NitroEnclaveVerifier.sol index e8a27364..e6c758b6 100644 --- a/src/multiproof/tee/NitroEnclaveVerifier.sol +++ b/src/multiproof/tee/NitroEnclaveVerifier.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache2.0 pragma solidity ^0.8.0; import { Ownable } from "@solady/auth/Ownable.sol"; From 178cbe5218c3e50f9f2e8257e32b8f43ffc8186d Mon Sep 17 00:00:00 2001 From: Roger Bai Date: Fri, 13 Mar 2026 17:02:11 -0400 Subject: [PATCH 5/5] add license to multiproof contracts --- snapshots/semver-lock.json | 4 ++-- src/multiproof/AggregateVerifier.sol | 2 +- test/multiproof/AggregateVerifier.t.sol | 2 +- test/multiproof/Challenge.t.sol | 2 +- test/multiproof/NitroEnclaveVerifier.t.sol | 2 +- test/multiproof/Nullify.t.sol | 2 +- test/multiproof/TEEProverRegistry.t.sol | 2 +- test/multiproof/TEEVerifier.t.sol | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 7316eb4b..526cec3c 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -241,11 +241,11 @@ }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { "initCodeHash": "0xc3866b1d4515c9d7b0ac6679b182d836f79371402d9e649e301b24cf8ae8fade", - "sourceCodeHash": "0x3a079ea52a26c8c38fb0cb3e9a9ff6ec9648cf83786b65c0fc1161e949b8f7e0" + "sourceCodeHash": "0x98267b52a71222506c4893d6632dc5a36dd197a8d60de9a1f6578a80f7ebdf2d" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { "initCodeHash": "0xe28eaeecda21594f6db23bb70127daa2b7b71debe38ce65b598f28d78d2561eb", - "sourceCodeHash": "0x3a079ea52a26c8c38fb0cb3e9a9ff6ec9648cf83786b65c0fc1161e949b8f7e0" + "sourceCodeHash": "0x98267b52a71222506c4893d6632dc5a36dd197a8d60de9a1f6578a80f7ebdf2d" }, "src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": { "initCodeHash": "0xee219c003a6af440b447e214e43d520e802001ae3d557262a7921ca3d57ebddf", diff --git a/src/multiproof/AggregateVerifier.sol b/src/multiproof/AggregateVerifier.sol index 72499a1c..43bac056 100644 --- a/src/multiproof/AggregateVerifier.sol +++ b/src/multiproof/AggregateVerifier.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; // Optimism diff --git a/test/multiproof/AggregateVerifier.t.sol b/test/multiproof/AggregateVerifier.t.sol index a60f081e..0679267e 100644 --- a/test/multiproof/AggregateVerifier.t.sol +++ b/test/multiproof/AggregateVerifier.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { BadExtraData, GameNotResolved } from "src/dispute/lib/Errors.sol"; diff --git a/test/multiproof/Challenge.t.sol b/test/multiproof/Challenge.t.sol index aefe5a4d..661b68f1 100644 --- a/test/multiproof/Challenge.t.sol +++ b/test/multiproof/Challenge.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/dispute/lib/Errors.sol"; diff --git a/test/multiproof/NitroEnclaveVerifier.t.sol b/test/multiproof/NitroEnclaveVerifier.t.sol index 1b20467b..604e555b 100644 --- a/test/multiproof/NitroEnclaveVerifier.t.sol +++ b/test/multiproof/NitroEnclaveVerifier.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import { Test } from "forge-std/Test.sol"; diff --git a/test/multiproof/Nullify.t.sol b/test/multiproof/Nullify.t.sol index 4674fc38..18ff74f3 100644 --- a/test/multiproof/Nullify.t.sol +++ b/test/multiproof/Nullify.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/dispute/lib/Errors.sol"; diff --git a/test/multiproof/TEEProverRegistry.t.sol b/test/multiproof/TEEProverRegistry.t.sol index 5b388f5f..c5923a19 100644 --- a/test/multiproof/TEEProverRegistry.t.sol +++ b/test/multiproof/TEEProverRegistry.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; diff --git a/test/multiproof/TEEVerifier.t.sol b/test/multiproof/TEEVerifier.t.sol index 87d6d266..ddcabf0c 100644 --- a/test/multiproof/TEEVerifier.t.sol +++ b/test/multiproof/TEEVerifier.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol";