diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index f241f702..493dc9a5 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -240,11 +240,11 @@ "sourceCodeHash": "0x955bd0c9b47e43219865e4e92abf28d916c96de20cbdf2f94c8ab14d02083759" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xc3866b1d4515c9d7b0ac6679b182d836f79371402d9e649e301b24cf8ae8fade", + "initCodeHash": "0xe28eaeecda21594f6db23bb70127daa2b7b71debe38ce65b598f28d78d2561eb", "sourceCodeHash": "0x3a079ea52a26c8c38fb0cb3e9a9ff6ec9648cf83786b65c0fc1161e949b8f7e0" }, "src/multiproof/AggregateVerifier.sol:AggregateVerifier:dispute": { - "initCodeHash": "0xe28eaeecda21594f6db23bb70127daa2b7b71debe38ce65b598f28d78d2561eb", + "initCodeHash": "0xc59c62a455533735aa9337d2db88b255713bd4dde20d3345265ec2fafe62af70", "sourceCodeHash": "0x3a079ea52a26c8c38fb0cb3e9a9ff6ec9648cf83786b65c0fc1161e949b8f7e0" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": { @@ -252,7 +252,7 @@ "sourceCodeHash": "0xe350108585e0855f10bac20d0e8894b3de9afe3be413908b4c59a1036e7a9842" }, "src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": { - "initCodeHash": "0x8ae045f0121d2c63ab6f0a830be842aaf0445096bfabe29d85cfd9bd38b40565", + "initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d", "sourceCodeHash": "0xe350108585e0855f10bac20d0e8894b3de9afe3be413908b4c59a1036e7a9842" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier": { @@ -260,7 +260,7 @@ "sourceCodeHash": "0x87b0ad3f68294d64b584e54cc719cc3be0624cbab2940a0a341c502dcc69b4fc" }, "src/multiproof/tee/TEEVerifier.sol:TEEVerifier:dispute": { - "initCodeHash": "0x090330a5c64206b523fd5d9e199269268131035c62ff9eb518247a9024c4b703", + "initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a", "sourceCodeHash": "0x87b0ad3f68294d64b584e54cc719cc3be0624cbab2940a0a341c502dcc69b4fc" }, "src/revenue-share/FeeDisburser.sol:FeeDisburser": { 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) + ), + "" + ); + } +}