diff --git a/contracts/solidity/TEEInferenceVerifier.sol b/contracts/solidity/TEEInferenceVerifier.sol index 78286a2b..6692dd92 100644 --- a/contracts/solidity/TEEInferenceVerifier.sol +++ b/contracts/solidity/TEEInferenceVerifier.sol @@ -6,7 +6,25 @@ import "./precompiles/tee/ITEEVerifier.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; /// @title TEEInferenceVerifier - Verification for TEE-signed inference outputs -/// @notice Reads TEE public keys from TEERegistry and verifies RSA-PSS signatures with timestamp validation. +/// @notice Stateless verifier that confirms an AI inference output was produced by a +/// registered, active TEE within an acceptable time window. +/// +/// @dev ## How It Works +/// +/// When a TEE runs an inference, it signs `keccak256(inputHash || outputHash || timestamp)` +/// with its RSA-PSS private key. Any party (settlement relay, on-chain consumer, etc.) can +/// then call `verifySignature` to confirm authenticity. The check is three-fold: +/// +/// 1. **TEE status** — the TEE must be active in the `TEERegistry`. +/// 2. **Timestamp bounds** — the signed timestamp must be within +/// `[block.timestamp - MAX_INFERENCE_AGE, block.timestamp + FUTURE_TOLERANCE]` +/// to prevent replay of stale results and reject clock-skewed signatures. +/// 3. **Cryptographic proof** — the RSA-PSS signature is verified against the TEE's +/// on-chain public key via the 0x900 precompile. +/// +/// The contract is intentionally read-only (no state mutations in `verifySignature`) so +/// it can be called from view contexts and composed freely by downstream contracts like +/// `InferenceSettlementRelay`. contract TEEInferenceVerifier is AccessControl { // ============ Constants ============ diff --git a/contracts/solidity/TEERegistry.json b/contracts/solidity/TEERegistry.json index 985649ca..ca2e525b 100644 --- a/contracts/solidity/TEERegistry.json +++ b/contracts/solidity/TEERegistry.json @@ -61,6 +61,11 @@ "name": "NotTEEOwner", "type": "error" }, + { + "inputs": [], + "name": "PCRAlreadyExists", + "type": "error" + }, { "inputs": [], "name": "PCRExpired", @@ -71,6 +76,11 @@ "name": "PCRNotApproved", "type": "error" }, + { + "inputs": [], + "name": "PCRTypeMismatch", + "type": "error" + }, { "inputs": [], "name": "TEEAlreadyExists", @@ -118,6 +128,12 @@ "name": "pcrHash", "type": "bytes32" }, + { + "indexed": true, + "internalType": "uint8", + "name": "teeType", + "type": "uint8" + }, { "indexed": false, "internalType": "string", @@ -136,6 +152,12 @@ "internalType": "bytes32", "name": "pcrHash", "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "gracePeriod", + "type": "uint256" } ], "name": "PCRRevoked", @@ -399,14 +421,9 @@ "type": "string" }, { - "internalType": "bytes32", - "name": "previousPcrHash", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "gracePeriod", - "type": "uint256" + "internalType": "uint8", + "name": "teeType", + "type": "uint8" } ], "name": "approvePCR", @@ -429,6 +446,11 @@ "name": "active", "type": "bool" }, + { + "internalType": "uint8", + "name": "teeType", + "type": "uint8" + }, { "internalType": "uint256", "name": "approvedAt", @@ -984,6 +1006,11 @@ "internalType": "bytes32", "name": "pcrHash", "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "gracePeriod", + "type": "uint256" } ], "name": "revokePCR", diff --git a/contracts/solidity/TEERegistry.sol b/contracts/solidity/TEERegistry.sol index 3f2d5dfb..4af90f05 100644 --- a/contracts/solidity/TEERegistry.sol +++ b/contracts/solidity/TEERegistry.sol @@ -5,8 +5,78 @@ import "./precompiles/tee/ITEEVerifier.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; /// @title TEERegistry - TEE Registration and Management -/// @notice Manages TEE lifecycle, calls precompile only for crypto -/// @dev All storage in Solidity, crypto in precompile at 0x900 +/// @notice On-chain registry for Trusted Execution Environment (TEE) nodes that provide +/// verifiable AI inference. Manages the full TEE lifecycle: registration, activation, +/// heartbeat liveness, and decommissioning. +/// +/// @dev ## Overall Flow +/// +/// 1. **Admin setup** — An admin adds TEE types (e.g. LLM inference, agent execution) via `addTEEType`, then +/// approves known-good enclave measurements via `approvePCR`. The AWS root certificate +/// used for attestation verification is stored via `setAWSRootCertificate`. +/// +/// 2. **Registration** — A TEE operator calls `registerTEEWithAttestation`, which: +/// a. Verifies the attestation document against the AWS root cert via the 0x900 +/// precompile (`ITEEVerifier`). +/// b. Extracts PCR measurements and checks they match an admin-approved set. +/// c. Stores the TEE as **active** and indexes it by type and owner. +/// +/// 3. **Heartbeat** — Each TEE periodically proves liveness by submitting a signed +/// timestamp via `heartbeat`. The RSA-PSS signature is verified on-chain against the +/// TEE's stored public key. Stale or future timestamps are rejected. +/// +/// 4. **Deactivation / Reactivation** — TEE owners or admins can toggle a TEE's active +/// status. `activateTEE` re-validates the TEE's PCR before reactivating. +/// +/// 5. **PCR revocation** — When an enclave image is compromised or outdated, admins revoke +/// its PCR via `revokePCR` (optionally with a grace period). TEEs running the revoked +/// image are caught lazily: `activateTEE` and `heartbeat` both call +/// `_requirePCRValidForTEE` and will revert once the PCR expires. +/// +/// 6. **Removal** — `removeTEE` permanently deletes a TEE from all storage and indexes. +/// +/// ## Querying TEE Status +/// +/// The contract exposes three tiers of TEE queries with increasing strictness: +/// - `getTEEsByType` — all TEEs ever registered for a type (active + inactive). +/// - `getActivatedTEEs` — only TEEs in the active list (no heartbeat/PCR check). +/// - `getLiveTEEs` — active TEEs with a valid PCR **and** a fresh heartbeat. +/// +/// ## Client Integration Guide +/// +/// **Choosing a query method:** +/// +/// - `getLiveTEEs(teeType)` — **Recommended for most clients.** Returns only TEEs +/// that are active, running approved (non-revoked) enclave code, and have sent a +/// recent heartbeat. These are fully verified and ready to serve requests. +/// +/// - `getActivatedTEEs(teeType)` — Returns TEEs that are in the active list but +/// does **not** check heartbeat freshness or PCR validity. Use this if you want +/// to perform your own filtering logic off-chain (e.g. custom staleness +/// thresholds, geographic selection, or load-balancing across TEEs that may have +/// briefly missed a heartbeat). You are responsible for checking liveness and +/// PCR status yourself. +/// +/// - `getTEEsByType(teeType)` — Returns all TEEs ever registered for a type, +/// including inactive ones. Useful for dashboards, auditing, or historical views. +/// Not suitable for selecting a TEE to connect to. +/// +/// **TLS certificate verification:** +/// +/// When connecting to a TEE, clients **must** verify that the TLS certificate +/// presented by the TEE's endpoint matches the `tlsCertificate` stored on-chain. +/// This certificate was bound to the enclave at registration time via attestation +/// verification. Without this check, a compromised or spoofed endpoint could +/// impersonate a registered TEE. The recommended flow is: +/// 1. Query the registry for a live TEE (e.g. via `getLiveTEEs`). +/// 2. Open a TLS connection to the TEE's `endpoint`. +/// 3. Compare the server's presented certificate against `TEEInfo.tlsCertificate`. +/// 4. Abort the connection if they do not match. +/// +/// ## Access Control +/// +/// - `DEFAULT_ADMIN_ROLE` — manages TEE types, PCRs, certificates, heartbeat config. +/// - `TEE_OPERATOR` — registers TEEs, manages owned TEEs (deactivate/activate/remove). contract TEERegistry is AccessControl { // ============ Constants ============ @@ -24,6 +94,7 @@ contract TEERegistry is AccessControl { struct ApprovedPCR { bool active; + uint8 teeType; uint256 approvedAt; uint256 expiresAt; string version; @@ -54,9 +125,14 @@ contract TEERegistry is AccessControl { mapping(uint8 => TEETypeInfo) public teeTypes; uint8[] private _teeTypeList; - // PCR Registry - mapping(bytes32 => ApprovedPCR) public approvedPCRs; - bytes32[] private _pcrList; + // PCR Registry: teeType => pcrHash => ApprovedPCR + mapping(uint8 => mapping(bytes32 => ApprovedPCR)) public approvedPCRs; + + struct PCRKey { + bytes32 pcrHash; + uint8 teeType; + } + PCRKey[] private _pcrList; // AWS Root Certificate bytes public awsRootCertificate; @@ -64,24 +140,32 @@ contract TEERegistry is AccessControl { // Heartbeat: max allowed age of the signed timestamp vs block.timestamp. uint256 public heartbeatMaxAge = 1800; // 30 minutes default - // TEE Storage + // All TEEs mapping(bytes32 => TEEInfo) public tees; - bytes32[] private _activeTEEList; - mapping(bytes32 => uint256) private _activeTEEIndex; - mapping(address => bytes32[]) private _teesByOwner; - mapping(uint8 => bytes32[]) private _teesByType; + + // Active TEEs by type: teeType => list of active teeIds + mapping(uint8 => bytes32[]) private _activeTEEList; + // teeType => teeId => index in _activeTEEList[teeType] + mapping(uint8 => mapping(bytes32 => uint256)) private _activeTEEIndex; + + // All TEEs by type (active + inactive) + mapping(uint8 => bytes32[]) internal _teesByType; + + // TEEs by owner + mapping(address => bytes32[]) internal _teesByOwner; // ============ Events ============ event TEETypeAdded(uint8 indexed typeId, string name); event TEETypeDeactivated(uint8 indexed typeId); - event PCRApproved(bytes32 indexed pcrHash, string version); - event PCRRevoked(bytes32 indexed pcrHash); + event PCRApproved(bytes32 indexed pcrHash, uint8 indexed teeType, string version); + event PCRRevoked(bytes32 indexed pcrHash, uint256 gracePeriod); event TEERegistered(bytes32 indexed teeId, address indexed owner, uint8 teeType); event TEEDeactivated(bytes32 indexed teeId); event TEEActivated(bytes32 indexed teeId); event AWSCertificateUpdated(bytes32 indexed certHash); event HeartbeatReceived(bytes32 indexed teeId, uint256 timestamp); + event TEERemoved(bytes32 indexed teeId); // ============ Errors ============ @@ -90,6 +174,7 @@ contract TEERegistry is AccessControl { error InvalidTEEType(); error PCRNotApproved(); error PCRExpired(); + error PCRAlreadyExists(); error TEEAlreadyExists(); error TEENotFound(); error TEENotActive(); @@ -100,6 +185,15 @@ contract TEERegistry is AccessControl { error HeartbeatTimestampTooOld(); error HeartbeatTimestampInFuture(); + // ============ Modifiers ============ + + modifier onlyTEEOwnerOrAdmin(bytes32 teeId) { + if (tees[teeId].registeredAt == 0) revert TEENotFound(); + if (tees[teeId].owner != msg.sender && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert NotTEEOwner(); + if (!hasRole(TEE_OPERATOR, msg.sender) && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert NotTEEOwner(); + _; + } + // ============ Constructor ============ constructor() { @@ -141,54 +235,87 @@ contract TEERegistry is AccessControl { // ============ PCR Management ============ + /// @notice Approve a new PCR measurement for a specific TEE type + /// @param pcrs The PCR measurements (pcr0, pcr1, pcr2) + /// @param version Human-readable version string (e.g., "v1.2.0") + /// @param teeType The TEE type this PCR is valid for function approvePCR( PCRMeasurements calldata pcrs, string calldata version, - bytes32 previousPcrHash, - uint256 gracePeriod + uint8 teeType ) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (!isValidTEEType(teeType)) revert InvalidTEEType(); + bytes32 pcrHash = computePCRHash(pcrs); - - // Set expiry on previous PCR if provided - if (previousPcrHash != bytes32(0) && approvedPCRs[previousPcrHash].active) { - approvedPCRs[previousPcrHash].expiresAt = block.timestamp + gracePeriod; - } + bool isNew = approvedPCRs[teeType][pcrHash].approvedAt == 0; - approvedPCRs[pcrHash] = ApprovedPCR({ + approvedPCRs[teeType][pcrHash] = ApprovedPCR({ active: true, + teeType: teeType, approvedAt: block.timestamp, expiresAt: 0, version: version }); - _pcrList.push(pcrHash); - emit PCRApproved(pcrHash, version); + + if (isNew) { + _pcrList.push(PCRKey({pcrHash: pcrHash, teeType: teeType})); + } + + emit PCRApproved(pcrHash, teeType, version); } - function revokePCR(bytes32 pcrHash) external onlyRole(DEFAULT_ADMIN_ROLE) { - approvedPCRs[pcrHash].active = false; - emit PCRRevoked(pcrHash); + /// @notice Revoke a PCR, either immediately or with a grace period + /// @dev TEEs using this PCR are caught lazily at activateTEE() and heartbeat() + /// @param pcrHash The PCR hash to revoke + /// @param gracePeriod Seconds until revocation takes effect (0 = immediate) + function revokePCR(bytes32 pcrHash, uint8 teeType, uint256 gracePeriod) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (!isPCRApproved(teeType, pcrHash)) revert PCRNotApproved(); + + if (gracePeriod == 0) { + approvedPCRs[teeType][pcrHash].active = false; + } else { + approvedPCRs[teeType][pcrHash].expiresAt = block.timestamp + gracePeriod; + } + emit PCRRevoked(pcrHash, gracePeriod); } - function isPCRApproved(bytes32 pcrHash) public view returns (bool) { - ApprovedPCR storage pcr = approvedPCRs[pcrHash]; + /// @notice Check if a PCR is currently approved and not expired + /// @param teeType The TEE type the PCR is valid for + /// @param pcrHash The PCR hash to check + /// @return bool True if approved and not expired + function isPCRApproved(uint8 teeType, bytes32 pcrHash) public view returns (bool) { + ApprovedPCR storage pcr = approvedPCRs[teeType][pcrHash]; if (!pcr.active) return false; if (pcr.expiresAt != 0 && block.timestamp >= pcr.expiresAt) return false; return true; } + /// @dev Reverts if PCR is not approved for the given TEE type + function _requirePCRValidForTEE(bytes32 pcrHash, uint8 teeType) private view { + ApprovedPCR storage pcr = approvedPCRs[teeType][pcrHash]; + if (!pcr.active) revert PCRNotApproved(); + if (pcr.expiresAt != 0 && block.timestamp >= pcr.expiresAt) revert PCRExpired(); + } + + /// @notice Compute PCR hash from measurements + /// @param pcrs The PCR measurements + /// @return bytes32 Hash of the concatenated PCRs function computePCRHash(PCRMeasurements calldata pcrs) public pure returns (bytes32) { return keccak256(abi.encodePacked(pcrs.pcr0, pcrs.pcr1, pcrs.pcr2)); } - function getActivePCRs() external view returns (bytes32[] memory) { + /// @notice Get all currently active (approved and not expired) PCRs + /// @return PCRKey[] Array of active PCR keys (pcrHash + teeType) + function getActivePCRs() external view returns (PCRKey[] memory) { uint256 count = 0; for (uint256 i = 0; i < _pcrList.length; i++) { - if (isPCRApproved(_pcrList[i])) count++; + if (isPCRApproved(_pcrList[i].teeType, _pcrList[i].pcrHash)) count++; } - bytes32[] memory result = new bytes32[](count); + + PCRKey[] memory result = new PCRKey[](count); uint256 j = 0; for (uint256 i = 0; i < _pcrList.length; i++) { - if (isPCRApproved(_pcrList[i])) { + if (isPCRApproved(_pcrList[i].teeType, _pcrList[i].pcrHash)) { result[j++] = _pcrList[i]; } } @@ -202,7 +329,7 @@ contract TEERegistry is AccessControl { emit AWSCertificateUpdated(keccak256(certificate)); } - // ============ TEE Registration ============ + // ============ TEE Management ============ function registerTEEWithAttestation( bytes calldata attestationDocument, @@ -226,10 +353,10 @@ contract TEERegistry is AccessControl { tlsCertificate, awsRootCertificate ); - if (!valid) revert AttestationInvalid("Attestation verification failed"); + if (!valid) revert AttestationInvalid("Attestation document verification failed"); - // Verify PCR is approved - if (!isPCRApproved(pcrHash)) revert PCRNotApproved(); + // Verify PCR is approved and matches the TEE type + _requirePCRValidForTEE(pcrHash, teeType); // Store TEE tees[teeId] = TEEInfo({ @@ -246,57 +373,98 @@ contract TEERegistry is AccessControl { }); // Add to indexes - _activeTEEIndex[teeId] = _activeTEEList.length; - _activeTEEList.push(teeId); - _teesByOwner[msg.sender].push(teeId); + _activeTEEIndex[teeType][teeId] = _activeTEEList[teeType].length; + _activeTEEList[teeType].push(teeId); _teesByType[teeType].push(teeId); + _teesByOwner[msg.sender].push(teeId); emit TEERegistered(teeId, msg.sender, teeType); } - // ============ TEE Management ============ - - function deactivateTEE(bytes32 teeId) external { + /// @notice Deactivate a TEE, removing it from the active list + /// @dev Requires caller to be the TEE owner with TEE_OPERATOR role, or an admin + /// @param teeId The TEE identifier to deactivate + function deactivateTEE(bytes32 teeId) external onlyTEEOwnerOrAdmin(teeId) { TEEInfo storage tee = tees[teeId]; - if (tee.registeredAt == 0) revert TEENotFound(); - if (tee.owner != msg.sender && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert NotTEEOwner(); - if (!tee.active) return; // Already deactivated, nothing to do + if (!tee.active) return; tee.active = false; - tee.lastUpdatedAt = block.timestamp; - _removeFromActiveList(teeId); + _removeFromActiveList(teeId, tee.teeType); emit TEEDeactivated(teeId); } - function activateTEE(bytes32 teeId) external { + /// @notice Re-activate a previously deactivated TEE + /// @dev Requires caller to be the TEE owner with TEE_OPERATOR role, or an admin. + /// Also re-validates that the TEE's PCR is still approved for its type. + /// @param teeId The TEE identifier to activate + function activateTEE(bytes32 teeId) external onlyTEEOwnerOrAdmin(teeId) { TEEInfo storage tee = tees[teeId]; - if (tee.registeredAt == 0) revert TEENotFound(); - if (tee.owner != msg.sender && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert NotTEEOwner(); - if (tee.active) return; // Already active, nothing to do + // Make sure to do an early return here in order to prevent + // getting around the heartbeat check which relies on lastUpdatedAt. + if (tee.active) return; + + _requirePCRValidForTEE(tee.pcrHash, tee.teeType); tee.active = true; - tee.lastUpdatedAt = block.timestamp; - _addToActiveList(teeId); + _addToActiveList(teeId, tee.teeType); emit TEEActivated(teeId); } - function _addToActiveList(bytes32 teeId) private { - _activeTEEIndex[teeId] = _activeTEEList.length; - _activeTEEList.push(teeId); + /// @notice Permanently remove a TEE from all storage + /// @dev Callable by TEE owner (with TEE_OPERATOR role) or admin. + /// Use to clean up decommissioned or upgraded TEEs and reclaim storage. + /// @param teeId The TEE identifier to remove + function removeTEE(bytes32 teeId) external onlyTEEOwnerOrAdmin(teeId) { + TEEInfo storage tee = tees[teeId]; + + uint8 teeType = tee.teeType; + address owner = tee.owner; + + // Remove from active list if active + if (tee.active) { + _removeFromActiveList(teeId, teeType); + } + + // Remove from _teesByType + _removeFromArray(_teesByType[teeType], teeId); + + // Remove from _teesByOwner + _removeFromArray(_teesByOwner[owner], teeId); + + // Delete TEE data + delete tees[teeId]; + + emit TEERemoved(teeId); + } + + function _addToActiveList(bytes32 teeId, uint8 teeType) private { + _activeTEEIndex[teeType][teeId] = _activeTEEList[teeType].length; + _activeTEEList[teeType].push(teeId); } - function _removeFromActiveList(bytes32 teeId) private { - uint256 index = _activeTEEIndex[teeId]; - uint256 lastIndex = _activeTEEList.length - 1; - + function _removeFromActiveList(bytes32 teeId, uint8 teeType) private { + uint256 index = _activeTEEIndex[teeType][teeId]; + uint256 lastIndex = _activeTEEList[teeType].length - 1; + if (index != lastIndex) { - bytes32 lastTeeId = _activeTEEList[lastIndex]; - _activeTEEList[index] = lastTeeId; - _activeTEEIndex[lastTeeId] = index; + bytes32 lastTeeId = _activeTEEList[teeType][lastIndex]; + _activeTEEList[teeType][index] = lastTeeId; + _activeTEEIndex[teeType][lastTeeId] = index; + } + + _activeTEEList[teeType].pop(); + delete _activeTEEIndex[teeType][teeId]; + } + + /// @dev Swap-and-pop removal from an unordered bytes32 array + function _removeFromArray(bytes32[] storage arr, bytes32 value) private { + for (uint256 i = 0; i < arr.length; i++) { + if (arr[i] == value) { + arr[i] = arr[arr.length - 1]; + arr.pop(); + return; + } } - - _activeTEEList.pop(); - delete _activeTEEIndex[teeId]; } // ============ Heartbeat ============ @@ -317,7 +485,10 @@ contract TEERegistry is AccessControl { if (tee.registeredAt == 0) revert TEENotFound(); if (!tee.active) revert TEENotActive(); - // Reject stale or future signed timestamps. + // Lazy PCR enforcement (validity + type match) + _requirePCRValidForTEE(tee.pcrHash, tee.teeType); + + // Reject stale or future signed timestamps if (timestamp > block.timestamp) revert HeartbeatTimestampInFuture(); if (block.timestamp - timestamp > heartbeatMaxAge) revert HeartbeatTimestampTooOld(); @@ -327,64 +498,95 @@ contract TEERegistry is AccessControl { if (!valid) revert HeartbeatSignatureInvalid(); tee.lastUpdatedAt = block.timestamp; - emit HeartbeatReceived(teeId, timestamp); } - /// @notice Update the max allowed age for heartbeat timestamps. + /// @notice Update the max allowed age for heartbeat timestamps function setHeartbeatMaxAge(uint256 maxAge) external onlyRole(DEFAULT_ADMIN_ROLE) { heartbeatMaxAge = maxAge; } - // ============ Utilities ============ - - function computeMessageHash( - bytes32 inputHash, - bytes32 outputHash, - uint256 timestamp - ) public pure returns (bytes32) { - return keccak256(abi.encodePacked(inputHash, outputHash, timestamp)); - } - // ============ Queries ============ - + + /// @notice Get full TEE info by ID + /// @param teeId The TEE identifier + /// @return The TEE info struct function getTEE(bytes32 teeId) external view returns (TEEInfo memory) { if (tees[teeId].registeredAt == 0) revert TEENotFound(); return tees[teeId]; } - function getActiveTEEs() external view returns (bytes32[] memory) { - return _activeTEEList; + /// @notice Get TEE IDs that have been activated for a given type + /// @dev Does NOT filter by heartbeat freshness or PCR validity. + /// Use getLiveTEEs() for fully verified results. + /// @param teeType The TEE type to query + /// @return Array of TEE IDs + function getActivatedTEEs(uint8 teeType) external view returns (bytes32[] memory) { + return _activeTEEList[teeType]; } + /// @notice Get TEEs that are activated, have a valid PCR, and a fresh heartbeat + /// @dev More expensive than getActivatedTEEs() due to on-chain filtering. + /// Use this when you need guaranteed-healthy TEEs without client-side checks. + /// @param teeType The TEE type to query + /// @return Array of TEEInfo structs for live TEEs + function getLiveTEEs(uint8 teeType) external view returns (TEEInfo[] memory) { + bytes32[] storage list = _activeTEEList[teeType]; + uint256 count = 0; + for (uint256 i = 0; i < list.length; i++) { + if (_isLive(tees[list[i]])) count++; + } + + TEEInfo[] memory result = new TEEInfo[](count); + uint256 j = 0; + for (uint256 i = 0; i < list.length; i++) { + if (_isLive(tees[list[i]])) { + result[j++] = tees[list[i]]; + } + } + return result; + } + + /// @notice Get all TEE IDs (active and inactive) for a given type + /// @param teeType The TEE type to query + /// @return Array of TEE IDs function getTEEsByType(uint8 teeType) external view returns (bytes32[] memory) { return _teesByType[teeType]; } + /// @notice Get all TEE IDs owned by an address + /// @param owner The owner address to query + /// @return Array of TEE IDs function getTEEsByOwner(address owner) external view returns (bytes32[] memory) { return _teesByOwner[owner]; } - function getPublicKey(bytes32 teeId) external view returns (bytes memory) { - if (tees[teeId].registeredAt == 0) revert TEENotFound(); - return tees[teeId].publicKey; + /// @notice Check if a TEE is currently active + function isActive(bytes32 teeId) external view returns (bool) { + return tees[teeId].active; } - function getTLSCertificate(bytes32 teeId) external view returns (bytes memory) { - if (tees[teeId].registeredAt == 0) revert TEENotFound(); - return tees[teeId].tlsCertificate; + /// @notice Check if a TEE is live (active + valid PCR + fresh heartbeat) + function isLive(bytes32 teeId) external view returns (bool) { + return _isLive(tees[teeId]); } - function isActive(bytes32 teeId) external view returns (bool) { - return tees[teeId].active; + function _isLive(TEEInfo storage tee) private view returns (bool) { + if (!tee.active) return false; + if (block.timestamp - tee.lastUpdatedAt > heartbeatMaxAge) return false; + if (!isPCRApproved(tee.teeType, tee.pcrHash)) return false; + return true; } - function getPaymentAddress(bytes32 teeId) external view returns (address) { - if (tees[teeId].registeredAt == 0) revert TEENotFound(); - return tees[teeId].paymentAddress; + /// @notice Get a TEE's public key + function getPublicKey(bytes32 teeId) external view returns (bytes memory) { + return tees[teeId].publicKey; } + /// @notice Compute TEE ID from its public key + /// @param publicKey The TEE's public key + /// @return The TEE identifier (keccak256 hash) function computeTEEId(bytes calldata publicKey) external pure returns (bytes32) { return keccak256(publicKey); } -} +} \ No newline at end of file diff --git a/precompiles/tee/README.md b/precompiles/tee/README.md index b74bba98..8d2f0285 100644 --- a/precompiles/tee/README.md +++ b/precompiles/tee/README.md @@ -1,6 +1,6 @@ # TEE Registry -TEE registration and settlement verification for AWS Nitro Enclaves. +TEE registration, lifecycle management, and settlement verification for AWS Nitro Enclaves. ## Architecture @@ -11,28 +11,83 @@ TEE registration and settlement verification for AWS Nitro Enclaves. │ │ │ │ │ • Storage & Logic │ │ • verifyAttestation() │ │ • Access Control │ │ • verifyRSAPSS() │ -└─────────────────────────┘ └─────────────────────────┘ +│ • PCR Management │ └─────────────────────────┘ +│ • Heartbeat │ +└────────┬────────────────┘ + │ + ▼ +┌─────────────────────────┐ ┌─────────────────────────┐ +│ TEEInferenceVerifier.sol│────────►│ InferenceSettlement │ +│ │ │ Relay.sol │ +│ • verifySignature() │ │ │ +│ • Timestamp bounds │ │ • settleIndividual() │ +│ • RSA-PSS via 0x900 │ │ • batchSettle() │ +└─────────────────────────┘ │ • Merkle proof verify │ + └─────────────────────────┘ ``` -## Quick Start +## Workflow -```solidity -TEERegistry registry = TEERegistry(registryAddress); +### 1. Setup (Admin) -// 1. Admin: Setup TEE type and PCRs +```solidity +// Add a TEE type (e.g., LLMProxy, Validator) registry.addTEEType(0, "LLMProxy"); -registry.approvePCR(pcrs, "v1.0.0", bytes32(0), 0); -// 2. Operator: Register TEE +// Approve PCR measurements for that type +// PCRs identify the exact enclave code allowed to register +registry.approvePCR(pcrs, "v1.0.0", 0); +``` + +### 2. Registration (TEE Operator) + +```solidity +// Register TEE with attestation document from AWS Nitro +// Precompile verifies attestation, extracts PCR hash, binds signing key + TLS cert bytes32 teeId = registry.registerTEEWithAttestation( attestationDoc, signingKey, tlsCert, paymentAddr, endpoint, 0 ); +// TEE is now active and in the active TEE list +``` + +### 3. Heartbeat (TEE) -// 3. Verifier: Check TEE inference signature +```solidity +// TEE periodically proves liveness with RSA-PSS signed timestamp +// Also enforces PCR validity — if PCR was revoked, heartbeat fails +registry.heartbeat(teeId, timestamp, signature); +``` + +### 4. Inference Verification (Settlement) + +```solidity +// TEEInferenceVerifier checks: TEE is active, timestamp in bounds, RSA-PSS signature valid bool valid = verifier.verifySignature(teeId, inputHash, outputHash, timestamp, signature); -// 4. Client: Get TLS cert for HTTPS verification -bytes memory cert = registry.getTLSCertificate(teeId); +// InferenceSettlementRelay uses this for on-chain settlement +relay.settleIndividual(teeId, inputHash, outputHash, timestamp, ethAddress, blobId, signature); +``` + +### 5. PCR Revocation (Admin) + +```solidity +// Immediate revocation +registry.revokePCR(pcrHash, 0); + +// Or with grace period (e.g., 1 hour for TEEs to upgrade) +registry.revokePCR(pcrHash, 3600); +``` + +PCR revocation is enforced lazily — TEEs with revoked PCRs are caught at: +- `activateTEE()` — cannot re-activate with a revoked PCR +- `heartbeat()` — next heartbeat will fail + +### 6. TEE Lifecycle (Owner/Admin) + +```solidity +// Owner or admin can deactivate/reactivate +registry.deactivateTEE(teeId); +registry.activateTEE(teeId); // requires PCR to still be approved ``` ## Key Functions @@ -40,12 +95,21 @@ bytes memory cert = registry.getTLSCertificate(teeId); | Function | Who | Purpose | |----------|-----|---------| | `addTEEType()` | Admin | Add TEE category | -| `approvePCR()` | Admin | Approve enclave code hash | -| `registerTEEWithAttestation()` | Operator | Register new TEE | +| `deactivateTEEType()` | Admin | Deactivate a TEE category | +| `approvePCR()` | Admin | Approve enclave code hash for a TEE type | +| `revokePCR()` | Admin | Revoke PCR (immediate or with grace period) | +| `registerTEEWithAttestation()` | Operator | Register new TEE via attestation | +| `activateTEE()` | Owner/Admin | Re-activate TEE (checks PCR validity) | +| `deactivateTEE()` | Owner/Admin | Deactivate TEE | +| `heartbeat()` | Anyone (relayed) | Prove TEE liveness (checks PCR validity) | | `verifySignature()` | TEEInferenceVerifier | Verify TEE inference signature | +| `settleIndividual()` | Settlement Relay | Settle with signature verification | +| `batchSettle()` | Settlement Relay | Emit batch settlement root | | `getTEE()` | Anyone | Get TEE info | | `getTLSCertificate()` | Anyone | Get TLS cert for HTTPS | | `isActive()` | Anyone | Check TEE status | +| `getActiveTEEs()` | Anyone | List all active TEE IDs | +| `getActivePCRs()` | Anyone | List all approved PCR hashes | ## Access Control @@ -53,14 +117,35 @@ Uses OpenZeppelin AccessControl: | Role | Can Do | |------|--------| -| `DEFAULT_ADMIN_ROLE` | Manage PCRs, TEE types, roles | +| `DEFAULT_ADMIN_ROLE` | Manage PCRs, TEE types, certificates, roles, heartbeat config | | `TEE_OPERATOR` | Register TEEs | +| `SETTLEMENT_RELAY_ROLE` | Submit settlements (on InferenceSettlementRelay) | + +## CLI + +The `tee-mgmt-cli` tool (`scripts/tee-mgmt-cli/`) provides commands for all admin and operator operations: + +```bash +# PCR management +tee-mgmt-cli pcr approve --measurements-file measurements.json --version v1.0.0 --tee-type 0 +tee-mgmt-cli pcr revoke --grace-period 3600 +tee-mgmt-cli pcr list +tee-mgmt-cli pcr check +tee-mgmt-cli pcr compute --measurements-file measurements.json + +# TEE management +tee-mgmt-cli tee list +tee-mgmt-cli tee info +tee-mgmt-cli tee register --host +tee-mgmt-cli tee deactivate +tee-mgmt-cli tee activate +``` ## Environment Variables | Variable | Required by | Description | |---|---|---| -| `TEE_ENCLAVE_HOST` | Integration tests & scripts | IP or hostname of the live enclave | +| `TEE_ENCLAVE_HOST` | Integration tests & scripts | IP or hostname of the live enclave | | `TEE_REGISTRY_ADDRESS` | `local_tee_workflow.go` | Optional — reuse an already-deployed TEERegistry contract | ## Testing @@ -71,6 +156,12 @@ cd precompiles/tee go test -v ./... ``` +### Solidity Tests +```bash +cd tests/solidity/suites/tee +npm test +``` + ### Integration Tests Require a live AWS Nitro enclave and a running local node. @@ -91,13 +182,10 @@ TEE_REGISTRY_ADDRESS=0x... TEE_ENCLAVE_HOST=127.0.0.1 go run local_tee_workflow. ## TODO ### Planned Features -- [ ] TEE health monitoring hooks +- [ ] TEE health monitoring hooks - [ ] Batch TEE registration ### Integration Tasks -- [ ] LLM Server: - [ ] Facilitator (x402): Call `verifySignature()` via FacilitatorSettlementRelay before payment - [ ] Frontend/dashboard: Download and pin TLS certificates - [ ] Monitoring: Track TEE active status - - diff --git a/scripts/integration/local_tee_workflow.go b/scripts/integration/local_tee_workflow.go index 04a9acb9..94ebe7f3 100755 --- a/scripts/integration/local_tee_workflow.go +++ b/scripts/integration/local_tee_workflow.go @@ -30,7 +30,7 @@ import ( ) const ( - RPC_URL = "http://13.59.43.94:8545" + RPC_URL = "http://127.0.0.1:8545" VERIFIER_ADDRESS = "0x0000000000000000000000000000000000000900" ENCLAVE_HOST = "3.15.214.21" ENCLAVE_PORT = "443" @@ -42,7 +42,7 @@ var TEE_REGISTRY_ADDRESS string // ============================================================================ // PASTE YOUR NEW COMPILED BYTECODE HERE // ============================================================================ -const TEE_REGISTRY_BYTECODE = "608060405234801561000f575f5ffd5b5061001a5f3361004f565b506100325f516020612f4d5f395f51905f523361004f565b5061004a5f516020612f4d5f395f51905f525f6100f8565b610142565b5f828152602081815260408083206001600160a01b038516845290915281205460ff166100ef575f838152602081815260408083206001600160a01b03861684529091529020805460ff191660011790556100a73390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45060016100f2565b505f5b92915050565b5f82815260208190526040808220600101805490849055905190918391839186917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a4505050565b612dfe8061014f5f395ff3fe608060405234801561000f575f5ffd5b506004361061024a575f3560e01c8063b19ffd1c11610140578063d046a7fa116100bf578063dd31611311610084578063dd316113146105c9578063de3c8005146105dc578063e10a2ea4146105e5578063e44cc4821461060e578063f33d2c0014610621578063f44a078014610634575f5ffd5b8063d046a7fa14610571578063d2e0342714610579578063d547741f14610581578063d5ed579d14610594578063d6741a42146105a7575f5ffd5b8063c0abde4511610105578063c0abde45146104bb578063c206baa3146104ce578063c855bcc414610513578063cbdfc4e014610535578063ccdf049314610548575f5ffd5b8063b19ffd1c1461043c578063b1c551ca1461045f578063b348303f1461047f578063b778f86914610488578063b82644b71461049b575f5ffd5b8063476cb030116101cc5780639138c99e116101915780639138c99e146103e657806391cc00e9146103f957806391d148541461040c578063971cfbf11461041f578063a217fddf14610435575f5ffd5b8063476cb0301461036f5780634b19d4631461038257806358db61a8146103955780635c36901c146103a8578063643d8cb5146103d3575f5ffd5b80632f2ff15d116102125780632f2ff15d146102ef57806336568abe146103025780633c1a88b014610315578063418b207d1461033c57806343ed32741461035c575f5ffd5b806301ffc9a71461024e57806308c84e7014610276578063097775c61461029757806315c9bdb4146102ac578063248a9ca3146102cd575b5f5ffd5b61026161025c36600461225f565b61063c565b60405190151581526020015b60405180910390f35b61027f61090081565b6040516001600160a01b03909116815260200161026d565b6102aa6102a536600461228d565b610672565b005b6102bf6102ba366004612313565b610785565b60405190815260200161026d565b6102bf6102db36600461228d565b5f9081526020819052604090206001015490565b6102aa6102fd3660046123fe565b610bc7565b6102aa6103103660046123fe565b610bf1565b6102bf7fae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf81565b61034f61034a36600461228d565b610c29565b60405161026d9190612456565b6102aa61036a366004612535565b610eee565b6102bf61037d366004612589565b610f4a565b6102aa6103903660046125c2565b610fa1565b6102616103a33660046125db565b611026565b6102616103b636600461228d565b5f9081526006602081905260409091200154610100900460ff1690565b6102616103e13660046125db565b6110f9565b6102aa6103f436600461228d565b611239565b61027f61040736600461228d565b611283565b61026161041a3660046123fe565b6112d1565b6104276112f9565b60405161026d92919061263f565b6102bf5f81565b61044f61044a36600461228d565b611511565b60405161026d9493929190612703565b61047261046d36600461228d565b6115c6565b60405161026d9190612729565b6102bf61012c81565b61047261049636600461228d565b611696565b6104ae6104a93660046125c2565b6116e3565b60405161026d919061273b565b6102aa6104c936600461277d565b611745565b6102bf6104dc3660046127f5565b6040805160208082019590955280820193909352606080840192909252805180840390920182526080909201909152805191012090565b61026161052136600461228d565b600b6020525f908152604090205460ff1681565b6102aa61054336600461281e565b6118b0565b61055b61055636600461228d565b611a0b565b60405161026d9a9998979695949392919061286c565b6104ae611bff565b6104ae611c55565b6102aa61058f3660046123fe565b611d69565b6102616105a236600461228d565b611d8d565b6105ba6105b53660046125c2565b611dd8565b60405161026d939291906128f1565b6102aa6105d736600461228d565b611e83565b6102bf610e1081565b6102616105f33660046125c2565b60ff9081165f90815260016020819052604090912001541690565b6104ae61061c366004612915565b611f53565b6102bf61062f366004612535565b611fba565b610472611fdb565b5f6001600160e01b03198216637965db0b60e01b148061066c57506301ffc9a760e01b6001600160e01b03198316145b92915050565b5f81815260066020526040812060078101549091036106a45760405163f6523b6560e01b815260040160405180910390fd5b80546001600160a01b031633148015906106c557506106c35f336112d1565b155b156106e3576040516398b31e6f60e01b815260040160405180910390fd5b6006810154610100900460ff16156106f9575050565b60068101805461ff00191661010017905542600882015561075782600780545f838152600860205260408120829055600182018355919091527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6880155565b60405182907ff1107fa7ae6c5cdb1675a2191d02d30d03220d684e051e5c0632ca0458fe7974905f90a25050565b5f7fae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf6107b081612067565b60ff8084165f9081526001602081905260409091200154166107e557604051635d672da760e01b815260040160405180910390fd5b89896040516107f592919061292e565b6040518091039020915060065f8381526020019081526020015f20600701545f1461083357604051631346e43960e01b815260040160405180910390fd5b5f5f6109006001600160a01b031663f7cf40d38f8f8f8f8f8f60056040518863ffffffff1660e01b81526004016108709796959493929190612a16565b6040805180830381865afa15801561088a573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108ae9190612a82565b915091508161090557604051630368f92d60e11b815260206004820152601f60248201527f4174746573746174696f6e20766572696669636174696f6e206661696c65640060448201526064015b60405180910390fd5b61090e81611d8d565b61092a5760405162d85d7f60e71b815260040160405180910390fd5b604051806101400160405280336001600160a01b03168152602001896001600160a01b0316815260200188888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505081526020018d8d8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250505090825250604080516020601f8e018190048102820181019092528c815291810191908d908d90819084018382808284375f920182905250938552505050602080830185905260ff8916604080850191909152600160608501819052426080860181905260a0909501949094528883526006825291829020845181546001600160a01b03199081166001600160a01b0392831617835592860151948201805490931694169390931790558201516002820190610a8c9082612b0b565b5060608201516003820190610aa19082612b0b565b5060808201516004820190610ab69082612b0b565b5060a0820151600582015560c082015160068201805460e085015160ff93841661ffff19909216919091176101009115158202179091558301516007808401919091556101209093015160089283015582545f8881526020938452604080822083905560018381019096557fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688909201899055338082526009855282822080548088018255908352858320018a9055928a16808252600a855282822080549687018155825290849020909401889055519283529186917f97d0af70a4d2c616e9c1093d5f57c8b31b3a7d824f1d335e5d5416402ec14931910160405180910390a35050509a9950505050505050505050565b5f82815260208190526040902060010154610be181612067565b610beb8383612074565b50505050565b6001600160a01b0381163314610c1a5760405163334bd91960e11b815260040160405180910390fd5b610c248282612103565b505050565b610c8f6040518061014001604052805f6001600160a01b031681526020015f6001600160a01b031681526020016060815260200160608152602001606081526020015f81526020015f60ff1681526020015f151581526020015f81526020015f81525090565b5f828152600660205260408120600701549003610cbf5760405163f6523b6560e01b815260040160405180910390fd5b5f8281526006602090815260409182902082516101408101845281546001600160a01b039081168252600183015416928101929092526002810180549293919291840191610d0c90612965565b80601f0160208091040260200160405190810160405280929190818152602001828054610d3890612965565b8015610d835780601f10610d5a57610100808354040283529160200191610d83565b820191905f5260205f20905b815481529060010190602001808311610d6657829003601f168201915b50505050508152602001600382018054610d9c90612965565b80601f0160208091040260200160405190810160405280929190818152602001828054610dc890612965565b8015610e135780601f10610dea57610100808354040283529160200191610e13565b820191905f5260205f20905b815481529060010190602001808311610df657829003601f168201915b50505050508152602001600482018054610e2c90612965565b80601f0160208091040260200160405190810160405280929190818152602001828054610e5890612965565b8015610ea35780601f10610e7a57610100808354040283529160200191610ea3565b820191905f5260205f20905b815481529060010190602001808311610e8657829003601f168201915b505050918352505060058201546020820152600682015460ff808216604084015261010090910416151560608201526007820154608082015260089091015460a09091015292915050565b5f610ef881612067565b6005610f05838583612bc5565b508282604051610f1692919061292e565b604051908190038120907f378c4547bd8b346276ae1db9e298d984a43bd8788ad795e7a141ba069045f4cb905f90a2505050565b5f610f558280612c7e565b610f626020850185612c7e565b610f6f6040870187612c7e565b604051602001610f8496959493929190612cc0565b604051602081830303815290604052805190602001209050919050565b5f610fab81612067565b60ff82165f908152600160205260408120600201549003610fde57604051629f97d960e31b815260040160405180910390fd5b60ff82165f818152600160208190526040808320909101805460ff19169055517f30e2b43b128fac88e776d92f693d539845d727cdb1978017ddff47a2e5319a039190a25050565b5f868152600660208190526040822090810154610100900460ff1661104e575f9150506110ef565b6040805160208082018a905281830189905260608083018990528351808403909101815260808301938490528051910120633f1333cd60e11b90925261090090637e26679a906110ab90600386019085908a908a90608401612cec565b602060405180830381865afa1580156110c6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110ea9190612d22565b925050505b9695505050505050565b5f611106610e1042612d4f565b8410156111265760405163d40fc74b60e01b815260040160405180910390fd5b61113261012c42612d62565b841115611152576040516347860b9760e01b815260040160405180910390fd5b604080516020810189905290810187905260608101869052608081018590525f9060a00160408051601f1981840301815291815281516020928301205f818152600b90935291205490915060ff16156111be5760405163043881e960e11b815260040160405180910390fd5b6111cc888888888888611026565b6111e957604051638baa579f60e01b815260040160405180910390fd5b5f818152600b6020526040808220805460ff191660011790555182918a917fa01d6f73b605f49667014b1d3d37ec01d0b7dce889375902112d267976765aeb9190a3506001979650505050505050565b5f61124381612067565b5f82815260036020526040808220805460ff191690555183917f021cbdaf8e2ddc626ca016468398743e833c1d67b00106b10077757586679a2091a25050565b5f8181526006602052604081206007015481036112b35760405163f6523b6560e01b815260040160405180910390fd5b505f908152600660205260409020600101546001600160a01b031690565b5f918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b606080600280548060200260200160405190810160405280929190818152602001828054801561136357602002820191905f5260205f20905f905b825461010083900a900460ff168152602060019283018181049485019490930390920291018084116113345790505b505060025493955050506001600160401b03821115905061138657611386612aac565b6040519080825280602002602001820160405280156113db57816020015b6113c86040518060600160405280606081526020015f151581526020015f81525090565b8152602001906001900390816113a45790505b5090505f5b60025481101561150c5760015f6002838154811061140057611400612d75565b905f5260205f2090602091828204019190069054906101000a900460ff1660ff1660ff1681526020019081526020015f206040518060600160405290815f8201805461144b90612965565b80601f016020809104026020016040519081016040528092919081815260200182805461147790612965565b80156114c25780601f10611499576101008083540402835291602001916114c2565b820191905f5260205f20905b8154815290600101906020018083116114a557829003601f168201915b5050509183525050600182015460ff161515602082015260029091015460409091015282518390839081106114f9576114f9612d75565b60209081029190910101526001016113e0565b509091565b600360208190525f9182526040909120805460018201546002830154938301805460ff909316949193919261154590612965565b80601f016020809104026020016040519081016040528092919081815260200182805461157190612965565b80156115bc5780601f10611593576101008083540402835291602001916115bc565b820191905f5260205f20905b81548152906001019060200180831161159f57829003601f168201915b5050505050905084565b5f81815260066020526040812060070154606091036115f85760405163f6523b6560e01b815260040160405180910390fd5b5f828152600660205260409020600301805461161390612965565b80601f016020809104026020016040519081016040528092919081815260200182805461163f90612965565b801561168a5780601f106116615761010080835404028352916020019161168a565b820191905f5260205f20905b81548152906001019060200180831161166d57829003601f168201915b50505050509050919050565b5f81815260066020526040812060070154606091036116c85760405163f6523b6560e01b815260040160405180910390fd5b5f828152600660205260409020600401805461161390612965565b60ff81165f908152600a602090815260409182902080548351818402810184019094528084526060939283018282801561168a57602002820191905f5260205f20905b8154815260200190600101908083116117265750505050509050919050565b5f61174f81612067565b5f61175987610f4a565b9050831580159061177757505f8481526003602052604090205460ff165b15611798576117868342612d62565b5f858152600360205260409020600201555b60405180608001604052806001151581526020014281526020015f815260200187878080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250939094525050838152600360208181526040928390208551815460ff191690151517815590850151600182015591840151600283015560608401519192508201906118349082612b0b565b5050600480546001810182555f919091527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b018290555060405181907fa3cbee87ba6def0cc8a2c5a3cf7a54f0209c6f939b07979dde08d447d6c3666d9061189f9089908990612d89565b60405180910390a250505050505050565b5f6118ba81612067565b60ff84165f90815260016020526040902060020154156118ed5760405163095faf7360e01b815260040160405180910390fd5b604051806060016040528084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201829052509385525050600160208085018290524260409586015260ff8a168452525020815181906119569082612b0b565b506020828101516001808401805492151560ff1990931692909217909155604093840151600293840155825490810183555f9290925281047f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01805460ff888116601f9094166101000a84810291021990911617905590517fed0161664cf318441e585cccad9fa9fc770298b648a49635dda84ac6f0adc908906119fd9086908690612d89565b60405180910390a250505050565b60066020525f90815260409020805460018201546002830180546001600160a01b03938416949290931692611a3f90612965565b80601f0160208091040260200160405190810160405280929190818152602001828054611a6b90612965565b8015611ab65780601f10611a8d57610100808354040283529160200191611ab6565b820191905f5260205f20905b815481529060010190602001808311611a9957829003601f168201915b505050505090806003018054611acb90612965565b80601f0160208091040260200160405190810160405280929190818152602001828054611af790612965565b8015611b425780601f10611b1957610100808354040283529160200191611b42565b820191905f5260205f20905b815481529060010190602001808311611b2557829003601f168201915b505050505090806004018054611b5790612965565b80601f0160208091040260200160405190810160405280929190818152602001828054611b8390612965565b8015611bce5780601f10611ba557610100808354040283529160200191611bce565b820191905f5260205f20905b815481529060010190602001808311611bb157829003601f168201915b5050506005840154600685015460078601546008909601549495919460ff808316955061010090920490911692508a565b60606007805480602002602001604051908101604052809291908181526020018280548015611c4b57602002820191905f5260205f20905b815481526020019060010190808311611c37575b5050505050905090565b60605f805b600454811015611ca357611c8860048281548110611c7a57611c7a612d75565b905f5260205f200154611d8d565b15611c9b5781611c9781612d9c565b9250505b600101611c5a565b505f816001600160401b03811115611cbd57611cbd612aac565b604051908082528060200260200182016040528015611ce6578160200160208202803683370190505b5090505f805b600454811015611d6057611d0c60048281548110611c7a57611c7a612d75565b15611d585760048181548110611d2457611d24612d75565b905f5260205f200154838380611d3990612d9c565b945081518110611d4b57611d4b612d75565b6020026020010181815250505b600101611cec565b50909392505050565b5f82815260208190526040902060010154611d8381612067565b610beb8383612103565b5f818152600360205260408120805460ff16611dab57505f92915050565b600281015415801590611dc2575080600201544210155b15611dcf57505f92915050565b50600192915050565b60016020525f9081526040902080548190611df290612965565b80601f0160208091040260200160405190810160405280929190818152602001828054611e1e90612965565b8015611e695780601f10611e4057610100808354040283529160200191611e69565b820191905f5260205f20905b815481529060010190602001808311611e4c57829003601f168201915b505050506001830154600290930154919260ff1691905083565b5f8181526006602052604081206007810154909103611eb55760405163f6523b6560e01b815260040160405180910390fd5b80546001600160a01b03163314801590611ed65750611ed45f336112d1565b155b15611ef4576040516398b31e6f60e01b815260040160405180910390fd5b6006810154610100900460ff16611f09575050565b60068101805461ff0019169055426008820155611f258261216c565b60405182907f784ce1077bc44f5b33e1724f6a1b9469423f16a15680c26d150787fb36349d17905f90a25050565b6001600160a01b0381165f9081526009602090815260409182902080548351818402810184019094528084526060939283018282801561168a57602002820191905f5260205f20908154815260200190600101908083116117265750505050509050919050565b5f8282604051611fcb92919061292e565b6040518091039020905092915050565b60058054611fe890612965565b80601f016020809104026020016040519081016040528092919081815260200182805461201490612965565b801561205f5780601f106120365761010080835404028352916020019161205f565b820191905f5260205f20905b81548152906001019060200180831161204257829003601f168201915b505050505081565b6120718133612222565b50565b5f61207f83836112d1565b6120fc575f838152602081815260408083206001600160a01b03861684529091529020805460ff191660011790556120b43390565b6001600160a01b0316826001600160a01b0316847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a450600161066c565b505f61066c565b5f61210e83836112d1565b156120fc575f838152602081815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a450600161066c565b5f8181526008602052604081205460075490919061218c90600190612d4f565b90508082146121e7575f600782815481106121a9576121a9612d75565b905f5260205f200154905080600784815481106121c8576121c8612d75565b5f91825260208083209091019290925591825260089052604090208290555b60078054806121f8576121f8612db4565b5f828152602080822083015f19908101839055909201909255938152600890935250506040812055565b61222c82826112d1565b61225b5760405163e2517d3f60e01b81526001600160a01b0382166004820152602481018390526044016108fc565b5050565b5f6020828403121561226f575f5ffd5b81356001600160e01b031981168114612286575f5ffd5b9392505050565b5f6020828403121561229d575f5ffd5b5035919050565b5f5f83601f8401126122b4575f5ffd5b5081356001600160401b038111156122ca575f5ffd5b6020830191508360208285010111156122e1575f5ffd5b9250929050565b80356001600160a01b03811681146122fe575f5ffd5b919050565b803560ff811681146122fe575f5ffd5b5f5f5f5f5f5f5f5f5f5f60c08b8d03121561232c575f5ffd5b8a356001600160401b03811115612341575f5ffd5b61234d8d828e016122a4565b909b5099505060208b01356001600160401b0381111561236b575f5ffd5b6123778d828e016122a4565b90995097505060408b01356001600160401b03811115612395575f5ffd5b6123a18d828e016122a4565b90975095506123b4905060608c016122e8565b935060808b01356001600160401b038111156123ce575f5ffd5b6123da8d828e016122a4565b90945092506123ed905060a08c01612303565b90509295989b9194979a5092959850565b5f5f6040838503121561240f575f5ffd5b8235915061241f602084016122e8565b90509250929050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081526124706020820183516001600160a01b03169052565b5f602083015161248b60408401826001600160a01b03169052565b50604083015161014060608401526124a7610160840182612428565b90506060840151601f198483030160808501526124c48282612428565b9150506080840151601f198483030160a08501526124e28282612428565b91505060a084015160c084015260c084015161250360e085018260ff169052565b5060e0840151801515610100850152506101008401516101208401526101208401516101408401528091505092915050565b5f5f60208385031215612546575f5ffd5b82356001600160401b0381111561255b575f5ffd5b612567858286016122a4565b90969095509350505050565b5f60608284031215612583575f5ffd5b50919050565b5f60208284031215612599575f5ffd5b81356001600160401b038111156125ae575f5ffd5b6125ba84828501612573565b949350505050565b5f602082840312156125d2575f5ffd5b61228682612303565b5f5f5f5f5f5f60a087890312156125f0575f5ffd5b8635955060208701359450604087013593506060870135925060808701356001600160401b03811115612621575f5ffd5b61262d89828a016122a4565b979a9699509497509295939492505050565b604080825283519082018190525f9060208501906060840190835b8181101561267b57835160ff1683526020938401939092019160010161265a565b50508381036020850152809150845180825260208201925060208160051b830101602087015f5b838110156126f557601f1985840301865281518051606085526126c86060860182612428565b602083810151151587820152604093840151939096019290925250958301959291909101906001016126a2565b509098975050505050505050565b8415158152836020820152826040820152608060608201525f6110ef6080830184612428565b602081525f6122866020830184612428565b602080825282518282018190525f918401906040840190835b81811015612772578351835260209384019390920191600101612754565b509095945050505050565b5f5f5f5f5f60808688031215612791575f5ffd5b85356001600160401b038111156127a6575f5ffd5b6127b288828901612573565b95505060208601356001600160401b038111156127cd575f5ffd5b6127d9888289016122a4565b9699909850959660408101359660609091013595509350505050565b5f5f5f60608486031215612807575f5ffd5b505081359360208301359350604090920135919050565b5f5f5f60408486031215612830575f5ffd5b61283984612303565b925060208401356001600160401b03811115612853575f5ffd5b61285f868287016122a4565b9497909650939450505050565b6001600160a01b038b811682528a166020820152610140604082018190525f906128989083018b612428565b82810360608401526128aa818b612428565b905082810360808401526128be818a612428565b60a0840198909852505060ff9490941660c085015291151560e08401526101008301526101209091015295945050505050565b606081525f6129036060830186612428565b93151560208301525060400152919050565b5f60208284031215612925575f5ffd5b612286826122e8565b818382375f9101908152919050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b600181811c9082168061297957607f821691505b60208210810361258357634e487b7160e01b5f52602260045260245ffd5b5f81546129a381612965565b8085526001821680156129bd57600181146129d957612a0d565b60ff1983166020870152602082151560051b8701019350612a0d565b845f5260205f205f5b83811015612a045781546020828a0101526001820191506020810190506129e2565b87016020019450505b50505092915050565b608081525f612a2960808301898b61293d565b8281036020840152612a3c81888a61293d565b90508281036040840152612a5181868861293d565b90508281036060840152612a658185612997565b9a9950505050505050505050565b805180151581146122fe575f5ffd5b5f5f60408385031215612a93575f5ffd5b612a9c83612a73565b9150602083015190509250929050565b634e487b7160e01b5f52604160045260245ffd5b601f821115610c2457805f5260205f20601f840160051c81016020851015612ae55750805b601f840160051c820191505b81811015612b04575f8155600101612af1565b5050505050565b81516001600160401b03811115612b2457612b24612aac565b612b3881612b328454612965565b84612ac0565b6020601f821160018114612b6a575f8315612b535750848201515b5f19600385901b1c1916600184901b178455612b04565b5f84815260208120601f198516915b82811015612b995787850151825560209485019460019092019101612b79565b5084821015612bb657868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160401b03831115612bdc57612bdc612aac565b612bf083612bea8354612965565b83612ac0565b5f601f841160018114612c21575f8515612c0a5750838201355b5f19600387901b1c1916600186901b178355612b04565b5f83815260208120601f198716915b82811015612c505786850135825560209485019460019092019101612c30565b5086821015612c6c575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b5f5f8335601e19843603018112612c93575f5ffd5b8301803591506001600160401b03821115612cac575f5ffd5b6020019150368190038213156122e1575f5ffd5b858782375f8682015f8152858782375f908601908152838582375f930192835250909695505050505050565b606081525f612cfe6060830187612997565b8560208401528281036040840152612d1781858761293d565b979650505050505050565b5f60208284031215612d32575f5ffd5b61228682612a73565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561066c5761066c612d3b565b8082018082111561066c5761066c612d3b565b634e487b7160e01b5f52603260045260245ffd5b602081525f6125ba60208301848661293d565b5f60018201612dad57612dad612d3b565b5060010190565b634e487b7160e01b5f52603160045260245ffdfea26469706673582212208e2a44c77fbf4a9ca259f9a92f6413f493f8e7dc5b5acafdd37452f333830c6364736f6c634300081c0033ae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf" +const TEE_REGISTRY_BYTECODE = "0x6080604052610708600655348015610015575f5ffd5b506100285f5f1b3361009160201b60201c565b506100597fae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf3361009160201b60201c565b5061008c7fae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf5f5f1b61018660201b60201c565b61026a565b5f6100a283836101e460201b60201c565b61017c5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061011961024760201b60201c565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019050610180565b5f90505b92915050565b5f6101968361024e60201b60201c565b9050815f5f8581526020019081526020015f20600101819055508181847fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff60405160405180910390a4505050565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f33905090565b5f5f5f8381526020019081526020015f20600101549050919050565b614a07806102775f395ff3fe608060405234801561000f575f5ffd5b5060043610610246575f3560e01c8063a217fddf11610139578063d2e03427116100b6578063e10a2ea41161007a578063e10a2ea41461077b578063e44cc482146107ab578063f0607aea146107db578063f33d2c00146107f7578063f44a07801461082757610246565b8063d2e03427146106c3578063d547741f146106e1578063d5ed579d146106fd578063d6741a421461072d578063dd3161131461075f57610246565b8063c0abde45116100fd578063c0abde4514610604578063c206baa314610620578063cbdfc4e014610650578063ccdf04931461066c578063d046a7fa146106a557610246565b8063a217fddf14610523578063b19ffd1c14610541578063b1c551ca14610574578063b778f869146105a4578063b82644b7146105d457610246565b806343ed3274116101c757806387267aca1161018b57806387267aca1461046a5780639138c99e1461048857806391cc00e9146104a457806391d14854146104d4578063971cfbf11461050457610246565b806343ed3274146103b6578063476cb030146103d25780634b19d463146104025780635c1090b21461041e5780635c36901c1461043a57610246565b80632f2ff15d1161020e5780632f2ff15d1461031457806336568abe146103305780633c1a88b01461034c578063418b207d1461036a57806342830ad81461039a57610246565b806301ffc9a71461024a57806308c84e701461027a578063097775c61461029857806315c9bdb4146102b4578063248a9ca3146102e4575b5f5ffd5b610264600480360381019061025f91906130b6565b610845565b60405161027191906130fb565b60405180910390f35b6102826108be565b60405161028f919061318e565b60405180910390f35b6102b260048036038101906102ad91906131da565b6108c4565b005b6102ce60048036038101906102c9919061332c565b610a74565b6040516102db9190613445565b60405180910390f35b6102fe60048036038101906102f991906131da565b61100e565b60405161030b9190613445565b60405180910390f35b61032e6004803603810190610329919061345e565b61102a565b005b61034a6004803603810190610345919061345e565b61104c565b005b6103546110c7565b6040516103619190613445565b60405180910390f35b610384600480360381019061037f91906131da565b6110eb565b6040516103919190613697565b60405180910390f35b6103b460048036038101906103af91906136e1565b611412565b005b6103d060048036038101906103cb9190613752565b6116a2565b005b6103ec60048036038101906103e791906137bf565b611709565b6040516103f99190613445565b60405180910390f35b61041c60048036038101906104179190613806565b61176b565b005b61043860048036038101906104339190613831565b61182f565b005b610454600480360381019061044f91906131da565b611846565b60405161046191906130fb565b60405180910390f35b610472611870565b60405161047f919061386b565b60405180910390f35b6104a2600480360381019061049d91906131da565b611876565b005b6104be60048036038101906104b991906131da565b6118e6565b6040516104cb9190613893565b60405180910390f35b6104ee60048036038101906104e9919061345e565b61196f565b6040516104fb91906130fb565b60405180910390f35b61050c6119d2565b60405161051a929190613a5c565b60405180910390f35b61052b611bed565b6040516105389190613445565b60405180910390f35b61055b600480360381019061055691906131da565b611bf3565b60405161056b9493929190613ad9565b60405180910390f35b61058e600480360381019061058991906131da565b611cb1565b60405161059b9190613b6b565b60405180910390f35b6105be60048036038101906105b991906131da565b611da2565b6040516105cb9190613b6b565b60405180910390f35b6105ee60048036038101906105e99190613806565b611e93565b6040516105fb9190613c33565b60405180910390f35b61061e60048036038101906106199190613c53565b611f00565b005b61063a60048036038101906106359190613cf3565b6120eb565b6040516106479190613445565b60405180910390f35b61066a60048036038101906106659190613d43565b612120565b005b610686600480360381019061068191906131da565b6122bd565b60405161069c9a99989796959493929190613daf565b60405180910390f35b6106ad6124f6565b6040516106ba9190613c33565b60405180910390f35b6106cb61254c565b6040516106d89190613c33565b60405180910390f35b6106fb60048036038101906106f6919061345e565b612697565b005b610717600480360381019061071291906131da565b6126b9565b60405161072491906130fb565b60405180910390f35b61074760048036038101906107429190613806565b61271c565b60405161075693929190613e5e565b60405180910390f35b610779600480360381019061077491906131da565b6127d4565b005b61079560048036038101906107909190613806565b61293f565b6040516107a291906130fb565b60405180910390f35b6107c560048036038101906107c09190613e9a565b61296e565b6040516107d29190613c33565b60405180910390f35b6107f560048036038101906107f091906131da565b612a01565b005b610811600480360381019061080c9190613752565b612a1a565b60405161081e9190613445565b60405180910390f35b61082f612a3b565b60405161083c9190613b6b565b60405180910390f35b5f7f7965db0b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806108b757506108b682612ac7565b5b9050919050565b61090081565b5f60075f8381526020019081526020015f2090505f816007015403610915576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff16815f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415801561097c575061097a5f5f1b3361196f565b155b156109b3576040517f98b31e6f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060060160019054906101000a900460ff16156109d05750610a71565b6109dd81600501546126b9565b610a13576040517f6c2ebf8000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160060160016101000a81548160ff021916908315150217905550428160080181905550610a4282612b30565b817ff1107fa7ae6c5cdb1675a2191d02d30d03220d684e051e5c0632ca0458fe797460405160405180910390a2505b50565b5f7fae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf610a9f81612b74565b610aa88361293f565b610ade576040517f5d672da700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8989604051610aee929190613f01565b604051809103902091505f60075f8481526020019081526020015f206007015414610b45576040517f1346e43900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f61090073ffffffffffffffffffffffffffffffffffffffff1663f7cf40d38f8f8f8f8f8f60056040518863ffffffff1660e01b8152600401610b8f9796959493929190614035565b6040805180830381865afa158015610ba9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bcd91906140d8565b9150915081610c11576040517f06d1f25a000000000000000000000000000000000000000000000000000000008152600401610c0890614160565b60405180910390fd5b610c1a816126b9565b610c50576040517f6c2ebf8000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040518061014001604052803373ffffffffffffffffffffffffffffffffffffffff1681526020018973ffffffffffffffffffffffffffffffffffffffff16815260200188888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505081526020018d8d8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505081526020018b8b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505081526020018281526020018660ff1681526020016001151581526020014281526020014281525060075f8681526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040820151816002019081610e419190614342565b506060820151816003019081610e579190614457565b506080820151816004019081610e6d9190614457565b5060a0820151816005015560c0820151816006015f6101000a81548160ff021916908360ff16021790555060e08201518160060160016101000a81548160ff0219169083151502179055506101008201518160070155610120820151816008015590505060088054905060095f8681526020019081526020015f2081905550600884908060018154018082558091505060019003905f5260205f20015f9091909190915055600a5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2084908060018154018082558091505060019003905f5260205f20015f9091909190915055600b5f8660ff1660ff1681526020019081526020015f2084908060018154018082558091505060019003905f5260205f20015f90919091909150553373ffffffffffffffffffffffffffffffffffffffff16847f97d0af70a4d2c616e9c1093d5f57c8b31b3a7d824f1d335e5d5416402ec1493187604051610ff59190614526565b60405180910390a35050509a9950505050505050505050565b5f5f5f8381526020019081526020015f20600101549050919050565b6110338261100e565b61103c81612b74565b6110468383612b88565b50505050565b611054612c71565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146110b8576040517f6697b23200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110c28282612c78565b505050565b7fae5084c516dacf3f1a818c437d702c28b8d2088455f592a7ea799413e3b6f1bf81565b6110f3612fbb565b5f60075f8481526020019081526020015f206007015403611140576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60075f8381526020019081526020015f20604051806101400160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200160028201805461121690613f72565b80601f016020809104026020016040519081016040528092919081815260200182805461124290613f72565b801561128d5780601f106112645761010080835404028352916020019161128d565b820191905f5260205f20905b81548152906001019060200180831161127057829003601f168201915b505050505081526020016003820180546112a690613f72565b80601f01602080910402602001604051908101604052809291908181526020018280546112d290613f72565b801561131d5780601f106112f45761010080835404028352916020019161131d565b820191905f5260205f20905b81548152906001019060200180831161130057829003601f168201915b5050505050815260200160048201805461133690613f72565b80601f016020809104026020016040519081016040528092919081815260200182805461136290613f72565b80156113ad5780601f10611384576101008083540402835291602001916113ad565b820191905f5260205f20905b81548152906001019060200180831161139057829003601f168201915b5050505050815260200160058201548152602001600682015f9054906101000a900460ff1660ff1660ff1681526020016006820160019054906101000a900460ff16151515158152602001600782015481526020016008820154815250509050919050565b5f60075f8681526020019081526020015f2090505f816007015403611463576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060060160019054906101000a900460ff166114ab576040517fac10e00100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6114b881600501546126b9565b6114ee576040517f6c2ebf8000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b42841115611528576040517f0c57bc2c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006548442611537919061456c565b111561156f576040517f39f8a70600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f85856040516020016115839291906145df565b6040516020818303038152906040528051906020012090505f61090073ffffffffffffffffffffffffffffffffffffffff16637e26679a846003018488886040518563ffffffff1660e01b81526004016115e0949392919061460a565b602060405180830381865afa1580156115fb573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061161f919061464f565b905080611658576040517f45576abb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b428360080181905550867f3cf0bf7708060bf073b6ddb921eef0485b3eb9da828e0ed751fa6582ef337f5787604051611691919061386b565b60405180910390a250505050505050565b5f5f1b6116ae81612b74565b8282600591826116bf929190614684565b5082826040516116d0929190613f01565b60405180910390207f378c4547bd8b346276ae1db9e298d984a43bd8788ad795e7a141ba069045f4cb60405160405180910390a2505050565b5f81805f0190611719919061475d565b838060200190611729919061475d565b858060400190611739919061475d565b60405160200161174e969594939291906147bf565b604051602081830303815290604052805190602001209050919050565b5f5f1b61177781612b74565b5f60015f8460ff1660ff1681526020019081526020015f2060020154036117ca576040517f04fcbec800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f8460ff1660ff1681526020019081526020015f206001015f6101000a81548160ff0219169083151502179055508160ff167f30e2b43b128fac88e776d92f693d539845d727cdb1978017ddff47a2e5319a0360405160405180910390a25050565b5f5f1b61183b81612b74565b816006819055505050565b5f60075f8381526020019081526020015f2060060160019054906101000a900460ff169050919050565b60065481565b5f5f1b61188281612b74565b5f60035f8481526020019081526020015f205f015f6101000a81548160ff021916908315150217905550817f021cbdaf8e2ddc626ca016468398743e833c1d67b00106b10077757586679a2060405160405180910390a26118e282612d61565b5050565b5f5f60075f8481526020019081526020015f206007015403611934576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60075f8381526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b6060806002805480602002602001604051908101604052809291908181526020018280548015611a4357602002820191905f5260205f20905f905b82829054906101000a900460ff1660ff16815260200190600101906020825f01049283019260010382029150808411611a0d5790505b5050505050915060028054905067ffffffffffffffff811115611a6957611a6861417e565b5b604051908082528060200260200182016040528015611aa257816020015b611a8f613038565b815260200190600190039081611a875790505b5090505f5f90505b600280549050811015611be85760015f60028381548110611ace57611acd6147f5565b5b905f5260205f2090602091828204019190069054906101000a900460ff1660ff1660ff1681526020019081526020015f206040518060600160405290815f82018054611b1990613f72565b80601f0160208091040260200160405190810160405280929190818152602001828054611b4590613f72565b8015611b905780601f10611b6757610100808354040283529160200191611b90565b820191905f5260205f20905b815481529060010190602001808311611b7357829003601f168201915b50505050508152602001600182015f9054906101000a900460ff16151515158152602001600282015481525050828281518110611bd057611bcf6147f5565b5b60200260200101819052508080600101915050611aaa565b509091565b5f5f1b81565b6003602052805f5260405f205f91509050805f015f9054906101000a900460ff1690806001015490806002015490806003018054611c3090613f72565b80601f0160208091040260200160405190810160405280929190818152602001828054611c5c90613f72565b8015611ca75780601f10611c7e57610100808354040283529160200191611ca7565b820191905f5260205f20905b815481529060010190602001808311611c8a57829003601f168201915b5050505050905084565b60605f60075f8481526020019081526020015f206007015403611d00576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60075f8381526020019081526020015f206003018054611d1f90613f72565b80601f0160208091040260200160405190810160405280929190818152602001828054611d4b90613f72565b8015611d965780601f10611d6d57610100808354040283529160200191611d96565b820191905f5260205f20905b815481529060010190602001808311611d7957829003601f168201915b50505050509050919050565b60605f60075f8481526020019081526020015f206007015403611df1576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60075f8381526020019081526020015f206004018054611e1090613f72565b80601f0160208091040260200160405190810160405280929190818152602001828054611e3c90613f72565b8015611e875780601f10611e5e57610100808354040283529160200191611e87565b820191905f5260205f20905b815481529060010190602001808311611e6a57829003601f168201915b50505050509050919050565b6060600b5f8360ff1660ff1681526020019081526020015f20805480602002602001604051908101604052809291908181526020018280548015611ef457602002820191905f5260205f20905b815481526020019060010190808311611ee0575b50505050509050919050565b5f5f1b611f0c81612b74565b5f611f1687611709565b90505f60035f8381526020019081526020015f206001015414611f65576040517f7ecd6d7100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f1b8414158015611f93575060035f8581526020019081526020015f205f015f9054906101000a900460ff165b15611fbd578242611fa49190614822565b60035f8681526020019081526020015f20600201819055505b60405180608001604052806001151581526020014281526020015f815260200187878080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505081525060035f8381526020019081526020015f205f820151815f015f6101000a81548160ff0219169083151502179055506020820151816001015560408201518160020155606082015181600301908161207b9190614342565b50905050600481908060018154018082558091505060019003905f5260205f20015f909190919091505583817f7d0d48aca3f04e4d78b308b395437e0c9f7f64f3beda007017912dfc7d22186c8888876040516120da93929190614881565b60405180910390a350505050505050565b5f838383604051602001612101939291906148b1565b6040516020818303038152906040528051906020012090509392505050565b5f5f1b61212c81612b74565b5f60015f8660ff1660ff1681526020019081526020015f20600201541461217f576040517f095faf7300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604051806060016040528084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f81840152601f19601f8201169050808301925050505050505081526020016001151581526020014281525060015f8660ff1660ff1681526020019081526020015f205f820151815f01908161220a9190614342565b506020820151816001015f6101000a81548160ff02191690831515021790555060408201518160020155905050600284908060018154018082558091505060019003905f5260205f2090602091828204019190069091909190916101000a81548160ff021916908360ff1602179055508360ff167fed0161664cf318441e585cccad9fa9fc770298b648a49635dda84ac6f0adc90884846040516122af9291906148ed565b60405180910390a250505050565b6007602052805f5260405f205f91509050805f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169080600201805461232690613f72565b80601f016020809104026020016040519081016040528092919081815260200182805461235290613f72565b801561239d5780601f106123745761010080835404028352916020019161239d565b820191905f5260205f20905b81548152906001019060200180831161238057829003601f168201915b5050505050908060030180546123b290613f72565b80601f01602080910402602001604051908101604052809291908181526020018280546123de90613f72565b80156124295780601f1061240057610100808354040283529160200191612429565b820191905f5260205f20905b81548152906001019060200180831161240c57829003601f168201915b50505050509080600401805461243e90613f72565b80601f016020809104026020016040519081016040528092919081815260200182805461246a90613f72565b80156124b55780601f1061248c576101008083540402835291602001916124b5565b820191905f5260205f20905b81548152906001019060200180831161249857829003601f168201915b505050505090806005015490806006015f9054906101000a900460ff16908060060160019054906101000a900460ff1690806007015490806008015490508a565b6060600880548060200260200160405190810160405280929190818152602001828054801561254257602002820191905f5260205f20905b81548152602001906001019080831161252e575b5050505050905090565b60605f5f90505f5f90505b6004805490508110156125aa576125896004828154811061257b5761257a6147f5565b5b905f5260205f2001546126b9565b1561259d5781806125999061490f565b9250505b8080600101915050612557565b505f8167ffffffffffffffff8111156125c6576125c561417e565b5b6040519080825280602002602001820160405280156125f45781602001602082028036833780820191505090505b5090505f5f90505f5f90505b60048054905081101561268d5761263260048281548110612624576126236147f5565b5b905f5260205f2001546126b9565b15612680576004818154811061264b5761264a6147f5565b5b905f5260205f2001548383806126609061490f565b945081518110612673576126726147f5565b5b6020026020010181815250505b8080600101915050612600565b5081935050505090565b6126a08261100e565b6126a981612b74565b6126b38383612c78565b50505050565b5f5f60035f8481526020019081526020015f209050805f015f9054906101000a900460ff166126eb575f915050612717565b5f816002015414158015612703575080600201544210155b15612711575f915050612717565b60019150505b919050565b6001602052805f5260405f205f91509050805f01805461273b90613f72565b80601f016020809104026020016040519081016040528092919081815260200182805461276790613f72565b80156127b25780601f10612789576101008083540402835291602001916127b2565b820191905f5260205f20905b81548152906001019060200180831161279557829003601f168201915b505050505090806001015f9054906101000a900460ff16908060020154905083565b5f60075f8381526020019081526020015f2090505f816007015403612825576040517ff6523b6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff16815f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415801561288c575061288a5f5f1b3361196f565b155b156128c3576040517f98b31e6f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060060160019054906101000a900460ff166128df575061293c565b5f8160060160016101000a81548160ff02191690831515021790555042816008018190555061290d82612ea2565b817f784ce1077bc44f5b33e1724f6a1b9469423f16a15680c26d150787fb36349d1760405160405180910390a2505b50565b5f60015f8360ff1660ff1681526020019081526020015f206001015f9054906101000a900460ff169050919050565b6060600a5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208054806020026020016040519081016040528092919081815260200182805480156129f557602002820191905f5260205f20905b8154815260200190600101908083116129e1575b50505050509050919050565b5f5f1b612a0d81612b74565b612a1682612d61565b5050565b5f8282604051612a2b929190613f01565b6040518091039020905092915050565b60058054612a4890613f72565b80601f0160208091040260200160405190810160405280929190818152602001828054612a7490613f72565b8015612abf5780601f10612a9657610100808354040283529160200191612abf565b820191905f5260205f20905b815481529060010190602001808311612aa257829003601f168201915b505050505081565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b60088054905060095f8381526020019081526020015f2081905550600881908060018154018082558091505060019003905f5260205f20015f909190919091505550565b612b8581612b80612c71565b612f6a565b50565b5f612b93838361196f565b612c675760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff021916908315150217905550612c04612c71565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019050612c6b565b5f90505b92915050565b5f33905090565b5f612c83838361196f565b15612d57575f5f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff021916908315150217905550612cf4612c71565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a460019050612d5b565b5f90505b92915050565b5f5f90505f60088054905090505b5f811115612e5c575f6008600183612d87919061456c565b81548110612d9857612d976147f5565b5b905f5260205f20015490505f60075f8381526020019081526020015f209050848160050154148015612dd857508060060160019054906101000a900460ff165b15612e47575f8160060160016101000a81548160ff021916908315150217905550428160080181905550612e0b82612ea2565b817f784ce1077bc44f5b33e1724f6a1b9469423f16a15680c26d150787fb36349d1760405160405180910390a28380612e439061490f565b9450505b50508080612e5490614956565b915050612d6f565b505f811115612e9e57817f46830dc23f937ff33669b0270c90c655aea89fd176ba9f2275b67f329e3b8c9e82604051612e95919061386b565b60405180910390a25b5050565b5f60095f8381526020019081526020015f205490505f6001600880549050612eca919061456c565b9050808214612f2c575f60088281548110612ee857612ee76147f5565b5b905f5260205f20015490508060088481548110612f0857612f076147f5565b5b905f5260205f2001819055508260095f8381526020019081526020015f2081905550505b6008805480612f3e57612f3d61497d565b5b600190038181905f5260205f20015f9055905560095f8481526020019081526020015f205f9055505050565b612f74828261196f565b612fb75780826040517fe2517d3f000000000000000000000000000000000000000000000000000000008152600401612fae9291906149aa565b60405180910390fd5b5050565b6040518061014001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020016060815260200160608152602001606081526020015f81526020015f60ff1681526020015f151581526020015f81526020015f81525090565b6040518060600160405280606081526020015f151581526020015f81525090565b5f5ffd5b5f5ffd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61309581613061565b811461309f575f5ffd5b50565b5f813590506130b08161308c565b92915050565b5f602082840312156130cb576130ca613059565b5b5f6130d8848285016130a2565b91505092915050565b5f8115159050919050565b6130f5816130e1565b82525050565b5f60208201905061310e5f8301846130ec565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f61315661315161314c84613114565b613133565b613114565b9050919050565b5f6131678261313c565b9050919050565b5f6131788261315d565b9050919050565b6131888161316e565b82525050565b5f6020820190506131a15f83018461317f565b92915050565b5f819050919050565b6131b9816131a7565b81146131c3575f5ffd5b50565b5f813590506131d4816131b0565b92915050565b5f602082840312156131ef576131ee613059565b5b5f6131fc848285016131c6565b91505092915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f84011261322657613225613205565b5b8235905067ffffffffffffffff81111561324357613242613209565b5b60208301915083600182028301111561325f5761325e61320d565b5b9250929050565b5f61327082613114565b9050919050565b61328081613266565b811461328a575f5ffd5b50565b5f8135905061329b81613277565b92915050565b5f5f83601f8401126132b6576132b5613205565b5b8235905067ffffffffffffffff8111156132d3576132d2613209565b5b6020830191508360018202830111156132ef576132ee61320d565b5b9250929050565b5f60ff82169050919050565b61330b816132f6565b8114613315575f5ffd5b50565b5f8135905061332681613302565b92915050565b5f5f5f5f5f5f5f5f5f5f60c08b8d03121561334a57613349613059565b5b5f8b013567ffffffffffffffff8111156133675761336661305d565b5b6133738d828e01613211565b9a509a505060208b013567ffffffffffffffff8111156133965761339561305d565b5b6133a28d828e01613211565b985098505060408b013567ffffffffffffffff8111156133c5576133c461305d565b5b6133d18d828e01613211565b965096505060606133e48d828e0161328d565b94505060808b013567ffffffffffffffff8111156134055761340461305d565b5b6134118d828e016132a1565b935093505060a06134248d828e01613318565b9150509295989b9194979a5092959850565b61343f816131a7565b82525050565b5f6020820190506134585f830184613436565b92915050565b5f5f6040838503121561347457613473613059565b5b5f613481858286016131c6565b92505060206134928582860161328d565b9150509250929050565b6134a581613266565b82525050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6134ed826134ab565b6134f781856134b5565b93506135078185602086016134c5565b613510816134d3565b840191505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f61353f8261351b565b6135498185613525565b93506135598185602086016134c5565b613562816134d3565b840191505092915050565b613576816131a7565b82525050565b613585816132f6565b82525050565b613594816130e1565b82525050565b5f819050919050565b6135ac8161359a565b82525050565b5f61014083015f8301516135c85f86018261349c565b5060208301516135db602086018261349c565b50604083015184820360408601526135f382826134e3565b9150506060830151848203606086015261360d8282613535565b915050608083015184820360808601526136278282613535565b91505060a083015161363c60a086018261356d565b5060c083015161364f60c086018261357c565b5060e083015161366260e086018261358b565b506101008301516136776101008601826135a3565b5061012083015161368c6101208601826135a3565b508091505092915050565b5f6020820190508181035f8301526136af81846135b2565b905092915050565b6136c08161359a565b81146136ca575f5ffd5b50565b5f813590506136db816136b7565b92915050565b5f5f5f5f606085870312156136f9576136f8613059565b5b5f613706878288016131c6565b9450506020613717878288016136cd565b935050604085013567ffffffffffffffff8111156137385761373761305d565b5b61374487828801613211565b925092505092959194509250565b5f5f6020838503121561376857613767613059565b5b5f83013567ffffffffffffffff8111156137855761378461305d565b5b61379185828601613211565b92509250509250929050565b5f5ffd5b5f606082840312156137b6576137b561379d565b5b81905092915050565b5f602082840312156137d4576137d3613059565b5b5f82013567ffffffffffffffff8111156137f1576137f061305d565b5b6137fd848285016137a1565b91505092915050565b5f6020828403121561381b5761381a613059565b5b5f61382884828501613318565b91505092915050565b5f6020828403121561384657613845613059565b5b5f613853848285016136cd565b91505092915050565b6138658161359a565b82525050565b5f60208201905061387e5f83018461385c565b92915050565b61388d81613266565b82525050565b5f6020820190506138a65f830184613884565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6138e0838361357c565b60208301905092915050565b5f602082019050919050565b5f613902826138ac565b61390c81856138b6565b9350613917836138c6565b805f5b8381101561394757815161392e88826138d5565b9750613939836138ec565b92505060018101905061391a565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f606083015f8301518482035f86015261399782826134e3565b91505060208301516139ac602086018261358b565b5060408301516139bf60408601826135a3565b508091505092915050565b5f6139d5838361397d565b905092915050565b5f602082019050919050565b5f6139f382613954565b6139fd818561395e565b935083602082028501613a0f8561396e565b805f5b85811015613a4a5784840389528151613a2b85826139ca565b9450613a36836139dd565b925060208a01995050600181019050613a12565b50829750879550505050505092915050565b5f6040820190508181035f830152613a7481856138f8565b90508181036020830152613a8881846139e9565b90509392505050565b5f82825260208201905092915050565b5f613aab826134ab565b613ab58185613a91565b9350613ac58185602086016134c5565b613ace816134d3565b840191505092915050565b5f608082019050613aec5f8301876130ec565b613af9602083018661385c565b613b06604083018561385c565b8181036060830152613b188184613aa1565b905095945050505050565b5f82825260208201905092915050565b5f613b3d8261351b565b613b478185613b23565b9350613b578185602086016134c5565b613b60816134d3565b840191505092915050565b5f6020820190508181035f830152613b838184613b33565b905092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f613bbf838361356d565b60208301905092915050565b5f602082019050919050565b5f613be182613b8b565b613beb8185613b95565b9350613bf683613ba5565b805f5b83811015613c26578151613c0d8882613bb4565b9750613c1883613bcb565b925050600181019050613bf9565b5085935050505092915050565b5f6020820190508181035f830152613c4b8184613bd7565b905092915050565b5f5f5f5f5f60808688031215613c6c57613c6b613059565b5b5f86013567ffffffffffffffff811115613c8957613c8861305d565b5b613c95888289016137a1565b955050602086013567ffffffffffffffff811115613cb657613cb561305d565b5b613cc2888289016132a1565b94509450506040613cd5888289016131c6565b9250506060613ce6888289016136cd565b9150509295509295909350565b5f5f5f60608486031215613d0a57613d09613059565b5b5f613d17868287016131c6565b9350506020613d28868287016131c6565b9250506040613d39868287016136cd565b9150509250925092565b5f5f5f60408486031215613d5a57613d59613059565b5b5f613d6786828701613318565b935050602084013567ffffffffffffffff811115613d8857613d8761305d565b5b613d94868287016132a1565b92509250509250925092565b613da9816132f6565b82525050565b5f61014082019050613dc35f83018d613884565b613dd0602083018c613884565b8181036040830152613de2818b613aa1565b90508181036060830152613df6818a613b33565b90508181036080830152613e0a8189613b33565b9050613e1960a0830188613436565b613e2660c0830187613da0565b613e3360e08301866130ec565b613e4161010083018561385c565b613e4f61012083018461385c565b9b9a5050505050505050505050565b5f6060820190508181035f830152613e768186613aa1565b9050613e8560208301856130ec565b613e92604083018461385c565b949350505050565b5f60208284031215613eaf57613eae613059565b5b5f613ebc8482850161328d565b91505092915050565b5f81905092915050565b828183375f83830152505050565b5f613ee88385613ec5565b9350613ef5838584613ecf565b82840190509392505050565b5f613f0d828486613edd565b91508190509392505050565b5f613f248385613b23565b9350613f31838584613ecf565b613f3a836134d3565b840190509392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680613f8957607f821691505b602082108103613f9c57613f9b613f45565b5b50919050565b5f819050815f5260205f209050919050565b5f8154613fc081613f72565b613fca8186613b23565b9450600182165f8114613fe45760018114613ffa5761402c565b60ff19831686528115156020028601935061402c565b61400385613fa2565b5f5b8381101561402457815481890152600182019150602081019050614005565b808801955050505b50505092915050565b5f6080820190508181035f83015261404e81898b613f19565b90508181036020830152614063818789613f19565b90508181036040830152614078818587613f19565b9050818103606083015261408c8184613fb4565b905098975050505050505050565b6140a3816130e1565b81146140ad575f5ffd5b50565b5f815190506140be8161409a565b92915050565b5f815190506140d2816131b0565b92915050565b5f5f604083850312156140ee576140ed613059565b5b5f6140fb858286016140b0565b925050602061410c858286016140c4565b9150509250929050565b7f4174746573746174696f6e20766572696669636174696f6e206661696c6564005f82015250565b5f61414a601f83613a91565b915061415582614116565b602082019050919050565b5f6020820190508181035f8301526141778161413e565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026142077fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826141cc565b61421186836141cc565b95508019841693508086168417925050509392505050565b5f61424361423e6142398461359a565b613133565b61359a565b9050919050565b5f819050919050565b61425c83614229565b6142706142688261424a565b8484546141d8565b825550505050565b5f5f905090565b614287614278565b614292818484614253565b505050565b5b818110156142b5576142aa5f8261427f565b600181019050614298565b5050565b601f8211156142fa576142cb816141ab565b6142d4846141bd565b810160208510156142e3578190505b6142f76142ef856141bd565b830182614297565b50505b505050565b5f82821c905092915050565b5f61431a5f19846008026142ff565b1980831691505092915050565b5f614332838361430b565b9150826002028217905092915050565b61434b826134ab565b67ffffffffffffffff8111156143645761436361417e565b5b61436e8254613f72565b6143798282856142b9565b5f60209050601f8311600181146143aa575f8415614398578287015190505b6143a28582614327565b865550614409565b601f1984166143b8866141ab565b5f5b828110156143df578489015182556001820191506020850194506020810190506143ba565b868310156143fc57848901516143f8601f89168261430b565b8355505b6001600288020188555050505b505050505050565b601f8211156144525761442381613fa2565b61442c846141bd565b8101602085101561443b578190505b61444f614447856141bd565b830182614297565b50505b505050565b6144608261351b565b67ffffffffffffffff8111156144795761447861417e565b5b6144838254613f72565b61448e828285614411565b5f60209050601f8311600181146144bf575f84156144ad578287015190505b6144b78582614327565b86555061451e565b601f1984166144cd86613fa2565b5f5b828110156144f4578489015182556001820191506020850194506020810190506144cf565b86831015614511578489015161450d601f89168261430b565b8355505b6001600288020188555050505b505050505050565b5f6020820190506145395f830184613da0565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6145768261359a565b91506145818361359a565b92508282039050818111156145995761459861453f565b5b92915050565b5f819050919050565b6145b96145b4826131a7565b61459f565b82525050565b5f819050919050565b6145d96145d48261359a565b6145bf565b82525050565b5f6145ea82856145a8565b6020820191506145fa82846145c8565b6020820191508190509392505050565b5f6060820190508181035f8301526146228187613fb4565b90506146316020830186613436565b8181036040830152614644818486613f19565b905095945050505050565b5f6020828403121561466457614663613059565b5b5f614671848285016140b0565b91505092915050565b5f82905092915050565b61468e838361467a565b67ffffffffffffffff8111156146a7576146a661417e565b5b6146b18254613f72565b6146bc828285614411565b5f601f8311600181146146e9575f84156146d7578287013590505b6146e18582614327565b865550614748565b601f1984166146f786613fa2565b5f5b8281101561471e578489013582556001820191506020850194506020810190506146f9565b8683101561473b5784890135614737601f89168261430b565b8355505b6001600288020188555050505b50505050505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f8335600160200384360303811261477957614778614751565b5b80840192508235915067ffffffffffffffff82111561479b5761479a614755565b5b6020830192506001820236038313156147b7576147b6614759565b5b509250929050565b5f6147cb82888a613edd565b91506147d8828688613edd565b91506147e5828486613edd565b9150819050979650505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f61482c8261359a565b91506148378361359a565b925082820190508082111561484f5761484e61453f565b5b92915050565b5f6148608385613a91565b935061486d838584613ecf565b614876836134d3565b840190509392505050565b5f6040820190508181035f83015261489a818587614855565b90506148a9602083018461385c565b949350505050565b5f6148bc82866145a8565b6020820191506148cc82856145a8565b6020820191506148dc82846145c8565b602082019150819050949350505050565b5f6020820190508181035f830152614906818486614855565b90509392505050565b5f6149198261359a565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361494b5761494a61453f565b5b600182019050919050565b5f6149608261359a565b91505f82036149725761497161453f565b5b600182039050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b5f6040820190506149bd5f830185613884565b6149ca6020830184613436565b939250505056fea26469706673582212201567f158f5e99bc78a5379f9de16126e3c6ee87955f8bc5a5d3d5eca524ae37964736f6c634300081c0033" // ============================================================================ // AccessControl Role Constants @@ -65,8 +65,8 @@ var ( SEL_IS_VALID_TYPE = crypto.Keccak256([]byte("isValidTEEType(uint8)"))[:4] SEL_GET_TEE_TYPES = crypto.Keccak256([]byte("getTEETypes()"))[:4] - SEL_APPROVE_PCR = crypto.Keccak256([]byte("approvePCR((bytes,bytes,bytes),string,bytes32,uint256)"))[:4] - SEL_REVOKE_PCR = crypto.Keccak256([]byte("revokePCR(bytes32)"))[:4] + SEL_APPROVE_PCR = crypto.Keccak256([]byte("approvePCR((bytes,bytes,bytes),string,uint8)"))[:4] + SEL_REVOKE_PCR = crypto.Keccak256([]byte("revokePCR(bytes32,uint256)"))[:4] SEL_IS_PCR_APPROVED = crypto.Keccak256([]byte("isPCRApproved(bytes32)"))[:4] SEL_COMPUTE_PCR_HASH = crypto.Keccak256([]byte("computePCRHash((bytes,bytes,bytes))"))[:4] SEL_GET_ACTIVE_PCRS = crypto.Keccak256([]byte("getActivePCRs()"))[:4] @@ -244,7 +244,7 @@ func main() { } } if len(existingActivePCRs) == 0 { - txHash, err := callApprovePCR(account, pcr0, pcr1, pcr2, "v1.0.0") + txHash, err := callApprovePCR(account, pcr0, pcr1, pcr2, "v1.0.0", 0) if err == nil { waitForTx(txHash) } @@ -340,7 +340,7 @@ func main() { if !realApproved { fmt.Println(" 📝 Approving real enclave PCRs...") - txHash, err := callApprovePCR(account, realPCR0, realPCR1, realPCR2, "enclave-v1") + txHash, err := callApprovePCR(account, realPCR0, realPCR1, realPCR2, "enclave-v1", 0) if err == nil { waitForTx(txHash) // [LOG] Verify the approval actually stuck @@ -517,10 +517,158 @@ func main() { badSig[0] ^= 0xFF err = verifySignatureLocal(testPubKeyDER, messageHash[:], badSig) results.Add("Reject invalid signature", err != nil, "") + // SECTION 8: PCR Revocation & TEE Deactivation Security + fmt.Println("\n------------------------------------------") + fmt.Println("SECTION 8: PCR Revocation & TEE Deactivation") + fmt.Println("------------------------------------------") + + if registrationSuccess { + fmt.Println(" 🔒 Testing PCR revocation security...") + + // Step 1: Create test PCR for revocation testing + fmt.Println("\n Step 1: Create test PCR for revocation") + testPCR0 := make([]byte, 48) + testPCR1 := make([]byte, 48) + testPCR2 := make([]byte, 48) + rand.Read(testPCR0) + rand.Read(testPCR1) + rand.Read(testPCR2) + + testPCRHash, _ := callComputePCRHash(testPCR0, testPCR1, testPCR2) + fmt.Printf(" 📊 Test PCR Hash: 0x%s\n", hex.EncodeToString(testPCRHash[:])) + + // Approve the test PCR + txHash, err := callApprovePCR(account, testPCR0, testPCR1, testPCR2, "test-revoke", 0) + if err == nil { + waitForTx(txHash) + approved, _ := callIsPCRApproved(testPCRHash) + results.Add("Test PCR approved", approved, "") + } else { + results.Add("Approve test PCR", false, err.Error()) + } + + // Step 2: Revoke the test PCR + fmt.Println("\n Step 2: Revoke test PCR") + txHash, err = callRevokePCR(account, testPCRHash, 0) + if err != nil { + results.Add("Revoke test PCR", false, err.Error()) + } else { + waitForTx(txHash) + stillApproved, _ := callIsPCRApproved(testPCRHash) + results.Add("Test PCR no longer approved after revocation", !stillApproved, "") + fmt.Println(" ✅ Test PCR successfully revoked") + } + + // Step 3: Test activate/deactivate cycle with valid PCR + fmt.Println("\n Step 3: Test activate/deactivate cycle") + + // Deactivate our real TEE + txHash, err = callDeactivateTEE(account, registeredTEEId) + if err == nil { + waitForTx(txHash) + isActive, _ := callIsActive(registeredTEEId) + results.Add("TEE deactivated", !isActive, "") + } + + // Reactivate - should succeed since TEE's PCR is still valid + txHash, err = callActivateTEE(account, registeredTEEId) + if err == nil { + waitForTx(txHash) + isActive, _ := callIsActive(registeredTEEId) + results.Add("TEE reactivated with valid PCR", isActive, "") + fmt.Println(" ✅ Reactivation successful (PCR valid)") + } else { + results.Add("Reactivate TEE", false, err.Error()) + } + + // Step 4: Test grace period functionality + fmt.Println("\n Step 4: Test PCR grace period") + + pcrV1_0 := make([]byte, 48) + pcrV1_1 := make([]byte, 48) + pcrV1_2 := make([]byte, 48) + rand.Read(pcrV1_0) + rand.Read(pcrV1_1) + rand.Read(pcrV1_2) + + pcrV2_0 := make([]byte, 48) + pcrV2_1 := make([]byte, 48) + pcrV2_2 := make([]byte, 48) + rand.Read(pcrV2_0) + rand.Read(pcrV2_1) + rand.Read(pcrV2_2) + + pcrV1Hash, _ := callComputePCRHash(pcrV1_0, pcrV1_1, pcrV1_2) + pcrV2Hash, _ := callComputePCRHash(pcrV2_0, pcrV2_1, pcrV2_2) + + fmt.Printf(" 📊 PCR v1 Hash: 0x%s\n", hex.EncodeToString(pcrV1Hash[:])) + fmt.Printf(" 📊 PCR v2 Hash: 0x%s\n", hex.EncodeToString(pcrV2Hash[:])) + + // Approve v1 and v2 + txHash, _ = callApprovePCR(account, pcrV1_0, pcrV1_1, pcrV1_2, "v1-grace", 0) + waitForTx(txHash) + txHash, _ = callApprovePCR(account, pcrV2_0, pcrV2_1, pcrV2_2, "v2-grace", 0) + waitForTx(txHash) + + // Revoke v1 with 1 hour grace period + txHash, err = callRevokePCR(account, pcrV1Hash, 3600) + if err == nil { + waitForTx(txHash) - // SECTION 8: Utility Functions + // Both should be valid during grace period + v1Valid, _ := callIsPCRApproved(pcrV1Hash) + v2Valid, _ := callIsPCRApproved(pcrV2Hash) + + bothValid := v1Valid && v2Valid + results.Add("Both PCRs valid during grace period", bothValid, + fmt.Sprintf("v1=%v, v2=%v", v1Valid, v2Valid)) + + if bothValid { + fmt.Println(" ✅ Grace period working: both v1 and v2 valid") + } else { + fmt.Printf(" ⚠️ Grace period issue: v1=%v, v2=%v\n", v1Valid, v2Valid) + } + } else { + results.Add("Revoke PCR with grace period", false, err.Error()) + } + + // Step 5: Test duplicate PCR prevention + fmt.Println("\n Step 5: Test duplicate PCR prevention") + + dupPCR0 := make([]byte, 48) + dupPCR1 := make([]byte, 48) + dupPCR2 := make([]byte, 48) + rand.Read(dupPCR0) + rand.Read(dupPCR1) + rand.Read(dupPCR2) + + // Approve once + txHash, err = callApprovePCR(account, dupPCR0, dupPCR1, dupPCR2, "dup-test-1", 0) + if err == nil { + waitForTx(txHash) + } + + // Try to approve same PCRs again - should fail + txHash, err = callApprovePCR(account, dupPCR0, dupPCR1, dupPCR2, "dup-test-2", 0) + if err != nil { + results.Add("Duplicate PCR registration prevented", true, "") + fmt.Println(" ✅ Duplicate PCR rejected as expected") + } else { + // If tx was sent, wait for it - it should revert + success := waitForTx(txHash) + results.Add("Duplicate PCR registration prevented", !success, "") + if !success { + fmt.Println(" ✅ Duplicate PCR transaction reverted") + } else { + fmt.Println(" ⚠️ Duplicate PCR was accepted (should have failed)") + } + } + } else { + fmt.Println(" ⚠️ Skipping PCR revocation tests (no registered TEE)") + } + // SECTION 9: Utility Functions fmt.Println("\n------------------------------------------") - fmt.Println("SECTION 8: Utility Functions") + fmt.Println("SECTION 9: Utility Functions") fmt.Println("------------------------------------------") computedId, err := callComputeTEEId(testPubKeyDER) @@ -790,6 +938,14 @@ func loadPCRMeasurements() ([]byte, []byte, []byte, error) { return pcr0, pcr1, pcr2, nil } +func callRevokePCR(from string, pcrHash [32]byte, gracePeriod uint64) (string, error) { + bytes32Type, _ := abi.NewType("bytes32", "", nil) + uint256Type, _ := abi.NewType("uint256", "", nil) + args := abi.Arguments{{Type: bytes32Type}, {Type: uint256Type}} + encoded, _ := args.Pack(pcrHash, new(big.Int).SetUint64(gracePeriod)) + return sendTx(from, append(SEL_REVOKE_PCR, encoded...)) +} + // ============================================================================ // AWS NITRO ROOT CERTIFICATE // ============================================================================ @@ -923,19 +1079,18 @@ func callIsValidTEEType(typeId uint8) (bool, error) { // CONTRACT CALLS - PCR Management // ============================================================================ -func callApprovePCR(from string, pcr0, pcr1, pcr2 []byte, version string) (string, error) { +func callApprovePCR(from string, pcr0, pcr1, pcr2 []byte, version string, teeType uint8) (string, error) { tupleType, _ := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ {Name: "pcr0", Type: "bytes"}, {Name: "pcr1", Type: "bytes"}, {Name: "pcr2", Type: "bytes"}, }) stringType, _ := abi.NewType("string", "", nil) - bytes32Type, _ := abi.NewType("bytes32", "", nil) - uint256Type, _ := abi.NewType("uint256", "", nil) + uint8Type, _ := abi.NewType("uint8", "", nil) - args := abi.Arguments{{Type: tupleType}, {Type: stringType}, {Type: bytes32Type}, {Type: uint256Type}} + args := abi.Arguments{{Type: tupleType}, {Type: stringType}, {Type: uint8Type}} pcrs := struct{ Pcr0, Pcr1, Pcr2 []byte }{pcr0, pcr1, pcr2} - encoded, _ := args.Pack(pcrs, version, [32]byte{}, big.NewInt(0)) + encoded, _ := args.Pack(pcrs, version, teeType) return sendTx(from, append(SEL_APPROVE_PCR, encoded...)) } diff --git a/scripts/tee-mgmt-cli/cmd/pcr.go b/scripts/tee-mgmt-cli/cmd/pcr.go index 0946676a..9f06030d 100644 --- a/scripts/tee-mgmt-cli/cmd/pcr.go +++ b/scripts/tee-mgmt-cli/cmd/pcr.go @@ -34,38 +34,26 @@ var pcrListCmd = &cobra.Command{ var pcrApproveCmd = &cobra.Command{ Use: "approve", - Short: "Approve a set of PCR measurements (PCR0, PCR1, PCR2) for TEE attestation", + Short: "Approve a set of PCR measurements (PCR0, PCR1, PCR2) for a specific TEE type", RunE: func(cmd *cobra.Command, args []string) error { measurementsFile, _ := cmd.Flags().GetString("measurements-file") pcr0Hex, _ := cmd.Flags().GetString("pcr0") pcr1Hex, _ := cmd.Flags().GetString("pcr1") pcr2Hex, _ := cmd.Flags().GetString("pcr2") version, _ := cmd.Flags().GetString("version") - gracePeriodStr, _ := cmd.Flags().GetString("grace-period") - prevPCRStr, _ := cmd.Flags().GetString("previous-pcr") + teeType, _ := cmd.Flags().GetUint8("tee-type") pcr0, pcr1, pcr2 := registry.LoadPCRsFromArgs(measurementsFile, pcr0Hex, pcr1Hex, pcr2Hex) - gracePeriod := new(big.Int) - gracePeriod.SetString(gracePeriodStr, 10) - - var prevPCR [32]byte - if prevPCRStr != "" { - var err error - prevPCR, err = registry.ParseBytes32(prevPCRStr) - if err != nil { - return fmt.Errorf("invalid --previous-pcr: %w", err) - } - } - pcrHash, _ := client.ComputePCRHash(pcr0, pcr1, pcr2) fmt.Println("=== Approving PCR ===") fmt.Printf(" PCR Hash: 0x%s\n", hex.EncodeToString(pcrHash[:])) - fmt.Printf(" Version: %s\n\n", version) + fmt.Printf(" Version: %s\n", version) + fmt.Printf(" TEE Type: %d\n\n", teeType) account, _ := client.GetAccountAddress() - txHash, err := client.ApprovePCR(account, pcr0, pcr1, pcr2, version, prevPCR, gracePeriod) + txHash, err := client.ApprovePCR(account, pcr0, pcr1, pcr2, version, teeType) if err != nil { return fmt.Errorf("failed: %w", err) } @@ -77,17 +65,22 @@ var pcrApproveCmd = &cobra.Command{ var pcrRevokeCmd = &cobra.Command{ Use: "revoke ", - Short: "Revoke a previously approved PCR hash", + Short: "Revoke a previously approved PCR hash (immediately or with grace period)", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { pcrHash, err := registry.ParseBytes32(args[0]) if err != nil { return fmt.Errorf("invalid pcrHash: %w", err) } + teeType, _ := cmd.Flags().GetUint8("tee-type") + gracePeriodStr, _ := cmd.Flags().GetString("grace-period") + gracePeriod := new(big.Int) + gracePeriod.SetString(gracePeriodStr, 10) + account, _ := client.GetAccountAddress() - registry.Log("Revoking PCR: 0x%s", hex.EncodeToString(pcrHash[:])) - txHash, err := client.RevokePCR(account, pcrHash) + registry.Log("Revoking PCR: 0x%s (type: %d, grace period: %s seconds)", hex.EncodeToString(pcrHash[:]), teeType, gracePeriod.String()) + txHash, err := client.RevokePCR(account, pcrHash, teeType, gracePeriod) if err != nil { return fmt.Errorf("failed: %w", err) } @@ -99,19 +92,20 @@ var pcrRevokeCmd = &cobra.Command{ var pcrCheckCmd = &cobra.Command{ Use: "check ", - Short: "Check whether a PCR hash is currently approved", + Short: "Check whether a PCR hash is currently approved for a TEE type", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { pcrHash, err := registry.ParseBytes32(args[0]) if err != nil { return fmt.Errorf("invalid pcrHash: %w", err) } + teeType, _ := cmd.Flags().GetUint8("tee-type") - approved, _ := client.IsPCRApproved(pcrHash) + approved, _ := client.IsPCRApproved(teeType, pcrHash) if approved { - fmt.Printf("PCR 0x%s is APPROVED\n", hex.EncodeToString(pcrHash[:])) + fmt.Printf("PCR 0x%s is APPROVED for type %d\n", hex.EncodeToString(pcrHash[:]), teeType) } else { - fmt.Printf("PCR 0x%s is NOT approved\n", hex.EncodeToString(pcrHash[:])) + fmt.Printf("PCR 0x%s is NOT approved for type %d\n", hex.EncodeToString(pcrHash[:]), teeType) } return nil }, @@ -146,8 +140,12 @@ func addPCRFlags(cmd *cobra.Command) { func init() { addPCRFlags(pcrApproveCmd) pcrApproveCmd.Flags().StringP("version", "v", "v1.0.0", "Version label for this PCR set") - pcrApproveCmd.Flags().String("grace-period", "0", "Grace period in seconds before the previous PCR is revoked") - pcrApproveCmd.Flags().String("previous-pcr", "", "PCR hash being rotated out (bytes32 hex)") + pcrApproveCmd.Flags().Uint8("tee-type", 0, "TEE type ID this PCR is valid for") + + pcrRevokeCmd.Flags().Uint8("tee-type", 0, "TEE type ID the PCR is approved for") + pcrRevokeCmd.Flags().String("grace-period", "0", "Grace period in seconds before revocation takes effect (0 = immediate)") + + pcrCheckCmd.Flags().Uint8("tee-type", 0, "TEE type ID to check against") addPCRFlags(pcrComputeCmd) diff --git a/scripts/tee-mgmt-cli/cmd/tee.go b/scripts/tee-mgmt-cli/cmd/tee.go index 3385a846..9479b136 100644 --- a/scripts/tee-mgmt-cli/cmd/tee.go +++ b/scripts/tee-mgmt-cli/cmd/tee.go @@ -18,18 +18,21 @@ var teeCmd = &cobra.Command{ var teeListCmd = &cobra.Command{ Use: "list", - Short: "List all active TEEs in the registry", + Short: "List activated TEEs for a given type", RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("=== Active TEEs in Registry ===") + teeType, _ := cmd.Flags().GetUint8("tee-type") + + fmt.Println("=== Activated TEEs in Registry ===") fmt.Printf("Registry: %s\n", client.RegistryAddress) - fmt.Printf("RPC: %s\n\n", client.RPCURL) + fmt.Printf("RPC: %s\n", client.RPCURL) + fmt.Printf("Type: %d\n\n", teeType) - tees, err := client.GetActiveTEEs() + tees, err := client.GetActivatedTEEs(teeType) if err != nil { - return fmt.Errorf("failed to get active TEEs: %w", err) + return fmt.Errorf("failed to get activated TEEs: %w", err) } - fmt.Printf("Found %d active TEE(s)\n\n", len(tees)) + fmt.Printf("Found %d activated TEE(s)\n\n", len(tees)) for i, teeId := range tees { fmt.Printf(" [%d] 0x%s\n", i+1, teeId) } @@ -64,17 +67,17 @@ var teeShowCmd = &cobra.Command{ fmt.Printf(" Last Updated: %s UTC\n", info.LastUpdatedAt.UTC().Format("2006-01-02 15:04:05")) fmt.Println("\n --- Public Key ---") - if pubKey, err := client.GetPublicKey(teeId); err == nil && len(pubKey) > 0 { - fmt.Printf(" Size: %d bytes\n", len(pubKey)) - fmt.Printf(" Hex: %s...\n", registry.Truncate(hex.EncodeToString(pubKey), 64)) + if len(info.PublicKey) > 0 { + fmt.Printf(" Size: %d bytes\n", len(info.PublicKey)) + fmt.Printf(" Hex: %s...\n", registry.Truncate(hex.EncodeToString(info.PublicKey), 64)) } else { fmt.Println(" Not available") } fmt.Println("\n --- TLS Certificate ---") - if tlsCert, err := client.GetTLSCertificate(teeId); err == nil && len(tlsCert) > 0 { - fmt.Printf(" Size: %d bytes\n", len(tlsCert)) - fmt.Printf(" Hash: 0x%s\n", hex.EncodeToString(crypto.Keccak256(tlsCert))) + if len(info.TLSCertificate) > 0 { + fmt.Printf(" Size: %d bytes\n", len(info.TLSCertificate)) + fmt.Printf(" Hash: 0x%s\n", hex.EncodeToString(crypto.Keccak256(info.TLSCertificate))) } else { fmt.Println(" Not available") } @@ -137,7 +140,7 @@ var teeRegisterCmd = &cobra.Command{ fmt.Printf(" TLS Cert: %d bytes\n", len(tlsCert)) expectedId := crypto.Keccak256Hash(signingKey) - if active, _ := client.IsActive(expectedId); active { + if info, err := client.GetTEE(expectedId); err == nil && info != nil { fmt.Printf("\nTEE already registered: 0x%s\n", hex.EncodeToString(expectedId[:])) return nil } @@ -203,7 +206,78 @@ var teeActivateCmd = &cobra.Command{ }, } +var teeLiveCmd = &cobra.Command{ + Use: "live", + Short: "List live TEEs (active + valid PCR + fresh heartbeat)", + RunE: func(cmd *cobra.Command, args []string) error { + teeType, _ := cmd.Flags().GetUint8("tee-type") + + fmt.Println("=== Live TEEs ===") + fmt.Printf("Registry: %s\n", client.RegistryAddress) + fmt.Printf("Type: %d\n\n", teeType) + + tees, err := client.GetActivatedTEEs(teeType) + if err != nil { + return fmt.Errorf("failed to get activated TEEs: %w", err) + } + + liveCount := 0 + for _, teeIdHex := range tees { + teeId, err := registry.ParseBytes32(teeIdHex) + if err != nil { + continue + } + live, err := client.IsLive(teeId) + if err != nil || !live { + continue + } + + liveCount++ + info, err := client.GetTEE(teeId) + if err != nil { + fmt.Printf(" [%d] 0x%s (could not fetch details)\n", liveCount, teeIdHex) + continue + } + fmt.Printf(" [%d] 0x%s\n", liveCount, teeIdHex) + fmt.Printf(" Endpoint: %s\n", info.Endpoint) + fmt.Printf(" Type: %d (%s)\n", info.TEEType, registry.GetTEETypeName(info.TEEType)) + fmt.Printf(" Last Updated: %s UTC\n\n", info.LastUpdatedAt.UTC().Format("2006-01-02 15:04:05")) + } + + if liveCount == 0 { + fmt.Println(" No live TEEs found") + } else { + fmt.Printf("Total: %d live / %d activated\n", liveCount, len(tees)) + } + return nil + }, +} + +var teeRemoveCmd = &cobra.Command{ + Use: "remove ", + Short: "Permanently remove a TEE from the registry (owner or admin)", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + teeId, err := registry.ParseBytes32(args[0]) + if err != nil { + return fmt.Errorf("invalid TEE ID: %w", err) + } + account, _ := client.GetAccountAddress() + + registry.Log("Removing TEE: 0x%s", hex.EncodeToString(teeId[:])) + txHash, err := client.RemoveTEE(account, teeId) + if err != nil { + return fmt.Errorf("failed: %w", err) + } + fmt.Printf("TX: %s\n", txHash) + registry.PrintTxResult(client.WaitForTx(txHash), "TEE removed") + return nil + }, +} + func init() { + teeListCmd.Flags().Uint8("tee-type", 0, "TEE type ID to list") + teeRegisterCmd.Flags().String("enclave-host", "", "Enclave hostname or IP (required)") teeRegisterCmd.Flags().String("enclave-port", "443", "Enclave TLS port for certificate fetch") teeRegisterCmd.Flags().String("payment-address", "", "Payment address for the TEE (defaults to sender)") @@ -211,6 +285,8 @@ func init() { teeRegisterCmd.Flags().Uint8("tee-type", 0, "TEE type ID (e.g. 0=LLMProxy, 1=Validator)") teeRegisterCmd.MarkFlagRequired("enclave-host") - teeCmd.AddCommand(teeListCmd, teeShowCmd, teeRegisterCmd, teeDeactivateCmd, teeActivateCmd) + teeLiveCmd.Flags().Uint8("tee-type", 0, "TEE type ID to list") + + teeCmd.AddCommand(teeListCmd, teeShowCmd, teeLiveCmd, teeRegisterCmd, teeDeactivateCmd, teeActivateCmd, teeRemoveCmd) rootCmd.AddCommand(teeCmd) } diff --git a/scripts/tee-mgmt-cli/registry/client.go b/scripts/tee-mgmt-cli/registry/client.go index 12ad1b4e..235a0abe 100644 --- a/scripts/tee-mgmt-cli/registry/client.go +++ b/scripts/tee-mgmt-cli/registry/client.go @@ -38,20 +38,19 @@ var ( selAddTEEType = crypto.Keccak256([]byte("addTEEType(uint8,string)"))[:4] selDeactivateTEETyp = crypto.Keccak256([]byte("deactivateTEEType(uint8)"))[:4] selIsValidType = crypto.Keccak256([]byte("isValidTEEType(uint8)"))[:4] - selApprovePCR = crypto.Keccak256([]byte("approvePCR((bytes,bytes,bytes),string,bytes32,uint256)"))[:4] - selRevokePCR = crypto.Keccak256([]byte("revokePCR(bytes32)"))[:4] - selIsPCRApproved = crypto.Keccak256([]byte("isPCRApproved(bytes32)"))[:4] + selApprovePCR = crypto.Keccak256([]byte("approvePCR((bytes,bytes,bytes),string,uint8)"))[:4] + selRevokePCR = crypto.Keccak256([]byte("revokePCR(bytes32,uint8,uint256)"))[:4] + selIsPCRApproved = crypto.Keccak256([]byte("isPCRApproved(uint8,bytes32)"))[:4] selComputePCRHash = crypto.Keccak256([]byte("computePCRHash((bytes,bytes,bytes))"))[:4] selGetActivePCRs = crypto.Keccak256([]byte("getActivePCRs()"))[:4] selSetAWSRootCert = crypto.Keccak256([]byte("setAWSRootCertificate(bytes)"))[:4] selRegisterTEE = crypto.Keccak256([]byte("registerTEEWithAttestation(bytes,bytes,bytes,address,string,uint8)"))[:4] selDeactivateTEE = crypto.Keccak256([]byte("deactivateTEE(bytes32)"))[:4] selActivateTEE = crypto.Keccak256([]byte("activateTEE(bytes32)"))[:4] - selGetActiveTEEs = crypto.Keccak256([]byte("getActiveTEEs()"))[:4] + selRemoveTEE = crypto.Keccak256([]byte("removeTEE(bytes32)"))[:4] + selGetActivatedTEEs = crypto.Keccak256([]byte("getActivatedTEEs(uint8)"))[:4] selGetTEE = crypto.Keccak256([]byte("getTEE(bytes32)"))[:4] - selGetPublicKey = crypto.Keccak256([]byte("getPublicKey(bytes32)"))[:4] - selGetTLSCert = crypto.Keccak256([]byte("getTLSCertificate(bytes32)"))[:4] - selIsActive = crypto.Keccak256([]byte("isActive(bytes32)"))[:4] + selIsLive = crypto.Keccak256([]byte("isLive(bytes32)"))[:4] ) // Structs @@ -60,6 +59,8 @@ type TEEInfo struct { Owner common.Address PaymentAddress common.Address Endpoint string + PublicKey []byte + TLSCertificate []byte PCRHash [32]byte TEEType uint8 IsActive bool @@ -131,8 +132,10 @@ func (c *Client) getFirstAccount() (string, error) { // TEE Calls -func (c *Client) GetActiveTEEs() ([]string, error) { - result, err := c.ethCall(selGetActiveTEEs) +func (c *Client) GetActivatedTEEs(teeType uint8) ([]string, error) { + u8T, _ := abi.NewType("uint8", "", nil) + encoded, _ := abi.Arguments{{Type: u8T}}.Pack(teeType) + result, err := c.ethCall(append(selGetActivatedTEEs, encoded...)) if err != nil { return nil, err } @@ -161,6 +164,8 @@ func (c *Client) GetTEE(teeId [32]byte) (*TEEInfo, error) { {Name: "owner", Type: "address"}, {Name: "paymentAddress", Type: "address"}, {Name: "endpoint", Type: "string"}, + {Name: "publicKey", Type: "bytes"}, + {Name: "tlsCertificate", Type: "bytes"}, {Name: "pcrHash", Type: "bytes32"}, {Name: "teeType", Type: "uint8"}, {Name: "isActive", Type: "bool"}, @@ -187,6 +192,8 @@ func (c *Client) GetTEE(teeId [32]byte) (*TEEInfo, error) { Owner common.Address `json:"owner"` PaymentAddress common.Address `json:"paymentAddress"` Endpoint string `json:"endpoint"` + PublicKey []byte `json:"publicKey"` + TlsCertificate []byte `json:"tlsCertificate"` PcrHash [32]byte `json:"pcrHash"` TeeType uint8 `json:"teeType"` IsActive bool `json:"isActive"` @@ -198,6 +205,8 @@ func (c *Client) GetTEE(teeId [32]byte) (*TEEInfo, error) { Owner: s.Owner, PaymentAddress: s.PaymentAddress, Endpoint: s.Endpoint, + PublicKey: s.PublicKey, + TLSCertificate: s.TlsCertificate, PCRHash: s.PcrHash, TEEType: s.TeeType, IsActive: s.IsActive, @@ -206,27 +215,6 @@ func (c *Client) GetTEE(teeId [32]byte) (*TEEInfo, error) { }, nil } -func (c *Client) GetPublicKey(teeId [32]byte) ([]byte, error) { - result, err := c.ethCall(encodeBytes32(selGetPublicKey, teeId)) - if err != nil { - return nil, err - } - return decodeDynamicBytes(result) -} - -func (c *Client) GetTLSCertificate(teeId [32]byte) ([]byte, error) { - result, err := c.ethCall(encodeBytes32(selGetTLSCert, teeId)) - if err != nil { - return nil, err - } - return decodeDynamicBytes(result) -} - -func (c *Client) IsActive(teeId [32]byte) (bool, error) { - result, err := c.ethCall(encodeBytes32(selIsActive, teeId)) - return len(result) >= 32 && result[31] == 1, err -} - func (c *Client) RegisterTEE(from string, attestation, signingKey, tlsCert []byte, paymentAddr, endpoint string, teeType uint8) (string, error) { bytesT, _ := abi.NewType("bytes", "", nil) addrT, _ := abi.NewType("address", "", nil) @@ -246,6 +234,15 @@ func (c *Client) ActivateTEE(from string, teeId [32]byte) (string, error) { return c.sendTx(from, encodeBytes32(selActivateTEE, teeId)) } +func (c *Client) RemoveTEE(from string, teeId [32]byte) (string, error) { + return c.sendTx(from, encodeBytes32(selRemoveTEE, teeId)) +} + +func (c *Client) IsLive(teeId [32]byte) (bool, error) { + result, err := c.ethCall(encodeBytes32(selIsLive, teeId)) + return len(result) >= 32 && result[31] == 1, err +} + // PCR Calls func (c *Client) GetActivePCRs() ([]string, error) { @@ -270,25 +267,32 @@ func (c *Client) ComputePCRHash(pcr0, pcr1, pcr2 []byte) ([32]byte, error) { return hash, err } -func (c *Client) ApprovePCR(from string, pcr0, pcr1, pcr2 []byte, version string, prevPCR [32]byte, grace *big.Int) (string, error) { +func (c *Client) ApprovePCR(from string, pcr0, pcr1, pcr2 []byte, version string, teeType uint8) (string, error) { tupleT, _ := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ {Name: "pcr0", Type: "bytes"}, {Name: "pcr1", Type: "bytes"}, {Name: "pcr2", Type: "bytes"}, }) strT, _ := abi.NewType("string", "", nil) - b32T, _ := abi.NewType("bytes32", "", nil) - u256T, _ := abi.NewType("uint256", "", nil) + u8T, _ := abi.NewType("uint8", "", nil) - args := abi.Arguments{{Type: tupleT}, {Type: strT}, {Type: b32T}, {Type: u256T}} - encoded, _ := args.Pack(struct{ Pcr0, Pcr1, Pcr2 []byte }{pcr0, pcr1, pcr2}, version, prevPCR, grace) + args := abi.Arguments{{Type: tupleT}, {Type: strT}, {Type: u8T}} + encoded, _ := args.Pack(struct{ Pcr0, Pcr1, Pcr2 []byte }{pcr0, pcr1, pcr2}, version, teeType) return c.sendTx(from, append(selApprovePCR, encoded...)) } -func (c *Client) RevokePCR(from string, pcrHash [32]byte) (string, error) { - return c.sendTx(from, encodeBytes32(selRevokePCR, pcrHash)) +func (c *Client) RevokePCR(from string, pcrHash [32]byte, teeType uint8, gracePeriod *big.Int) (string, error) { + b32T, _ := abi.NewType("bytes32", "", nil) + u8T, _ := abi.NewType("uint8", "", nil) + u256T, _ := abi.NewType("uint256", "", nil) + args := abi.Arguments{{Type: b32T}, {Type: u8T}, {Type: u256T}} + encoded, _ := args.Pack(pcrHash, teeType, gracePeriod) + return c.sendTx(from, append(selRevokePCR, encoded...)) } -func (c *Client) IsPCRApproved(pcrHash [32]byte) (bool, error) { - result, err := c.ethCall(encodeBytes32(selIsPCRApproved, pcrHash)) +func (c *Client) IsPCRApproved(teeType uint8, pcrHash [32]byte) (bool, error) { + u8T, _ := abi.NewType("uint8", "", nil) + b32T, _ := abi.NewType("bytes32", "", nil) + encoded, _ := abi.Arguments{{Type: u8T}, {Type: b32T}}.Pack(teeType, pcrHash) + result, err := c.ethCall(append(selIsPCRApproved, encoded...)) return len(result) >= 32 && result[31] == 1, err } diff --git a/tests/solidity/suites/tee/contracts/MockTEERegistry.sol b/tests/solidity/suites/tee/contracts/MockTEERegistry.sol index 9bb0fa13..c77a37c3 100644 --- a/tests/solidity/suites/tee/contracts/MockTEERegistry.sol +++ b/tests/solidity/suites/tee/contracts/MockTEERegistry.sol @@ -39,6 +39,10 @@ contract MockTEERegistry is TEERegistry { lastUpdatedAt: block.timestamp }); + // Add to indexes (matching registerTEE behavior) + _teesByType[teeType].push(teeId); + _teesByOwner[msg.sender].push(teeId); + emit TEERegistered(teeId, msg.sender, teeType); } } diff --git a/tests/solidity/suites/tee/contracts/TEETestHelper.sol b/tests/solidity/suites/tee/contracts/TEETestHelper.sol index c752e9b4..d9d170e3 100644 --- a/tests/solidity/suites/tee/contracts/TEETestHelper.sol +++ b/tests/solidity/suites/tee/contracts/TEETestHelper.sol @@ -37,18 +37,17 @@ contract TEETestHelper { function approvePCR( TEERegistry.PCRMeasurements calldata pcrs, string calldata version, - bytes32 previousPcrHash, - uint256 gracePeriod + uint8 teeType ) external { - registry.approvePCR(pcrs, version, previousPcrHash, gracePeriod); + registry.approvePCR(pcrs, version, teeType); } - function revokePCR(bytes32 pcrHash) external { - registry.revokePCR(pcrHash); + function revokePCR(bytes32 pcrHash, uint8 teeType, uint256 gracePeriod) external { + registry.revokePCR(pcrHash, teeType, gracePeriod); } - function isPCRApproved(bytes32 pcrHash) external view returns (bool) { - return registry.isPCRApproved(pcrHash); + function isPCRApproved(uint8 teeType, bytes32 pcrHash) external view returns (bool) { + return registry.isPCRApproved(teeType, pcrHash); } function computePCRHash(TEERegistry.PCRMeasurements calldata pcrs) external pure returns (bytes32) { @@ -111,6 +110,10 @@ contract TEETestHelper { registry.activateTEE(teeId); } + function removeTEE(bytes32 teeId) external { + registry.removeTEE(teeId); + } + // ============ Verification Wrappers ============ function computeMessageHash( @@ -127,8 +130,12 @@ contract TEETestHelper { return registry.getTEE(teeId); } - function getActiveTEEs() external view returns (bytes32[] memory) { - return registry.getActiveTEEs(); + function getActivatedTEEs(uint8 teeType) external view returns (bytes32[] memory) { + return registry.getActivatedTEEs(teeType); + } + + function getLiveTEEs(uint8 teeType) external view returns (TEERegistry.TEEInfo[] memory) { + return registry.getLiveTEEs(teeType); } function getTEEsByType(uint8 teeType) external view returns (bytes32[] memory) { @@ -139,14 +146,6 @@ contract TEETestHelper { return registry.getTEEsByOwner(owner); } - function getPublicKey(bytes32 teeId) external view returns (bytes memory) { - return registry.getPublicKey(teeId); - } - - function isActive(bytes32 teeId) external view returns (bool) { - return registry.isActive(teeId); - } - function computeTEEId(bytes calldata publicKey) external pure returns (bytes32) { return keccak256(publicKey); } diff --git a/tests/solidity/suites/tee/test/lifecycle.js b/tests/solidity/suites/tee/test/lifecycle.js index acee0e03..49bd92ed 100644 --- a/tests/solidity/suites/tee/test/lifecycle.js +++ b/tests/solidity/suites/tee/test/lifecycle.js @@ -27,9 +27,17 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { const TEE_OPERATOR_ROLE = await registry.TEE_OPERATOR() await registry.grantRole(TEE_OPERATOR_ROLE, teeOperator) - // Add TEE type + // Add TEE type and approve PCR measurements await registry.addTEEType(TEE_TYPE_NITRO, 'AWS Nitro') + // Approve the PCR so activateTEE() passes _requirePCRValidForTEE + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x01).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x02).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x03).toString('hex') + } + await registry.approvePCR(pcrs, 'v1.0.0', TEE_TYPE_NITRO) + // Generate test keys const keyPair1 = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, @@ -64,6 +72,12 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { return teeId } + // Helper: check active status via getTEE + async function isActive(teeId) { + const tee = await registry.getTEE(teeId) + return tee.active + } + // ============ deactivateTEE Tests ============ describe('deactivateTEE', function () { @@ -83,17 +97,17 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { return ev.teeId === localTeeId }) - expect(await registry.isActive(localTeeId)).to.be.false + expect(await isActive(localTeeId)).to.be.false console.log('✓ Owner deactivated TEE') }) - it('should remove TEE from active list after deactivation', async function () { - const activeTEEs = await registry.getActiveTEEs() - const found = activeTEEs.some(id => id === localTeeId) + it('should remove TEE from activated list after deactivation', async function () { + const activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + const found = activatedTEEs.some(id => id === localTeeId) expect(found).to.be.false - console.log('✓ TEE removed from active list') + console.log('✓ TEE removed from activated list') }) it('should be a no-op when deactivating already inactive TEE', async function () { @@ -109,7 +123,7 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { it('should allow admin to deactivate any TEE', async function () { // Re-activate first await registry.activateTEE(localTeeId, { from: teeOperator }) - expect(await registry.isActive(localTeeId)).to.be.true + expect(await isActive(localTeeId)).to.be.true // Admin (owner/accounts[0]) deactivates const result = await registry.deactivateTEE(localTeeId, { from: owner }) @@ -118,7 +132,7 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { return ev.teeId === localTeeId }) - expect(await registry.isActive(localTeeId)).to.be.false + expect(await isActive(localTeeId)).to.be.false console.log('✓ Admin deactivated TEE') }) @@ -160,7 +174,7 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { it('should allow owner to activate their deactivated TEE', async function () { // TEE starts inactive from mock registration - expect(await registry.isActive(localTeeId)).to.be.false + expect(await isActive(localTeeId)).to.be.false const result = await registry.activateTEE(localTeeId, { from: teeOperator }) @@ -168,17 +182,17 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { return ev.teeId === localTeeId }) - expect(await registry.isActive(localTeeId)).to.be.true + expect(await isActive(localTeeId)).to.be.true console.log('✓ Owner activated TEE') }) - it('should add TEE back to active list after activation', async function () { - const activeTEEs = await registry.getActiveTEEs() - const found = activeTEEs.some(id => id === localTeeId) + it('should add TEE back to activated list after activation', async function () { + const activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + const found = activatedTEEs.some(id => id === localTeeId) expect(found).to.be.true - console.log('✓ TEE added to active list') + console.log('✓ TEE added to activated list') }) it('should be a no-op when activating already active TEE', async function () { @@ -194,7 +208,7 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { it('should allow admin to activate any TEE', async function () { // Deactivate first await registry.deactivateTEE(localTeeId, { from: teeOperator }) - expect(await registry.isActive(localTeeId)).to.be.false + expect(await isActive(localTeeId)).to.be.false // Admin (owner/accounts[0]) activates const result = await registry.activateTEE(localTeeId, { from: owner }) @@ -203,7 +217,7 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { return ev.teeId === localTeeId }) - expect(await registry.isActive(localTeeId)).to.be.true + expect(await isActive(localTeeId)).to.be.true console.log('✓ Admin activated TEE') }) @@ -230,6 +244,216 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { }) }) + // ============ PCR Enforcement Tests ============ + + describe('PCR enforcement on activateTEE', function () { + let pcrTeeId, pcrPublicKey + + before(async function () { + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + pcrPublicKey = '0x' + keyPair.publicKey.toString('hex') + }) + + it('should revert activateTEE when PCR is immediately revoked', async function () { + // Approve a fresh PCR + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0xA1).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0xA2).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0xA3).toString('hex') + } + await registry.approvePCR(pcrs, 'v-revoke-activate', TEE_TYPE_NITRO) + const pcrHash = await registry.computePCRHash(pcrs) + + // Register TEE (inactive) with this PCR + await registry.registerTEEForTesting( + pcrPublicKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, pcrHash, { from: teeOperator } + ) + pcrTeeId = await registry.computeTEEId(pcrPublicKey) + + // Revoke the PCR immediately + await registry.revokePCR(pcrHash, TEE_TYPE_NITRO, 0) + expect(await registry.isPCRApproved(TEE_TYPE_NITRO, pcrHash)).to.be.false + + // activateTEE should revert with PCRNotApproved + await truffleAssert.reverts( + registry.activateTEE(pcrTeeId, { from: teeOperator }) + ) + + expect(await isActive(pcrTeeId)).to.be.false + + console.log('✓ activateTEE reverts when PCR is revoked') + }) + + it('should revert activateTEE when PCR grace period has expired', async function () { + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0xB1).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0xB2).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0xB3).toString('hex') + } + await registry.approvePCR(pcrs, 'v-expire-activate', TEE_TYPE_NITRO) + const pcrHash = await registry.computePCRHash(pcrs) + + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + + // Register TEE (inactive) with this PCR + await registry.registerTEEForTesting( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, pcrHash, { from: teeOperator } + ) + const teeId = await registry.computeTEEId(pubKey) + + // Revoke with grace period of 1 second + await registry.revokePCR(pcrHash, TEE_TYPE_NITRO, 1) + + // Still valid during grace period + expect(await registry.isPCRApproved(TEE_TYPE_NITRO, pcrHash)).to.be.true + + // Mine a block to advance timestamp past the grace period + await registry.setAWSRootCertificate('0x01') + + // Now expired + expect(await registry.isPCRApproved(TEE_TYPE_NITRO, pcrHash)).to.be.false + + // activateTEE should revert with PCRExpired + await truffleAssert.reverts( + registry.activateTEE(teeId, { from: teeOperator }) + ) + + expect(await isActive(teeId)).to.be.false + + console.log('✓ activateTEE reverts when PCR grace period expired') + }) + + it('should revert activateTEE when PCR type does not match TEE type', async function () { + // Add a second TEE type + const TEE_TYPE_OTHER = 3 + await registry.addTEEType(TEE_TYPE_OTHER, 'Other TEE') + + // Approve a PCR only for TEE_TYPE_OTHER + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0xC1).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0xC2).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0xC3).toString('hex') + } + await registry.approvePCR(pcrs, 'v-type-mismatch', TEE_TYPE_OTHER) + const pcrHash = await registry.computePCRHash(pcrs) + + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + + // Register TEE as TEE_TYPE_NITRO but with a PCR approved for TEE_TYPE_OTHER + await registry.registerTEEForTesting( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, pcrHash, { from: teeOperator } + ) + const teeId = await registry.computeTEEId(pubKey) + + // activateTEE should revert + await truffleAssert.reverts( + registry.activateTEE(teeId, { from: teeOperator }) + ) + + expect(await isActive(teeId)).to.be.false + + console.log('✓ activateTEE reverts on PCR type mismatch') + }) + }) + + describe('PCR enforcement on heartbeat', function () { + it('should revert heartbeat when PCR is revoked after activation', async function () { + // Approve a fresh PCR + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0xD1).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0xD2).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0xD3).toString('hex') + } + await registry.approvePCR(pcrs, 'v-heartbeat-revoke', TEE_TYPE_NITRO) + const pcrHash = await registry.computePCRHash(pcrs) + + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + + // Register and activate TEE with valid PCR + await registry.registerTEEForTesting( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, pcrHash, { from: teeOperator } + ) + const teeId = await registry.computeTEEId(pubKey) + await registry.activateTEE(teeId, { from: teeOperator }) + expect(await isActive(teeId)).to.be.true + + // Now revoke the PCR + await registry.revokePCR(pcrHash, TEE_TYPE_NITRO, 0) + expect(await registry.isPCRApproved(TEE_TYPE_NITRO, pcrHash)).to.be.false + + // TEE is still marked active in storage (lazy enforcement) + expect(await isActive(teeId)).to.be.true + + // heartbeat should revert at _requirePCRValidForTEE before reaching signature check + const timestamp = Math.floor(Date.now() / 1000) + const dummySignature = '0x' + Buffer.alloc(256, 0xFF).toString('hex') + + await truffleAssert.reverts( + registry.heartbeat(teeId, timestamp, dummySignature) + ) + + console.log('✓ heartbeat reverts when PCR is revoked (lazy enforcement)') + }) + + it('should revert heartbeat when PCR grace period has expired', async function () { + // Approve a fresh PCR + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0xE1).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0xE2).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0xE3).toString('hex') + } + await registry.approvePCR(pcrs, 'v-heartbeat-expire', TEE_TYPE_NITRO) + const pcrHash = await registry.computePCRHash(pcrs) + + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + + // Register and activate + await registry.registerTEEForTesting( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, pcrHash, { from: teeOperator } + ) + const teeId = await registry.computeTEEId(pubKey) + await registry.activateTEE(teeId, { from: teeOperator }) + expect(await isActive(teeId)).to.be.true + + // Revoke with 1-second grace period + await registry.revokePCR(pcrHash, TEE_TYPE_NITRO, 1) + + // Mine a block to advance past grace period + await registry.setAWSRootCertificate('0x02') + + expect(await registry.isPCRApproved(TEE_TYPE_NITRO, pcrHash)).to.be.false + + // heartbeat should revert with PCRExpired + const timestamp = Math.floor(Date.now() / 1000) + const dummySignature = '0x' + Buffer.alloc(256, 0xFF).toString('hex') + + await truffleAssert.reverts( + registry.heartbeat(teeId, timestamp, dummySignature) + ) + + console.log('✓ heartbeat reverts when PCR grace period expired') + }) + }) + // ============ Query Function Tests ============ describe('Query functions', function () { @@ -251,106 +475,234 @@ contract('TEERegistry Lifecycle & Queries', function (accounts) { } }) - it('should return correct public key for registered TEE', async function () { - const key = await registry.getPublicKey(teeId1) - expect(key).to.equal(publicKey1) + it('should return correct public key via getTEE', async function () { + const tee = await registry.getTEE(teeId1) + expect(tee.publicKey).to.equal(publicKey1) - console.log('✓ getPublicKey returns correct key') + console.log('✓ getTEE returns correct public key') }) - it('should revert getPublicKey for non-existent TEE', async function () { + it('should revert getTEE for non-existent TEE', async function () { const fakeTeeId = web3.utils.keccak256('0xNONEXISTENT') await truffleAssert.reverts( - registry.getPublicKey(fakeTeeId) + registry.getTEE(fakeTeeId) ) - console.log('✓ getPublicKey reverts for non-existent TEE') - }) - - it('should return correct TLS certificate for registered TEE', async function () { - const cert = await registry.getTLSCertificate(teeId1) - expect(cert).to.equal(tlsCert1) - - console.log('✓ getTLSCertificate returns correct cert') + console.log('✓ getTEE reverts for non-existent TEE') }) - it('should revert getTLSCertificate for non-existent TEE', async function () { - const fakeTeeId = web3.utils.keccak256('0xNONEXISTENT') + it('should return correct TLS certificate via getTEE', async function () { + const tee = await registry.getTEE(teeId1) + expect(tee.tlsCertificate).to.equal(tlsCert1) - await truffleAssert.reverts( - registry.getTLSCertificate(fakeTeeId) - ) - - console.log('✓ getTLSCertificate reverts for non-existent TEE') + console.log('✓ getTEE returns correct TLS cert') }) - it('should return correct payment address for registered TEE', async function () { - const addr = await registry.getPaymentAddress(teeId1) - expect(addr).to.equal(user1) + it('should return correct payment address via getTEE', async function () { + const tee = await registry.getTEE(teeId1) + expect(tee.paymentAddress).to.equal(user1) - console.log('✓ getPaymentAddress returns correct address') + console.log('✓ getTEE returns correct payment address') }) - it('should revert getPaymentAddress for non-existent TEE', async function () { - const fakeTeeId = web3.utils.keccak256('0xNONEXISTENT') - - await truffleAssert.reverts( - registry.getPaymentAddress(fakeTeeId) - ) - - console.log('✓ getPaymentAddress reverts for non-existent TEE') - }) - - it('should return correct active TEE list', async function () { - const activeTEEs = await registry.getActiveTEEs() - expect(activeTEEs.length).to.be.greaterThan(0) + it('should return correct activated TEE list', async function () { + const activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + expect(activatedTEEs.length).to.be.greaterThan(0) // Both TEEs should be in the list - expect(activeTEEs.some(id => id === teeId1)).to.be.true - expect(activeTEEs.some(id => id === teeId2)).to.be.true + expect(activatedTEEs.some(id => id === teeId1)).to.be.true + expect(activatedTEEs.some(id => id === teeId2)).to.be.true - console.log('✓ getActiveTEEs returns correct list') + console.log('✓ getActivatedTEEs returns correct list') }) - it('should update active TEE list after deactivate and activate', async function () { + it('should update activated TEE list after deactivate and activate', async function () { // Deactivate teeId2 await registry.deactivateTEE(teeId2, { from: teeOperator }) - let activeTEEs = await registry.getActiveTEEs() - expect(activeTEEs.some(id => id === teeId2)).to.be.false - expect(activeTEEs.some(id => id === teeId1)).to.be.true + let activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + expect(activatedTEEs.some(id => id === teeId2)).to.be.false + expect(activatedTEEs.some(id => id === teeId1)).to.be.true // Re-activate teeId2 await registry.activateTEE(teeId2, { from: teeOperator }) - activeTEEs = await registry.getActiveTEEs() - expect(activeTEEs.some(id => id === teeId2)).to.be.true + activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + expect(activatedTEEs.some(id => id === teeId2)).to.be.true - console.log('✓ getActiveTEEs updates after deactivate/activate') + console.log('✓ getActivatedTEEs updates after deactivate/activate') }) - it('should return true for active TEE via isActive', async function () { - expect(await registry.isActive(teeId1)).to.be.true + it('should return active status via getTEE', async function () { + const tee = await registry.getTEE(teeId1) + expect(tee.active).to.be.true - console.log('✓ isActive returns true for active TEE') + console.log('✓ getTEE returns active=true for active TEE') }) - it('should return false for inactive TEE via isActive', async function () { + it('should return inactive status via getTEE after deactivation', async function () { await registry.deactivateTEE(teeId1, { from: teeOperator }) - expect(await registry.isActive(teeId1)).to.be.false + const tee = await registry.getTEE(teeId1) + expect(tee.active).to.be.false // Re-activate for subsequent tests await registry.activateTEE(teeId1, { from: teeOperator }) - console.log('✓ isActive returns false for inactive TEE') + console.log('✓ getTEE returns active=false for inactive TEE') }) - it('should return false for non-existent TEE via isActive', async function () { - const fakeTeeId = web3.utils.keccak256('0xNONEXISTENT') - expect(await registry.isActive(fakeTeeId)).to.be.false + it('should return all TEEs by type including inactive', async function () { + const allTEEs = await registry.getTEEsByType(TEE_TYPE_NITRO) + // Should include all registered TEEs (active and inactive from PCR tests) + expect(allTEEs.length).to.be.greaterThanOrEqual(2) + expect(allTEEs.some(id => id === teeId1)).to.be.true + expect(allTEEs.some(id => id === teeId2)).to.be.true + + console.log('✓ getTEEsByType returns all TEEs including inactive') + }) + + it('should return live TEEs filtered by heartbeat and PCR', async function () { + const liveTEEs = await registry.getLiveTEEs(TEE_TYPE_NITRO) + // liveTEEs returns TEEInfo structs, check they have valid fields + for (let i = 0; i < liveTEEs.length; i++) { + expect(liveTEEs[i].active).to.be.true + expect(Number(liveTEEs[i].registeredAt)).to.be.greaterThan(0) + } + + console.log('Live TEEs count:', liveTEEs.length) + console.log('✓ getLiveTEEs returns filtered results') + }) + + it('should return empty from getLiveTEEs for unused type', async function () { + const liveTEEs = await registry.getLiveTEEs(50) + expect(liveTEEs.length).to.equal(0) + + console.log('✓ getLiveTEEs returns empty for unused type') + }) + }) + + // ============ removeTEE Tests ============ + + describe('removeTEE', function () { + let removeTeeId, removePubKey + + before(async function () { + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + removePubKey = '0x' + keyPair.publicKey.toString('hex') + removeTeeId = await registry.computeTEEId(removePubKey) + }) + + it('should allow owner to remove their active TEE', async function () { + // Register and activate + await registerAndActivate( + removePubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, PCR_HASH, teeOperator + ) + expect(await isActive(removeTeeId)).to.be.true + + // Verify it's in all indexes before removal + let activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + expect(activatedTEEs.some(id => id === removeTeeId)).to.be.true + let byType = await registry.getTEEsByType(TEE_TYPE_NITRO) + expect(byType.some(id => id === removeTeeId)).to.be.true + let byOwner = await registry.getTEEsByOwner(teeOperator) + expect(byOwner.some(id => id === removeTeeId)).to.be.true + + // Remove + const result = await registry.removeTEE(removeTeeId, { from: teeOperator }) + truffleAssert.eventEmitted(result, 'TEERemoved', (ev) => { + return ev.teeId === removeTeeId + }) + + // getTEE should revert (deleted) + await truffleAssert.reverts(registry.getTEE(removeTeeId)) + + // Removed from all indexes + activatedTEEs = await registry.getActivatedTEEs(TEE_TYPE_NITRO) + expect(activatedTEEs.some(id => id === removeTeeId)).to.be.false + byType = await registry.getTEEsByType(TEE_TYPE_NITRO) + expect(byType.some(id => id === removeTeeId)).to.be.false + byOwner = await registry.getTEEsByOwner(teeOperator) + expect(byOwner.some(id => id === removeTeeId)).to.be.false + + console.log('✓ Owner removed active TEE from all storage') + }) + + it('should allow owner to remove their inactive TEE', async function () { + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + const teeId = await registry.computeTEEId(pubKey) + + // Register but don't activate + await registry.registerTEEForTesting( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, PCR_HASH, { from: teeOperator } + ) + expect(await isActive(teeId)).to.be.false + + // Remove + const result = await registry.removeTEE(teeId, { from: teeOperator }) + truffleAssert.eventEmitted(result, 'TEERemoved') + + // getTEE should revert + await truffleAssert.reverts(registry.getTEE(teeId)) + + console.log('✓ Owner removed inactive TEE') + }) + + it('should allow admin to remove any TEE', async function () { + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + + await registerAndActivate( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, PCR_HASH, teeOperator + ) + const teeId = await registry.computeTEEId(pubKey) + + // Admin removes (owner is teeOperator, caller is admin/owner) + const result = await registry.removeTEE(teeId, { from: owner }) + truffleAssert.eventEmitted(result, 'TEERemoved') - console.log('✓ isActive returns false for non-existent TEE') + await truffleAssert.reverts(registry.getTEE(teeId)) + + console.log('✓ Admin removed TEE') + }) + + it('should revert when non-owner/non-admin removes', async function () { + const keyPair = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'der' } + }) + const pubKey = '0x' + keyPair.publicKey.toString('hex') + + await registerAndActivate( + pubKey, tlsCert1, user1, ENDPOINT, TEE_TYPE_NITRO, PCR_HASH, teeOperator + ) + const teeId = await registry.computeTEEId(pubKey) + + await truffleAssert.reverts( + registry.removeTEE(teeId, { from: user1 }) + ) + + console.log('✓ Non-owner/non-admin rejected') + }) + + it('should revert for non-existent teeId', async function () { + const fakeTeeId = web3.utils.keccak256('0xREMOVEFAKE') + + await truffleAssert.reverts( + registry.removeTEE(fakeTeeId, { from: owner }) + ) + + console.log('✓ Non-existent teeId reverts') }) }) }) diff --git a/tests/solidity/suites/tee/test/registry.js b/tests/solidity/suites/tee/test/registry.js index 171de947..694944d0 100644 --- a/tests/solidity/suites/tee/test/registry.js +++ b/tests/solidity/suites/tee/test/registry.js @@ -76,8 +76,6 @@ contract('TEERegistry', function (accounts) { }) it('should reject non-admin adding TEE type', async function () { - const DEFAULT_ADMIN_ROLE = await registry.DEFAULT_ADMIN_ROLE() - await truffleAssert.reverts( registry.addTEEType(99, 'Unauthorized', { from: user1 }) ) @@ -122,6 +120,7 @@ contract('TEERegistry', function (accounts) { }) describe('PCR Management', function () { + const TEE_TYPE = 1 const pcrs = { pcr0: '0x' + Buffer.alloc(48, 0x01).toString('hex'), pcr1: '0x' + Buffer.alloc(48, 0x02).toString('hex'), @@ -137,15 +136,15 @@ contract('TEERegistry', function (accounts) { }) it('should allow admin to approve PCR', async function () { - const result = await registry.approvePCR(pcrs, 'v1.0.0', '0x0000000000000000000000000000000000000000000000000000000000000000', 0) + const result = await registry.approvePCR(pcrs, 'v1.0.0', TEE_TYPE) truffleAssert.eventEmitted(result, 'PCRApproved', (ev) => { - return ev.pcrHash === pcrHash && ev.version === 'v1.0.0' + return ev.pcrHash === pcrHash && ev.teeType.toString() === '1' && ev.version === 'v1.0.0' }) - expect(await registry.isPCRApproved(pcrHash)).to.be.true + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.true - const pcrInfo = await registry.approvedPCRs(pcrHash) + const pcrInfo = await registry.approvedPCRs(TEE_TYPE, pcrHash) expect(pcrInfo.active).to.be.true expect(pcrInfo.version).to.equal('v1.0.0') expect(pcrInfo.expiresAt.toString()).to.equal('0') @@ -153,7 +152,7 @@ contract('TEERegistry', function (accounts) { console.log('✓ PCR approved successfully') }) - it('should handle PCR versioning with grace period', async function () { + it('should handle PCR versioning with grace period via revokePCR', async function () { const pcrsV2 = { pcr0: '0x' + Buffer.alloc(48, 0x04).toString('hex'), pcr1: '0x' + Buffer.alloc(48, 0x05).toString('hex'), @@ -163,14 +162,17 @@ contract('TEERegistry', function (accounts) { const pcrHashV2 = await registry.computePCRHash(pcrsV2) const gracePeriod = 3600 // 1 hour - // Approve v2 with v1 as previous - await registry.approvePCR(pcrsV2, 'v2.0.0', pcrHash, gracePeriod) + // Approve v2 + await registry.approvePCR(pcrsV2, 'v2.0.0', TEE_TYPE) + + // Revoke v1 with grace period + await registry.revokePCR(pcrHash, TEE_TYPE, gracePeriod) // Both should be valid during grace period - expect(await registry.isPCRApproved(pcrHash)).to.be.true - expect(await registry.isPCRApproved(pcrHashV2)).to.be.true + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.true + expect(await registry.isPCRApproved(TEE_TYPE, pcrHashV2)).to.be.true - const pcrV1Info = await registry.approvedPCRs(pcrHash) + const pcrV1Info = await registry.approvedPCRs(TEE_TYPE, pcrHash) expect(pcrV1Info.expiresAt.toNumber()).to.be.greaterThan(0) console.log('✓ PCR versioning with grace period works') @@ -185,16 +187,16 @@ contract('TEERegistry', function (accounts) { const pcrHashRevoke = await registry.computePCRHash(pcrsRevoke) - await registry.approvePCR(pcrsRevoke, 'v1.0.0-revoke', '0x0000000000000000000000000000000000000000000000000000000000000000', 0) - expect(await registry.isPCRApproved(pcrHashRevoke)).to.be.true + await registry.approvePCR(pcrsRevoke, 'v1.0.0-revoke', TEE_TYPE) + expect(await registry.isPCRApproved(TEE_TYPE, pcrHashRevoke)).to.be.true - const result = await registry.revokePCR(pcrHashRevoke) + const result = await registry.revokePCR(pcrHashRevoke, TEE_TYPE, 0) truffleAssert.eventEmitted(result, 'PCRRevoked', (ev) => { - return ev.pcrHash === pcrHashRevoke + return ev.pcrHash === pcrHashRevoke && ev.gracePeriod.toString() === '0' }) - expect(await registry.isPCRApproved(pcrHashRevoke)).to.be.false + expect(await registry.isPCRApproved(TEE_TYPE, pcrHashRevoke)).to.be.false console.log('✓ PCR revoked successfully') }) @@ -215,7 +217,7 @@ contract('TEERegistry', function (accounts) { } await truffleAssert.reverts( - registry.approvePCR(pcrsUnauth, 'unauthorized', '0x0000000000000000000000000000000000000000000000000000000000000000', 0, { from: user1 }) + registry.approvePCR(pcrsUnauth, 'unauthorized', TEE_TYPE, { from: user1 }) ) console.log('✓ Non-admin cannot approve PCR') @@ -223,7 +225,7 @@ contract('TEERegistry', function (accounts) { it('should reject non-admin revoking PCR', async function () { await truffleAssert.reverts( - registry.revokePCR(pcrHash, { from: user1 }) + registry.revokePCR(pcrHash, TEE_TYPE, 0, { from: user1 }) ) console.log('✓ Non-admin cannot revoke PCR') @@ -238,25 +240,18 @@ contract('TEERegistry', function (accounts) { const pcrHashExpiry = await registry.computePCRHash(pcrsExpiry) - // First approve the PCR normally - await registry.approvePCR(pcrsExpiry, 'v-expiry', '0x0000000000000000000000000000000000000000000000000000000000000000', 0) - expect(await registry.isPCRApproved(pcrHashExpiry)).to.be.true - - // Approve a new PCR referencing the expiry PCR as previous, with gracePeriod = 0. - // This sets expiresAt = block.timestamp, so any subsequent block (where - // block.timestamp > expiresAt) will cause isPCRApproved to return false. - const pcrsNew = { - pcr0: '0x' + Buffer.alloc(48, 0xF1).toString('hex'), - pcr1: '0x' + Buffer.alloc(48, 0xF2).toString('hex'), - pcr2: '0x' + Buffer.alloc(48, 0xF3).toString('hex') - } - await registry.approvePCR(pcrsNew, 'v-new', pcrHashExpiry, 0) + // Approve the PCR + await registry.approvePCR(pcrsExpiry, 'v-expiry', TEE_TYPE) + expect(await registry.isPCRApproved(TEE_TYPE, pcrHashExpiry)).to.be.true + + // Revoke with gracePeriod=1s + await registry.revokePCR(pcrHashExpiry, TEE_TYPE, 1) // Send a dummy transaction to mine a new block with an advanced timestamp await registry.setAWSRootCertificate('0x01') // The expiry PCR should now be rejected (block.timestamp > expiresAt) - expect(await registry.isPCRApproved(pcrHashExpiry)).to.be.false + expect(await registry.isPCRApproved(TEE_TYPE, pcrHashExpiry)).to.be.false console.log('✓ Expired PCR rejected after grace period elapses') }) @@ -347,21 +342,6 @@ contract('TEERegistry', function (accounts) { }) it('should reject registration with invalid attestation', async function () { - // First approve a PCR - const pcrs = { - pcr0: '0x' + Buffer.alloc(48, 0x01).toString('hex'), - pcr1: '0x' + Buffer.alloc(48, 0x02).toString('hex'), - pcr2: '0x' + Buffer.alloc(48, 0x03).toString('hex') - } - await registry.approvePCR(pcrs, 'v1.0.0', '0x0000000000000000000000000000000000000000000000000000000000000000', 0) - - // Add TEE type if not exists - try { - await registry.addTEEType(1, 'AWS Nitro') - } catch (e) { - // Type already exists, continue - } - const invalidAttestation = '0x' + Buffer.alloc(100, 0xFF).toString('hex') const dummyCert = '0x' + Buffer.alloc(100, 0xAA).toString('hex') @@ -409,24 +389,6 @@ contract('TEERegistry', function (accounts) { console.log('✓ PCR hash computation correct') }) - it('should compute message hash correctly', async function () { - const inputHash = web3.utils.keccak256('0x01') - const outputHash = web3.utils.keccak256('0x02') - const timestamp = Math.floor(Date.now() / 1000) - - const expectedHash = web3.utils.keccak256( - web3.eth.abi.encodeParameters( - ['bytes32', 'bytes32', 'uint256'], - [inputHash, outputHash, timestamp] - ) - ) - - const computedHash = await registry.computeMessageHash(inputHash, outputHash, timestamp) - expect(computedHash).to.equal(expectedHash) - - console.log('✓ Message hash computation correct') - }) - it('should handle getTEE for non-existent TEE', async function () { const nonExistentId = web3.utils.keccak256('0xDEADBEEF') @@ -450,11 +412,24 @@ contract('TEERegistry', function (accounts) { console.log('✓ Empty array returned for unused TEE type') }) + + it('should return empty array from getActivatedTEEs for unused type', async function () { + const tees = await registry.getActivatedTEEs(50) + expect(tees.length).to.equal(0) + + console.log('✓ Empty array returned from getActivatedTEEs for unused type') + }) + + it('should return empty array from getLiveTEEs for unused type', async function () { + const tees = await registry.getLiveTEEs(50) + expect(tees.length).to.equal(0) + + console.log('✓ Empty array returned from getLiveTEEs for unused type') + }) }) describe('Access Control', function () { it('should enforce DEFAULT_ADMIN_ROLE for sensitive operations', async function () { - // Test various admin-only functions await truffleAssert.reverts( registry.addTEEType(99, 'Test', { from: user1 }) ) @@ -463,8 +438,7 @@ contract('TEERegistry', function (accounts) { registry.approvePCR( { pcr0: '0x01', pcr1: '0x02', pcr2: '0x03' }, 'v1', - '0x0000000000000000000000000000000000000000000000000000000000000000', - 0, + 1, { from: user1 } ) ) @@ -490,4 +464,163 @@ contract('TEERegistry', function (accounts) { console.log('✓ Role management works correctly') }) }) + + describe('PCR Revocation Security', function () { + const TEE_TYPE = 1 + + it('should allow re-approval of active PCR (e.g. to update version)', async function () { + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x20).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x21).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x22).toString('hex') + } + const pcrHash = await registry.computePCRHash(pcrs) + + // First approval + await registry.approvePCR(pcrs, 'v-duplicate-test', TEE_TYPE) + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.true + + // Re-approval should succeed and update version + const result = await registry.approvePCR(pcrs, 'v-duplicate-test-2', TEE_TYPE) + + truffleAssert.eventEmitted(result, 'PCRApproved', (ev) => { + return ev.pcrHash === pcrHash && ev.version === 'v-duplicate-test-2' + }) + + const pcrInfo = await registry.approvedPCRs(TEE_TYPE, pcrHash) + expect(pcrInfo.version).to.equal('v-duplicate-test-2') + + console.log('✓ Re-approval of active PCR updates version') + }) + + it('should allow re-approval to cancel pending grace-period revocation', async function () { + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x23).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x24).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x28).toString('hex') + } + const pcrHash = await registry.computePCRHash(pcrs) + + // Approve, then revoke with grace period + await registry.approvePCR(pcrs, 'v-grace-cancel', TEE_TYPE) + await registry.revokePCR(pcrHash, TEE_TYPE, 3600) + + // Still valid during grace period, but expiresAt is set + const infoBefore = await registry.approvedPCRs(TEE_TYPE, pcrHash) + expect(infoBefore.expiresAt.toNumber()).to.be.greaterThan(0) + + // Re-approve to cancel the pending revocation + await registry.approvePCR(pcrs, 'v-grace-cancel-fixed', TEE_TYPE) + + // expiresAt should be reset to 0 + const infoAfter = await registry.approvedPCRs(TEE_TYPE, pcrHash) + expect(infoAfter.expiresAt.toString()).to.equal('0') + expect(infoAfter.active).to.be.true + expect(infoAfter.version).to.equal('v-grace-cancel-fixed') + + console.log('✓ Re-approval cancels pending grace-period revocation') + }) + + it('should allow re-approval of revoked PCR', async function () { + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x25).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x26).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x27).toString('hex') + } + + const pcrHash = await registry.computePCRHash(pcrs) + + await registry.approvePCR(pcrs, 'v-reapprove-1', TEE_TYPE) + await registry.revokePCR(pcrHash, TEE_TYPE, 0) + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.false + + // Re-approval should succeed after revocation + await registry.approvePCR(pcrs, 'v-reapprove-2', TEE_TYPE) + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.true + + console.log('✓ Re-approval of revoked PCR works') + }) + + it('should revoke PCR immediately when gracePeriod is 0', async function () { + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x30).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x31).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x32).toString('hex') + } + const pcrHash = await registry.computePCRHash(pcrs) + + await registry.approvePCR(pcrs, 'v-immediate-revoke', TEE_TYPE) + + const result = await registry.revokePCR(pcrHash, TEE_TYPE, 0) + + truffleAssert.eventEmitted(result, 'PCRRevoked', (ev) => { + return ev.pcrHash === pcrHash && ev.gracePeriod.toString() === '0' + }) + + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.false + + console.log('✓ Immediate PCR revocation works') + }) + + it('should revoke PCR with grace period', async function () { + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x70).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x71).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x72).toString('hex') + } + + const pcrHash = await registry.computePCRHash(pcrs) + const gracePeriod = 3600 // 1 hour + + await registry.approvePCR(pcrs, 'v-grace-revoke', TEE_TYPE) + + const result = await registry.revokePCR(pcrHash, TEE_TYPE, gracePeriod) + + truffleAssert.eventEmitted(result, 'PCRRevoked', (ev) => { + return ev.pcrHash === pcrHash && ev.gracePeriod.toString() === gracePeriod.toString() + }) + + // Should still be valid during grace period + expect(await registry.isPCRApproved(TEE_TYPE, pcrHash)).to.be.true + + const pcrInfo = await registry.approvedPCRs(TEE_TYPE, pcrHash) + expect(pcrInfo.expiresAt.toNumber()).to.be.greaterThan(0) + + console.log('✓ PCR revocation with grace period works') + }) + + it('should maintain both old and new PCRs during grace period', async function () { + const pcrsV1 = { + pcr0: '0x' + Buffer.alloc(48, 0x80).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x81).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x82).toString('hex') + } + + const pcrsV2 = { + pcr0: '0x' + Buffer.alloc(48, 0x90).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x91).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x92).toString('hex') + } + + const v1Hash = await registry.computePCRHash(pcrsV1) + const v2Hash = await registry.computePCRHash(pcrsV2) + + // Approve v1, then v2 + await registry.approvePCR(pcrsV1, 'v1-grace', TEE_TYPE) + await registry.approvePCR(pcrsV2, 'v2-grace', TEE_TYPE) + + // Revoke v1 with 1 hour grace period + await registry.revokePCR(v1Hash, TEE_TYPE, 3600) + + // Both should be valid during grace period + expect(await registry.isPCRApproved(TEE_TYPE, v1Hash)).to.be.true + expect(await registry.isPCRApproved(TEE_TYPE, v2Hash)).to.be.true + + // v1 should have expiry set + const v1Info = await registry.approvedPCRs(TEE_TYPE, v1Hash) + expect(v1Info.expiresAt.toNumber()).to.be.greaterThan(0) + + console.log('✓ Both PCRs valid during grace period') + }) + }) }) diff --git a/tests/solidity/suites/tee/test/settlementRelay.js b/tests/solidity/suites/tee/test/settlementRelay.js index c722bbd5..94d8948f 100644 --- a/tests/solidity/suites/tee/test/settlementRelay.js +++ b/tests/solidity/suites/tee/test/settlementRelay.js @@ -400,7 +400,6 @@ contract('InferenceSettlementRelay', function (accounts) { const TEE_TYPE_NITRO = 1 const TLS_CERT = '0x' + Buffer.alloc(100, 0xAA).toString('hex') const ENDPOINT = 'https://tee.example.com' - const PCR_HASH = web3.utils.keccak256('test-pcr') before(async () => { // Generate RSA key pair for signing @@ -416,9 +415,18 @@ contract('InferenceSettlementRelay', function (accounts) { intRegistry = await MockTEERegistry.new() await intRegistry.addTEEType(TEE_TYPE_NITRO, 'AWS Nitro') + // Approve PCR measurements so activateTEE passes _requirePCRValidForTEE + const pcrs = { + pcr0: '0x' + Buffer.alloc(48, 0x01).toString('hex'), + pcr1: '0x' + Buffer.alloc(48, 0x02).toString('hex'), + pcr2: '0x' + Buffer.alloc(48, 0x03).toString('hex') + } + await intRegistry.approvePCR(pcrs, 'v1.0.0', TEE_TYPE_NITRO) + const pcrHash = await intRegistry.computePCRHash(pcrs) + // Register and activate TEE await intRegistry.registerTEEForTesting( - publicKeyDER, TLS_CERT, user, ENDPOINT, TEE_TYPE_NITRO, PCR_HASH + publicKeyDER, TLS_CERT, user, ENDPOINT, TEE_TYPE_NITRO, pcrHash ) intTeeId = await intRegistry.computeTEEId(publicKeyDER) await intRegistry.activateTEE(intTeeId)