diff --git a/.gitmodules b/.gitmodules index 7d79667..2ca6cfa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/risc0-ethereum"] + path = lib/risc0-ethereum + url = https://github.com/risc0/risc0-ethereum diff --git a/Makefile b/Makefile index 8694cc1..f975673 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SOLC_VERSION=0.8.20 +SOLC_VERSION=0.8.28 FORGE=forge SLITHER=slither TEST_UPGRADEABLE=false diff --git a/contracts/DCAPValidator.sol b/contracts/DCAPValidator.sol new file mode 100644 index 0000000..3eeb8f5 --- /dev/null +++ b/contracts/DCAPValidator.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +import {ILCPClientErrors} from "./ILCPClientErrors.sol"; + +library DCAPValidator { + enum TCBStatus { + UpToDate, + OutOfDate, + Revoked, + ConfigurationNeeded, + OutOfDateConfigurationNeeded, + SWHardeningNeeded, + ConfigurationAndSWHardeningNeeded + } + + struct Output { + TCBStatus tcbStatus; + bytes32 sgxIntelRootCAHash; + uint64 validityNotBeforeMax; + uint64 validityNotAfterMin; + bool enclaveDebugEnabled; + bytes32 mrenclave; + address enclaveKey; + address operator; + string[] advisoryIDs; + } + + function parseCommit(bytes calldata commit) internal pure returns (Output memory) { + require(bytes2(commit[0:2]) == hex"0000", "unexpected version"); + require(uint16(bytes2(commit[2:4])) == 3, "unexpected quote version"); + require(uint32(bytes4(commit[4:8])) == 0, "unexpected tee type"); + + Output memory output; + output.tcbStatus = TCBStatus(uint8(commit[8])); + output.sgxIntelRootCAHash = bytes32(commit[15:47]); + output.validityNotBeforeMax = uint64(bytes8(commit[47:55])); + output.validityNotAfterMin = uint64(bytes8(commit[55:63])); + + uint256 sgxQuoteBodyOffset = 63; + uint256 attrbutesOffset = sgxQuoteBodyOffset + 16 + 4 + 28; + output.enclaveDebugEnabled = uint8(commit[attrbutesOffset]) & uint8(2) != 0; + uint256 mrenclaveOffset = attrbutesOffset + 16; + output.mrenclave = bytes32(commit[mrenclaveOffset:mrenclaveOffset + 32]); + + uint256 reportDataOffset = sgxQuoteBodyOffset + 320; + require(commit[reportDataOffset] == 0x01, "unexpected report data version"); + output.enclaveKey = address(bytes20(commit[reportDataOffset + 1:reportDataOffset + 1 + 20])); + output.operator = address(bytes20(commit[reportDataOffset + 1 + 20:reportDataOffset + 1 + 20 + 20])); + + uint256 advisoryIDsOffset = reportDataOffset + 64; + output.advisoryIDs = abi.decode(commit[advisoryIDsOffset:commit.length], (string[])); + return output; + } + + function tcbStatusToString(TCBStatus tcbStatus) internal pure returns (string memory) { + if (tcbStatus == TCBStatus.UpToDate) { + return "UpToDate"; + } else if (tcbStatus == TCBStatus.OutOfDate) { + return "OutOfDate"; + } else if (tcbStatus == TCBStatus.Revoked) { + return "Revoked"; + } else if (tcbStatus == TCBStatus.ConfigurationNeeded) { + return "ConfigurationNeeded"; + } else if (tcbStatus == TCBStatus.OutOfDateConfigurationNeeded) { + return "OutOfDateConfigurationNeeded"; + } else if (tcbStatus == TCBStatus.SWHardeningNeeded) { + return "SWHardeningNeeded"; + } else if (tcbStatus == TCBStatus.ConfigurationAndSWHardeningNeeded) { + return "ConfigurationAndSWHardeningNeeded"; + } else { + revert ILCPClientErrors.LCPClientZKDCAPUnrecognizedTCBStatus(); + } + } +} diff --git a/contracts/ILCPClientErrors.sol b/contracts/ILCPClientErrors.sol index be2b1dd..2aaa120 100644 --- a/contracts/ILCPClientErrors.sol +++ b/contracts/ILCPClientErrors.sol @@ -58,4 +58,7 @@ interface ILCPClientErrors { error LCPClientUpdateOperatorsPermissionless(); error LCPClientUpdateOperatorsSignatureUnexpectedOperator(address actual, address expected); + + error LCPClientZKDCAPBaseOutputNotValid(); + error LCPClientZKDCAPUnrecognizedTCBStatus(); } diff --git a/contracts/LCPClientBase.sol b/contracts/LCPClientBase.sol index a104c3e..093c3fa 100644 --- a/contracts/LCPClientBase.sol +++ b/contracts/LCPClientBase.sol @@ -18,7 +18,7 @@ import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol"; import {AVRValidator} from "./AVRValidator.sol"; import {ILCPClientErrors} from "./ILCPClientErrors.sol"; -abstract contract LCPClientBase is ILightClient, ILCPClientErrors { +abstract contract LCPClientCommon is ILightClient, ILCPClientErrors { using IBCHeight for Height.Data; // --------------------- Data structures --------------------- @@ -40,42 +40,26 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { mapping(uint128 => ConsensusState) consensusStates; // enclave key => EKInfo mapping(address => EKInfo) ekInfos; + bytes32 zkDCAPRisc0ImageId; } - // --------------------- Events --------------------- - - event RegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); - // --------------------- Immutable fields --------------------- /// @dev ibcHandler is the address of the IBC handler contract. /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address internal immutable ibcHandler; - /// @dev if developmentMode is true, the client allows the remote attestation of IAS in development. - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - bool internal immutable developmentMode; // --------------------- Storage fields --------------------- /// @dev clientId => client storage mapping(string => ClientStorage) internal clientStorages; - /// @dev RootCA's public key parameters - AVRValidator.RSAParams internal verifiedRootCAParams; - /// @dev keccak256(signingCert) => RSAParams of signing public key - mapping(bytes32 => AVRValidator.RSAParams) internal verifiedSigningRSAParams; - - /// @dev Reserved storage space to allow for layout changes in the future - uint256[50] private __gap; - // --------------------- Constructor --------------------- /// @custom:oz-upgrades-unsafe-allow constructor /// @param ibcHandler_ the address of the IBC handler contract - /// @param developmentMode_ if true, the client allows the enclave debug mode - constructor(address ibcHandler_, bool developmentMode_) { + constructor(address ibcHandler_) { ibcHandler = ibcHandler_; - developmentMode = developmentMode_; } // --------------------- Modifiers --------------------- @@ -85,33 +69,13 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { _; } - // --------------------- Public methods --------------------- - - /// @dev isDevelopmentMode returns true if the client allows the enclave debug mode. - function isDevelopmentMode() public view returns (bool) { - return developmentMode; - } - - /// @dev initializeRootCACert initializes the root CA's public key parameters. - /// All contracts that inherit LCPClientBase should call this in the constructor or initializer. - function initializeRootCACert(bytes memory rootCACert) internal { - if (verifiedRootCAParams.notAfter != 0) { - revert LCPClientRootCACertAlreadyInitialized(); - } - verifiedRootCAParams = AVRValidator.verifyRootCACert(rootCACert); - } + // --------------------- Internal methods --------------------- - /** - * @dev initializeClient initializes a new client with the given state. - * If succeeded, it returns heights at which the consensus state are stored. - * This function is guaranteed by the IBC contract to be called only once for each `clientId`. - * @param clientId the client identifier which is unique within the IBC handler - */ - function initializeClient( - string calldata clientId, + function _initializeClient( + ClientStorage storage clientStorage, bytes calldata protoClientState, bytes calldata protoConsensusState - ) public onlyIBC returns (Height.Data memory height) { + ) internal returns (ProtoClientState.Data memory, ProtoConsensusState.Data memory) { ProtoClientState.Data memory clientState = LCPProtoMarshaler.unmarshalClientState(protoClientState); ProtoConsensusState.Data memory consensusState = LCPProtoMarshaler.unmarshalConsensusState(protoConsensusState); @@ -164,7 +128,6 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { } prev = addr; } - ClientStorage storage clientStorage = clientStorages[clientId]; clientStorage.clientState = clientState; // set allowed quote status and advisories @@ -182,14 +145,20 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { } clientStorage.allowedStatuses.allowedAdvisories[allowedAdvisoryId] = AVRValidator.FLAG_ALLOWED; } - - return clientState.latest_height; + return (clientState, consensusState); } + // --------------------- Public methods --------------------- + /** * @dev getTimestampAtHeight returns the timestamp of the consensus state at the given height. */ - function getTimestampAtHeight(string calldata clientId, Height.Data calldata height) public view returns (uint64) { + function getTimestampAtHeight(string calldata clientId, Height.Data calldata height) + public + view + override + returns (uint64) + { ConsensusState storage consensusState = clientStorages[clientId].consensusStates[height.toUint128()]; if (consensusState.timestamp == 0) { revert LCPClientConsensusStateNotFound(); @@ -200,7 +169,7 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { /** * @dev getLatestHeight returns the latest height of the client state corresponding to `clientId`. */ - function getLatestHeight(string calldata clientId) public view returns (Height.Data memory) { + function getLatestHeight(string calldata clientId) public view override returns (Height.Data memory) { ProtoClientState.Data storage clientState = clientStorages[clientId].clientState; if (clientState.latest_height.revision_height == 0) { revert LCPClientClientStateNotFound(); @@ -211,7 +180,7 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { * @dev getStatus returns the status of the client corresponding to `clientId`. */ - function getStatus(string calldata clientId) public view returns (ClientStatus) { + function getStatus(string calldata clientId) public view override returns (ClientStatus) { return clientStorages[clientId].clientState.frozen ? ClientStatus.Frozen : ClientStatus.Active; } @@ -221,6 +190,7 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { function getLatestInfo(string calldata clientId) public view + override returns (Height.Data memory latestHeight, uint64 latestTimestamp, ClientStatus status) { ClientStorage storage clientStorage = clientStorages[clientId]; @@ -229,177 +199,6 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { status = clientStorage.clientState.frozen ? ClientStatus.Frozen : ClientStatus.Active; } - /** - * @dev routeUpdateClient returns the calldata to the receiving function of the client message. - * Light client contract may encode a client message as other encoding scheme(e.g. ethereum ABI) - * Check ADR-001 for details. - */ - function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage) - public - pure - returns (bytes4, bytes memory) - { - (bytes32 typeUrlHash, bytes memory args) = LCPProtoMarshaler.routeClientMessage(clientId, protoClientMessage); - if (typeUrlHash == LCPProtoMarshaler.UPDATE_CLIENT_MESSAGE_TYPE_URL_HASH) { - return (this.updateClient.selector, args); - } else if (typeUrlHash == LCPProtoMarshaler.REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) { - return (this.registerEnclaveKey.selector, args); - } else if (typeUrlHash == LCPProtoMarshaler.UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH) { - return (this.updateOperators.selector, args); - } else { - revert LCPClientUnknownProtoTypeUrl(); - } - } - - /** - * @dev verifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. - * The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). - */ - function verifyMembership( - string calldata clientId, - Height.Data calldata height, - uint64, - uint64, - bytes calldata proof, - bytes memory prefix, - bytes memory path, - bytes calldata value - ) public view returns (bool) { - ( - LCPCommitment.CommitmentProofs memory commitmentProofs, - LCPCommitment.VerifyMembershipProxyMessage memory message - ) = LCPCommitment.parseVerifyMembershipCommitmentProofs(proof); - ClientStorage storage clientStorage = clientStorages[clientId]; - validateProxyMessage(clientStorage, message, height, prefix, path); - if (keccak256(value) != message.value) { - revert LCPClientMembershipVerificationInvalidValue(); - } - verifyCommitmentProofs(clientStorage, commitmentProofs); - return true; - } - - /** - * @dev verifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. - * The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). - */ - function verifyNonMembership( - string calldata clientId, - Height.Data calldata height, - uint64, - uint64, - bytes calldata proof, - bytes calldata prefix, - bytes calldata path - ) public view returns (bool) { - ( - LCPCommitment.CommitmentProofs memory commitmentProofs, - LCPCommitment.VerifyMembershipProxyMessage memory message - ) = LCPCommitment.parseVerifyMembershipCommitmentProofs(proof); - ClientStorage storage clientStorage = clientStorages[clientId]; - validateProxyMessage(clientStorage, message, height, prefix, path); - if (message.value != bytes32(0)) { - revert LCPClientMembershipVerificationInvalidValue(); - } - verifyCommitmentProofs(clientStorage, commitmentProofs); - return true; - } - - function validateProxyMessage( - ClientStorage storage clientStorage, - LCPCommitment.VerifyMembershipProxyMessage memory message, - Height.Data calldata height, - bytes memory prefix, - bytes memory path - ) internal view { - uint128 messageHeight = message.height.toUint128(); - uint128 heightValue = height.toUint128(); - ConsensusState storage consensusState = clientStorage.consensusStates[messageHeight]; - if (consensusState.stateId == bytes32(0)) { - revert LCPClientConsensusStateNotFound(); - } - if (heightValue != messageHeight) { - revert LCPClientMembershipVerificationInvalidHeight(); - } - if (keccak256(prefix) != keccak256(message.prefix)) { - revert LCPClientMembershipVerificationInvalidPrefix(); - } - if (keccak256(path) != keccak256(message.path)) { - revert LCPClientMembershipVerificationInvalidPath(); - } - if (consensusState.stateId != message.stateId) { - revert LCPClientMembershipVerificationInvalidStateId(); - } - } - - function verifyCommitmentProofs( - ClientStorage storage clientStorage, - LCPCommitment.CommitmentProofs memory commitmentProofs - ) internal view { - bytes32 commitment = keccak256(commitmentProofs.message); - verifySignatures(clientStorage, commitment, commitmentProofs.signatures); - } - - /** - * @dev getClientState returns the clientState corresponding to `clientId`. - * If it's not found, the function returns false. - */ - function getClientState(string calldata clientId) public view returns (bytes memory clientStateBytes, bool) { - ProtoClientState.Data storage clientState = clientStorages[clientId].clientState; - if (clientState.mrenclave.length == 0) { - return (clientStateBytes, false); - } - return (LCPProtoMarshaler.marshal(clientState), true); - } - - /** - * @dev getConsensusState returns the consensusState corresponding to `clientId` and `height`. - * If it's not found, the function returns false. - */ - function getConsensusState(string calldata clientId, Height.Data calldata height) - public - view - returns (bytes memory consensusStateBytes, bool) - { - ConsensusState storage consensusState = clientStorages[clientId].consensusStates[height.toUint128()]; - if (consensusState.timestamp == 0 && consensusState.stateId == bytes32(0)) { - return (consensusStateBytes, false); - } - return (LCPProtoMarshaler.marshalConsensusState(consensusState.stateId, consensusState.timestamp), true); - } - - function verifySignatures(ClientStorage storage clientStorage, bytes32 commitment, bytes[] memory signatures) - internal - view - { - uint256 sigNum = signatures.length; - uint256 opNum = clientStorage.clientState.operators.length; - if (opNum == 0) { - if (sigNum != 1) { - revert LCPClientInvalidSignaturesLength(); - } - ensureActiveKey(clientStorage, verifyECDSASignature(commitment, signatures[0])); - } else { - if (sigNum != opNum) { - revert LCPClientInvalidSignaturesLength(); - } - uint256 success = 0; - for (uint256 i = 0; i < sigNum; i++) { - bytes memory sig = signatures[i]; - if (sig.length != 0) { - ensureActiveKey( - clientStorage, - verifyECDSASignature(commitment, sig), - address(bytes20(clientStorage.clientState.operators[i])) - ); - unchecked { - success++; - } - } - } - ensureSufficientValidSignatures(clientStorage.clientState, success); - } - } - function updateClient(string calldata clientId, UpdateClientMessage.Data calldata message) public returns (Height.Data[] memory heights) @@ -495,74 +294,21 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { return heights; } - function registerEnclaveKey(string calldata clientId, RegisterEnclaveKeyMessage.Data calldata message) + function updateOperators(string calldata clientId, UpdateOperatorsMessage.Data calldata message) public returns (Height.Data[] memory heights) { - ClientStorage storage clientStorage = clientStorages[clientId]; - AVRValidator.ReportExtractedElements memory reElems = AVRValidator.verifyReport( - developmentMode, - verifiedRootCAParams, - verifiedSigningRSAParams, - clientStorage.allowedStatuses, - message.report, - message.signing_cert, - message.signature - ); - - if (bytes32(clientStorage.clientState.mrenclave) != reElems.mrenclave) { - revert LCPClientClientStateUnexpectedMrenclave(); - } - - // if `operator_signature` is empty, the operator address is zero - address operator; - if (message.operator_signature.length != 0) { - operator = verifyECDSASignature( - keccak256(LCPOperator.computeEIP712RegisterEnclaveKey(message.report)), message.operator_signature - ); + ProtoClientState.Data storage clientState = clientStorages[clientId].clientState; + uint256 opNum = clientState.operators.length; + uint256 sigNum = message.signatures.length; + if (opNum == 0) { + revert LCPClientUpdateOperatorsPermissionless(); } - if (reElems.operator != address(0) && reElems.operator != operator) { - revert LCPClientAVRUnexpectedOperator(operator, reElems.operator); + if (sigNum != opNum) { + revert LCPClientInvalidSignaturesLength(); } - uint64 expiredAt = reElems.attestationTime + clientStorage.clientState.key_expiration; - if (expiredAt <= block.timestamp) { - revert LCPClientAVRAlreadyExpired(); - } - EKInfo storage ekInfo = clientStorage.ekInfos[reElems.enclaveKey]; - if (ekInfo.expiredAt != 0) { - if (ekInfo.operator != operator) { - revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, operator); - } - if (ekInfo.expiredAt != expiredAt) { - revert LCPClientEnclaveKeyUnexpectedExpiredAt(); - } - // NOTE: if the key already exists, don't update any state - return heights; - } - ekInfo.expiredAt = expiredAt; - ekInfo.operator = operator; - - emit RegisteredEnclaveKey(clientId, reElems.enclaveKey, expiredAt, operator); - - // Note: client and consensus state are not always updated in registerEnclaveKey - return heights; - } - - function updateOperators(string calldata clientId, UpdateOperatorsMessage.Data calldata message) - public - returns (Height.Data[] memory heights) - { - ProtoClientState.Data storage clientState = clientStorages[clientId].clientState; - uint256 opNum = clientState.operators.length; - uint256 sigNum = message.signatures.length; - if (opNum == 0) { - revert LCPClientUpdateOperatorsPermissionless(); - } - if (sigNum != opNum) { - revert LCPClientInvalidSignaturesLength(); - } - if (message.new_operators_threshold_numerator == 0 || message.new_operators_threshold_denominator == 0) { - revert LCPClientClientStateInvalidOperatorsThreshold(); + if (message.new_operators_threshold_numerator == 0 || message.new_operators_threshold_denominator == 0) { + revert LCPClientClientStateInvalidOperatorsThreshold(); } uint64 nonce = clientState.operators_nonce; uint64 nextNonce = nonce + 1; @@ -662,4 +408,295 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { } return ECDSA.recover(commitment, signature); } + + /** + * @dev verifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. + * The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). + */ + function verifyMembership( + string calldata clientId, + Height.Data calldata height, + uint64, + uint64, + bytes calldata proof, + bytes memory prefix, + bytes memory path, + bytes calldata value + ) public view returns (bool) { + ( + LCPCommitment.CommitmentProofs memory commitmentProofs, + LCPCommitment.VerifyMembershipProxyMessage memory message + ) = LCPCommitment.parseVerifyMembershipCommitmentProofs(proof); + ClientStorage storage clientStorage = clientStorages[clientId]; + validateProxyMessage(clientStorage, message, height, prefix, path); + if (keccak256(value) != message.value) { + revert LCPClientMembershipVerificationInvalidValue(); + } + verifyCommitmentProofs(clientStorage, commitmentProofs); + return true; + } + + /** + * @dev verifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. + * The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). + */ + function verifyNonMembership( + string calldata clientId, + Height.Data calldata height, + uint64, + uint64, + bytes calldata proof, + bytes calldata prefix, + bytes calldata path + ) public view returns (bool) { + ( + LCPCommitment.CommitmentProofs memory commitmentProofs, + LCPCommitment.VerifyMembershipProxyMessage memory message + ) = LCPCommitment.parseVerifyMembershipCommitmentProofs(proof); + ClientStorage storage clientStorage = clientStorages[clientId]; + validateProxyMessage(clientStorage, message, height, prefix, path); + if (message.value != bytes32(0)) { + revert LCPClientMembershipVerificationInvalidValue(); + } + verifyCommitmentProofs(clientStorage, commitmentProofs); + return true; + } + + function validateProxyMessage( + ClientStorage storage clientStorage, + LCPCommitment.VerifyMembershipProxyMessage memory message, + Height.Data calldata height, + bytes memory prefix, + bytes memory path + ) internal view { + uint128 messageHeight = message.height.toUint128(); + uint128 heightValue = height.toUint128(); + ConsensusState storage consensusState = clientStorage.consensusStates[messageHeight]; + if (consensusState.stateId == bytes32(0)) { + revert LCPClientConsensusStateNotFound(); + } + if (heightValue != messageHeight) { + revert LCPClientMembershipVerificationInvalidHeight(); + } + if (keccak256(prefix) != keccak256(message.prefix)) { + revert LCPClientMembershipVerificationInvalidPrefix(); + } + if (keccak256(path) != keccak256(message.path)) { + revert LCPClientMembershipVerificationInvalidPath(); + } + if (consensusState.stateId != message.stateId) { + revert LCPClientMembershipVerificationInvalidStateId(); + } + } + + function verifyCommitmentProofs( + ClientStorage storage clientStorage, + LCPCommitment.CommitmentProofs memory commitmentProofs + ) internal view { + bytes32 commitment = keccak256(commitmentProofs.message); + verifySignatures(clientStorage, commitment, commitmentProofs.signatures); + } + + /** + * @dev getClientState returns the clientState corresponding to `clientId`. + * If it's not found, the function returns false. + */ + function getClientState(string calldata clientId) public view returns (bytes memory clientStateBytes, bool) { + ProtoClientState.Data storage clientState = clientStorages[clientId].clientState; + if (clientState.mrenclave.length == 0) { + return (clientStateBytes, false); + } + return (LCPProtoMarshaler.marshal(clientState), true); + } + + /** + * @dev getConsensusState returns the consensusState corresponding to `clientId` and `height`. + * If it's not found, the function returns false. + */ + function getConsensusState(string calldata clientId, Height.Data calldata height) + public + view + returns (bytes memory consensusStateBytes, bool) + { + ConsensusState storage consensusState = clientStorages[clientId].consensusStates[height.toUint128()]; + if (consensusState.timestamp == 0 && consensusState.stateId == bytes32(0)) { + return (consensusStateBytes, false); + } + return (LCPProtoMarshaler.marshalConsensusState(consensusState.stateId, consensusState.timestamp), true); + } + + function verifySignatures(ClientStorage storage clientStorage, bytes32 commitment, bytes[] memory signatures) + internal + view + { + uint256 sigNum = signatures.length; + uint256 opNum = clientStorage.clientState.operators.length; + if (opNum == 0) { + if (sigNum != 1) { + revert LCPClientInvalidSignaturesLength(); + } + ensureActiveKey(clientStorage, verifyECDSASignature(commitment, signatures[0])); + } else { + if (sigNum != opNum) { + revert LCPClientInvalidSignaturesLength(); + } + uint256 success = 0; + for (uint256 i = 0; i < sigNum; i++) { + bytes memory sig = signatures[i]; + if (sig.length != 0) { + ensureActiveKey( + clientStorage, + verifyECDSASignature(commitment, sig), + address(bytes20(clientStorage.clientState.operators[i])) + ); + unchecked { + success++; + } + } + } + ensureSufficientValidSignatures(clientStorage.clientState, success); + } + } +} + +abstract contract LCPClientBase is LCPClientCommon { + using IBCHeight for Height.Data; + + /// @dev if developmentMode is true, the client allows the target enclave which is debug mode enabled. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + bool internal immutable developmentMode; + + // --------------------- Constructor --------------------- + + /// @custom:oz-upgrades-unsafe-allow constructor + /// @param ibcHandler_ the address of the IBC handler contract + /// @param developmentMode_ if true, the client allows the enclave debug mode + constructor(address ibcHandler_, bool developmentMode_) LCPClientCommon(ibcHandler_) { + developmentMode = developmentMode_; + } + + // --------------------- Events --------------------- + + event RegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); + + // --------------------- Storage fields --------------------- + + /// @dev RootCA's public key parameters + AVRValidator.RSAParams internal verifiedRootCAParams; + /// @dev keccak256(signingCert) => RSAParams of signing public key + mapping(bytes32 => AVRValidator.RSAParams) internal verifiedSigningRSAParams; + + /// @dev Reserved storage space to allow for layout changes in the future + uint256[50] private __gap; + + // --------------------- Public methods --------------------- + + /// @dev isDevelopmentMode returns true if the client allows the enclave debug mode. + function isDevelopmentMode() public view returns (bool) { + return developmentMode; + } + + /** + * @dev initializeClient initializes a new client with the given state. + * If succeeded, it returns heights at which the consensus state are stored. + * This function is guaranteed by the IBC contract to be called only once for each `clientId`. + * @param clientId the client identifier which is unique within the IBC handler + */ + function initializeClient( + string calldata clientId, + bytes calldata protoClientState, + bytes calldata protoConsensusState + ) public override onlyIBC returns (Height.Data memory height) { + (ProtoClientState.Data memory clientState,) = + _initializeClient(clientStorages[clientId], protoClientState, protoConsensusState); + require(clientState.zkdcap_verifier_info.length == 0, "verifier info must be empty"); + return clientState.latest_height; + } + + /** + * @dev routeUpdateClient returns the calldata to the receiving function of the client message. + * Light client contract may encode a client message as other encoding scheme(e.g. ethereum ABI) + * Check ADR-001 for details. + */ + function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage) + public + pure + override + returns (bytes4, bytes memory) + { + (bytes32 typeUrlHash, bytes memory args) = LCPProtoMarshaler.routeClientMessage(clientId, protoClientMessage); + if (typeUrlHash == LCPProtoMarshaler.UPDATE_CLIENT_MESSAGE_TYPE_URL_HASH) { + return (this.updateClient.selector, args); + } else if (typeUrlHash == LCPProtoMarshaler.REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) { + return (this.registerEnclaveKey.selector, args); + } else if (typeUrlHash == LCPProtoMarshaler.UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH) { + return (this.updateOperators.selector, args); + } else { + revert LCPClientUnknownProtoTypeUrl(); + } + } + + function registerEnclaveKey(string calldata clientId, RegisterEnclaveKeyMessage.Data calldata message) + public + returns (Height.Data[] memory heights) + { + ClientStorage storage clientStorage = clientStorages[clientId]; + AVRValidator.ReportExtractedElements memory reElems = AVRValidator.verifyReport( + developmentMode, + verifiedRootCAParams, + verifiedSigningRSAParams, + clientStorage.allowedStatuses, + message.report, + message.signing_cert, + message.signature + ); + + if (bytes32(clientStorage.clientState.mrenclave) != reElems.mrenclave) { + revert LCPClientClientStateUnexpectedMrenclave(); + } + + // if `operator_signature` is empty, the operator address is zero + address operator; + if (message.operator_signature.length != 0) { + operator = verifyECDSASignature( + keccak256(LCPOperator.computeEIP712RegisterEnclaveKey(message.report)), message.operator_signature + ); + } + if (reElems.operator != address(0) && reElems.operator != operator) { + revert LCPClientAVRUnexpectedOperator(operator, reElems.operator); + } + uint64 expiredAt = reElems.attestationTime + clientStorage.clientState.key_expiration; + if (expiredAt <= block.timestamp) { + revert LCPClientAVRAlreadyExpired(); + } + EKInfo storage ekInfo = clientStorage.ekInfos[reElems.enclaveKey]; + if (ekInfo.expiredAt != 0) { + if (ekInfo.operator != operator) { + revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, operator); + } + if (ekInfo.expiredAt != expiredAt) { + revert LCPClientEnclaveKeyUnexpectedExpiredAt(); + } + // NOTE: if the key already exists, don't update any state + return heights; + } + ekInfo.expiredAt = expiredAt; + ekInfo.operator = operator; + + emit RegisteredEnclaveKey(clientId, reElems.enclaveKey, expiredAt, operator); + + // Note: client and consensus state are not always updated in registerEnclaveKey + return heights; + } + + // --------------------- Internal methods --------------------- + + /// @dev initializeRootCACert initializes the root CA's public key parameters. + /// All contracts that inherit LCPClientBase should call this in the constructor or initializer. + function initializeRootCACert(bytes memory rootCACert) internal { + if (verifiedRootCAParams.notAfter != 0) { + revert LCPClientRootCACertAlreadyInitialized(); + } + verifiedRootCAParams = AVRValidator.verifyRootCACert(rootCACert); + } } diff --git a/contracts/LCPClientZKDCAP.sol b/contracts/LCPClientZKDCAP.sol new file mode 100644 index 0000000..6b39239 --- /dev/null +++ b/contracts/LCPClientZKDCAP.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol"; + +contract LCPClientZKDCAP is LCPClientZKDCAPBase { + constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier) + LCPClientZKDCAPBase(ibcHandler_, developmentMode_, intelRootCA, riscZeroVerifier) + {} +} diff --git a/contracts/LCPClientZKDCAPBase.sol b/contracts/LCPClientZKDCAPBase.sol new file mode 100644 index 0000000..e406a62 --- /dev/null +++ b/contracts/LCPClientZKDCAPBase.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +import {IBCHeight} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/IBCHeight.sol"; +import {Height} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/Client.sol"; +import {IRiscZeroVerifier} from "risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import { + IbcLightclientsLcpV1ClientState as ProtoClientState, + IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnclaveKeyMessage +} from "./proto/ibc/lightclients/lcp/v1/LCP.sol"; +import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol"; +import {LCPClientCommon} from "./LCPClientBase.sol"; +import {LCPOperator} from "./LCPOperator.sol"; +import {AVRValidator} from "./AVRValidator.sol"; +import {DCAPValidator} from "./DCAPValidator.sol"; + +abstract contract LCPClientZKDCAPBase is LCPClientCommon { + using IBCHeight for Height.Data; + + // --------------------- Events --------------------- + + event ZKDCAPRegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); + + // --------------------- Immutable fields --------------------- + + /// @dev if developmentMode is true, the client allows the target enclave which is debug mode enabled. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + bool internal immutable developmentMode; + + /// @notice The hash of the root CA's public key certificate. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + bytes32 public immutable intelRootCAHash; + + /// @notice RISC Zero verifier contract address. + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IRiscZeroVerifier public immutable riscZeroVerifier; + + // --------------------- Storage fields --------------------- + + /// @dev Reserved storage space to allow for layout changes in the future + uint256[50] private __gap; + + // --------------------- Constructor --------------------- + + /// @custom:oz-upgrades-unsafe-allow constructor + /// @param ibcHandler_ the address of the IBC handler contract + constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier_) + LCPClientCommon(ibcHandler_) + { + require(intelRootCA.length != 0 && riscZeroVerifier_ != address(0), "invalid parameters"); + intelRootCAHash = keccak256(intelRootCA); + riscZeroVerifier = IRiscZeroVerifier(riscZeroVerifier_); + developmentMode = developmentMode_; + } + + // --------------------- Public methods --------------------- + + /** + * @dev initializeClient initializes a new client with the given state. + * If succeeded, it returns heights at which the consensus state are stored. + * This function is guaranteed by the IBC contract to be called only once for each `clientId`. + * @param clientId the client identifier which is unique within the IBC handler + */ + function initializeClient( + string calldata clientId, + bytes calldata protoClientState, + bytes calldata protoConsensusState + ) public override onlyIBC returns (Height.Data memory height) { + ClientStorage storage clientStorage = clientStorages[clientId]; + (ProtoClientState.Data memory clientState,) = + _initializeClient(clientStorage, protoClientState, protoConsensusState); + require(clientState.zkdcap_verifier_info.length == 64, "invalid verifier info length"); + require(clientState.zkdcap_verifier_info[0] == 0x01, "invalid verifier info version"); + bytes memory verifierInfo = clientState.zkdcap_verifier_info; + // 32..64 bytes: image ID + bytes32 imageId; + assembly { + imageId := mload(add(add(verifierInfo, 32), 32)) + } + clientStorage.zkDCAPRisc0ImageId = imageId; + return clientState.latest_height; + } + + /** + * @dev routeUpdateClient returns the calldata to the receiving function of the client message. + * Light client contract may encode a client message as other encoding scheme(e.g. ethereum ABI) + * Check ADR-001 for details. + */ + function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage) + public + pure + override + returns (bytes4, bytes memory) + { + (bytes32 typeUrlHash, bytes memory args) = LCPProtoMarshaler.routeClientMessage(clientId, protoClientMessage); + if (typeUrlHash == LCPProtoMarshaler.UPDATE_CLIENT_MESSAGE_TYPE_URL_HASH) { + return (this.updateClient.selector, args); + } else if (typeUrlHash == LCPProtoMarshaler.ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) { + return (this.zkdcapRegisterEnclaveKey.selector, args); + } else if (typeUrlHash == LCPProtoMarshaler.UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH) { + return (this.updateOperators.selector, args); + } else { + revert LCPClientUnknownProtoTypeUrl(); + } + } + + function zkdcapRegisterEnclaveKey(string calldata clientId, ZKDCAPRegisterEnclaveKeyMessage.Data calldata message) + public + returns (Height.Data[] memory heights) + { + ClientStorage storage clientStorage = clientStorages[clientId]; + require(clientStorage.zkDCAPRisc0ImageId != bytes32(0), "image ID not set"); + riscZeroVerifier.verify(message.proof, clientStorage.zkDCAPRisc0ImageId, sha256(message.commit)); + DCAPValidator.Output memory output = DCAPValidator.parseCommit(message.commit); + require(output.sgxIntelRootCAHash == intelRootCAHash, "unexpected root CA hash"); + + if (bytes32(clientStorage.clientState.mrenclave) != output.mrenclave) { + revert LCPClientClientStateUnexpectedMrenclave(); + } + + require( + clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(output.tcbStatus)] + == AVRValidator.FLAG_ALLOWED, + "disallowed TCB status" + ); + for (uint256 i = 0; i < output.advisoryIDs.length; i++) { + require( + clientStorage.allowedStatuses.allowedAdvisories[output.advisoryIDs[i]] == AVRValidator.FLAG_ALLOWED, + "disallowed advisory ID" + ); + } + require(output.enclaveDebugEnabled == developmentMode, "unexpected enclave debug mode"); + + // if `operator_signature` is empty, the operator address is zero + address operator; + if (message.operator_signature.length != 0) { + operator = verifyECDSASignature( + keccak256( + LCPOperator.computeEIP712ZKDCAPRegisterEnclaveKey( + clientStorage.clientState.zkdcap_verifier_info, keccak256(message.commit) + ) + ), + message.operator_signature + ); + } + if (output.operator != address(0) && output.operator != operator) { + revert LCPClientAVRUnexpectedOperator(operator, output.operator); + } + if (block.timestamp < output.validityNotBeforeMax || block.timestamp > output.validityNotAfterMin) { + revert LCPClientZKDCAPBaseOutputNotValid(); + } + uint64 expiredAt = output.validityNotAfterMin; + EKInfo storage ekInfo = clientStorage.ekInfos[output.enclaveKey]; + if (ekInfo.expiredAt != 0) { + if (ekInfo.operator != operator) { + revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, operator); + } + if (ekInfo.expiredAt != expiredAt) { + revert LCPClientEnclaveKeyUnexpectedExpiredAt(); + } + // NOTE: if the key already exists, don't update any state + return heights; + } + ekInfo.expiredAt = expiredAt; + ekInfo.operator = operator; + + emit ZKDCAPRegisteredEnclaveKey(clientId, output.enclaveKey, expiredAt, operator); + + // Note: client and consensus state are not always updated in registerEnclaveKey + return heights; + } +} diff --git a/contracts/LCPOperator.sol b/contracts/LCPOperator.sol index adca35a..580e9cf 100644 --- a/contracts/LCPOperator.sol +++ b/contracts/LCPOperator.sol @@ -7,6 +7,8 @@ library LCPOperator { bytes32 internal constant TYPEHASH_DOMAIN_SEPARATOR = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); bytes32 internal constant TYPEHASH_REGISTER_ENCLAVE_KEY = keccak256("RegisterEnclaveKey(string avr)"); + bytes32 internal constant TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY = + keccak256("ZKDCAPRegisterEnclaveKey(bytes zkdcapVerifierInfo,bytes32 commitHash)"); bytes32 internal constant TYPEHASH_UPDATE_OPERATORS = keccak256( "UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)" ); @@ -15,7 +17,7 @@ library LCPOperator { bytes32 internal constant DOMAIN_SEPARATOR_VERSION = keccak256("1"); // domainSeparatorUniversal() - bytes32 internal constant DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY = + bytes32 internal constant DOMAIN_SEPARATOR_LCP_CLIENT = 0x7fd21c2453e80741907e7ff11fd62ae1daa34c6fc0c2eced821f1c1d3fe88a4c; ChainType internal constant CHAIN_TYPE_EVM = ChainType.wrap(1); // chainTypeSalt(CHAIN_TYPE_EVM, hex"") @@ -49,10 +51,20 @@ library LCPOperator { } function computeEIP712RegisterEnclaveKey(bytes calldata avr) internal pure returns (bytes memory) { + return abi.encodePacked( + hex"1901", DOMAIN_SEPARATOR_LCP_CLIENT, keccak256(abi.encode(TYPEHASH_REGISTER_ENCLAVE_KEY, keccak256(avr))) + ); + } + + function computeEIP712ZKDCAPRegisterEnclaveKey(bytes memory zkdcapVerifierInfo, bytes32 commitHash) + internal + pure + returns (bytes memory) + { return abi.encodePacked( hex"1901", - DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY, - keccak256(abi.encode(TYPEHASH_REGISTER_ENCLAVE_KEY, keccak256(avr))) + DOMAIN_SEPARATOR_LCP_CLIENT, + keccak256(abi.encode(TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY, keccak256(zkdcapVerifierInfo), commitHash)) ); } diff --git a/contracts/LCPProtoMarshaler.sol b/contracts/LCPProtoMarshaler.sol index 4f45066..8e5fa1e 100644 --- a/contracts/LCPProtoMarshaler.sol +++ b/contracts/LCPProtoMarshaler.sol @@ -5,6 +5,7 @@ import { IbcLightclientsLcpV1ClientState as ClientState, IbcLightclientsLcpV1ConsensusState as ConsensusState, IbcLightclientsLcpV1RegisterEnclaveKeyMessage as RegisterEnclaveKeyMessage, + IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnclaveKeyMessage, IbcLightclientsLcpV1UpdateClientMessage as UpdateClientMessage, IbcLightclientsLcpV1UpdateOperatorsMessage as UpdateOperatorsMessage } from "./proto/ibc/lightclients/lcp/v1/LCP.sol"; @@ -13,6 +14,8 @@ import {GoogleProtobufAny as Any} from "@hyperledger-labs/yui-ibc-solidity/contr library LCPProtoMarshaler { string constant UPDATE_CLIENT_MESSAGE_TYPE_URL = "/ibc.lightclients.lcp.v1.UpdateClientMessage"; string constant REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL = "/ibc.lightclients.lcp.v1.RegisterEnclaveKeyMessage"; + string constant ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL = + "/ibc.lightclients.lcp.v1.ZKDCAPRegisterEnclaveKeyMessage"; string constant UPDATE_OPERATORS_MESSAGE_TYPE_URL = "/ibc.lightclients.lcp.v1.UpdateOperatorsMessage"; string constant CLIENT_STATE_TYPE_URL = "/ibc.lightclients.lcp.v1.ClientState"; string constant CONSENSUS_STATE_TYPE_URL = "/ibc.lightclients.lcp.v1.ConsensusState"; @@ -20,6 +23,8 @@ library LCPProtoMarshaler { bytes32 constant UPDATE_CLIENT_MESSAGE_TYPE_URL_HASH = keccak256(abi.encodePacked(UPDATE_CLIENT_MESSAGE_TYPE_URL)); bytes32 constant REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH = keccak256(abi.encodePacked(REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL)); + bytes32 constant ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH = + keccak256(abi.encodePacked(ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL)); bytes32 constant UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH = keccak256(abi.encodePacked(UPDATE_OPERATORS_MESSAGE_TYPE_URL)); bytes32 constant CLIENT_STATE_TYPE_URL_HASH = keccak256(abi.encodePacked(CLIENT_STATE_TYPE_URL)); @@ -47,6 +52,13 @@ library LCPProtoMarshaler { return Any.encode(any); } + function marshal(ZKDCAPRegisterEnclaveKeyMessage.Data calldata message) public pure returns (bytes memory) { + Any.Data memory any; + any.type_url = ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL; + any.value = ZKDCAPRegisterEnclaveKeyMessage.encode(message); + return Any.encode(any); + } + function marshal(ClientState.Data calldata clientState) public pure returns (bytes memory) { Any.Data memory anyClientState; anyClientState.type_url = CLIENT_STATE_TYPE_URL; @@ -74,6 +86,10 @@ library LCPProtoMarshaler { } else if (typeUrlHash == REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) { RegisterEnclaveKeyMessage.Data memory message = RegisterEnclaveKeyMessage.decode(anyClientMessage.value); return (typeUrlHash, abi.encode(clientId, message)); + } else if (typeUrlHash == ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) { + ZKDCAPRegisterEnclaveKeyMessage.Data memory message = + ZKDCAPRegisterEnclaveKeyMessage.decode(anyClientMessage.value); + return (typeUrlHash, abi.encode(clientId, message)); } else if (typeUrlHash == UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH) { UpdateOperatorsMessage.Data memory message = UpdateOperatorsMessage.decode(anyClientMessage.value); return (typeUrlHash, abi.encode(clientId, message)); diff --git a/contracts/proto/ibc/lightclients/lcp/v1/LCP.sol b/contracts/proto/ibc/lightclients/lcp/v1/LCP.sol index daa492c..56b7686 100644 --- a/contracts/proto/ibc/lightclients/lcp/v1/LCP.sol +++ b/contracts/proto/ibc/lightclients/lcp/v1/LCP.sol @@ -641,6 +641,300 @@ library IbcLightclientsLcpV1RegisterEnclaveKeyMessage { } //library IbcLightclientsLcpV1RegisterEnclaveKeyMessage +library IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage { + + + //struct definition + struct Data { + bytes commit; + bytes proof; + bytes operator_signature; + } + + // Decoder section + + /** + * @dev The main decoder for memory + * @param bs The bytes array to be decoded + * @return The decoded struct + */ + function decode(bytes memory bs) internal pure returns (Data memory) { + (Data memory x, ) = _decode(32, bs, bs.length); + return x; + } + + /** + * @dev The main decoder for storage + * @param self The in-storage struct + * @param bs The bytes array to be decoded + */ + function decode(Data storage self, bytes memory bs) internal { + (Data memory x, ) = _decode(32, bs, bs.length); + store(x, self); + } + // inner decoder + + /** + * @dev The decoder for internal usage + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param sz The number of bytes expected + * @return The decoded struct + * @return The number of bytes decoded + */ + function _decode(uint256 p, bytes memory bs, uint256 sz) + internal + pure + returns (Data memory, uint) + { + Data memory r; + uint256 fieldId; + ProtoBufRuntime.WireType wireType; + uint256 bytesRead; + uint256 offset = p; + uint256 pointer = p; + while (pointer < offset + sz) { + (fieldId, wireType, bytesRead) = ProtoBufRuntime._decode_key(pointer, bs); + pointer += bytesRead; + if (fieldId == 1) { + pointer += _read_commit(pointer, bs, r); + } else + if (fieldId == 2) { + pointer += _read_proof(pointer, bs, r); + } else + if (fieldId == 3) { + pointer += _read_operator_signature(pointer, bs, r); + } else + { + pointer += ProtoBufRuntime._skip_field_decode(wireType, pointer, bs); + } + + } + return (r, sz); + } + + // field readers + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_commit( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (bytes memory x, uint256 sz) = ProtoBufRuntime._decode_bytes(p, bs); + r.commit = x; + return sz; + } + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_proof( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (bytes memory x, uint256 sz) = ProtoBufRuntime._decode_bytes(p, bs); + r.proof = x; + return sz; + } + + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_operator_signature( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (bytes memory x, uint256 sz) = ProtoBufRuntime._decode_bytes(p, bs); + r.operator_signature = x; + return sz; + } + + + // Encoder section + + /** + * @dev The main encoder for memory + * @param r The struct to be encoded + * @return The encoded byte array + */ + function encode(Data memory r) internal pure returns (bytes memory) { + bytes memory bs = new bytes(_estimate(r)); + uint256 sz = _encode(r, 32, bs); + assembly { + mstore(bs, sz) + } + return bs; + } + // inner encoder + + /** + * @dev The encoder for internal usage + * @param r The struct to be encoded + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @return The number of bytes encoded + */ + function _encode(Data memory r, uint256 p, bytes memory bs) + internal + pure + returns (uint) + { + uint256 offset = p; + uint256 pointer = p; + + if (r.commit.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 1, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_bytes(r.commit, pointer, bs); + } + if (r.proof.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 2, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_bytes(r.proof, pointer, bs); + } + if (r.operator_signature.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 3, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_bytes(r.operator_signature, pointer, bs); + } + return pointer - offset; + } + // nested encoder + + /** + * @dev The encoder for inner struct + * @param r The struct to be encoded + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @return The number of bytes encoded + */ + function _encode_nested(Data memory r, uint256 p, bytes memory bs) + internal + pure + returns (uint) + { + /** + * First encoded `r` into a temporary array, and encode the actual size used. + * Then copy the temporary array into `bs`. + */ + uint256 offset = p; + uint256 pointer = p; + bytes memory tmp = new bytes(_estimate(r)); + uint256 tmpAddr = ProtoBufRuntime.getMemoryAddress(tmp); + uint256 bsAddr = ProtoBufRuntime.getMemoryAddress(bs); + uint256 size = _encode(r, 32, tmp); + pointer += ProtoBufRuntime._encode_varint(size, pointer, bs); + ProtoBufRuntime.copyBytes(tmpAddr + 32, bsAddr + pointer, size); + pointer += size; + delete tmp; + return pointer - offset; + } + // estimator + + /** + * @dev The estimator for a struct + * @param r The struct to be encoded + * @return The number of bytes encoded in estimation + */ + function _estimate( + Data memory r + ) internal pure returns (uint) { + uint256 e; + e += 1 + ProtoBufRuntime._sz_lendelim(r.commit.length); + e += 1 + ProtoBufRuntime._sz_lendelim(r.proof.length); + e += 1 + ProtoBufRuntime._sz_lendelim(r.operator_signature.length); + return e; + } + // empty checker + + function _empty( + Data memory r + ) internal pure returns (bool) { + + if (r.commit.length != 0) { + return false; + } + + if (r.proof.length != 0) { + return false; + } + + if (r.operator_signature.length != 0) { + return false; + } + + return true; + } + + + //store function + /** + * @dev Store in-memory struct to storage + * @param input The in-memory struct + * @param output The in-storage struct + */ + function store(Data memory input, Data storage output) internal { + output.commit = input.commit; + output.proof = input.proof; + output.operator_signature = input.operator_signature; + + } + + + + //utility functions + /** + * @dev Return an empty struct + * @return r The empty struct + */ + function nil() internal pure returns (Data memory r) { + assembly { + r := 0 + } + } + + /** + * @dev Test whether a struct is empty + * @param x The struct to be tested + * @return r True if it is empty + */ + function isNil(Data memory x) internal pure returns (bool r) { + assembly { + r := iszero(x) + } + } +} +//library IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage + library IbcLightclientsLcpV1UpdateOperatorsMessage { @@ -1110,6 +1404,7 @@ library IbcLightclientsLcpV1ClientState { uint64 operators_nonce; uint64 operators_threshold_numerator; uint64 operators_threshold_denominator; + bytes zkdcap_verifier_info; } // Decoder section @@ -1149,7 +1444,7 @@ library IbcLightclientsLcpV1ClientState { returns (Data memory, uint) { Data memory r; - uint[11] memory counters; + uint[12] memory counters; uint256 fieldId; ProtoBufRuntime.WireType wireType; uint256 bytesRead; @@ -1188,6 +1483,9 @@ library IbcLightclientsLcpV1ClientState { if (fieldId == 10) { pointer += _read_operators_threshold_denominator(pointer, bs, r); } else + if (fieldId == 11) { + pointer += _read_zkdcap_verifier_info(pointer, bs, r); + } else { pointer += ProtoBufRuntime._skip_field_decode(wireType, pointer, bs); } @@ -1308,7 +1606,7 @@ library IbcLightclientsLcpV1ClientState { uint256 p, bytes memory bs, Data memory r, - uint[11] memory counters + uint[12] memory counters ) internal pure returns (uint) { /** * if `r` is NULL, then only counting the number of fields. @@ -1335,7 +1633,7 @@ library IbcLightclientsLcpV1ClientState { uint256 p, bytes memory bs, Data memory r, - uint[11] memory counters + uint[12] memory counters ) internal pure returns (uint) { /** * if `r` is NULL, then only counting the number of fields. @@ -1362,7 +1660,7 @@ library IbcLightclientsLcpV1ClientState { uint256 p, bytes memory bs, Data memory r, - uint[11] memory counters + uint[12] memory counters ) internal pure returns (uint) { /** * if `r` is NULL, then only counting the number of fields. @@ -1428,6 +1726,23 @@ library IbcLightclientsLcpV1ClientState { return sz; } + /** + * @dev The decoder for reading a field + * @param p The offset of bytes array to start decode + * @param bs The bytes array to be decoded + * @param r The in-memory struct + * @return The number of bytes decoded + */ + function _read_zkdcap_verifier_info( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (bytes memory x, uint256 sz) = ProtoBufRuntime._decode_bytes(p, bs); + r.zkdcap_verifier_info = x; + return sz; + } + // struct decoder /** * @dev The decoder for reading a inner struct field @@ -1577,6 +1892,15 @@ library IbcLightclientsLcpV1ClientState { ); pointer += ProtoBufRuntime._encode_uint64(r.operators_threshold_denominator, pointer, bs); } + if (r.zkdcap_verifier_info.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 11, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_bytes(r.zkdcap_verifier_info, pointer, bs); + } return pointer - offset; } // nested encoder @@ -1636,6 +1960,7 @@ library IbcLightclientsLcpV1ClientState { e += 1 + ProtoBufRuntime._sz_uint64(r.operators_nonce); e += 1 + ProtoBufRuntime._sz_uint64(r.operators_threshold_numerator); e += 1 + ProtoBufRuntime._sz_uint64(r.operators_threshold_denominator); + e += 1 + ProtoBufRuntime._sz_lendelim(r.zkdcap_verifier_info.length); return e; } // empty checker @@ -1680,6 +2005,10 @@ library IbcLightclientsLcpV1ClientState { return false; } + if (r.zkdcap_verifier_info.length != 0) { + return false; + } + return true; } @@ -1701,6 +2030,7 @@ library IbcLightclientsLcpV1ClientState { output.operators_nonce = input.operators_nonce; output.operators_threshold_numerator = input.operators_threshold_numerator; output.operators_threshold_denominator = input.operators_threshold_denominator; + output.zkdcap_verifier_info = input.zkdcap_verifier_info; } diff --git a/lib/risc0-ethereum b/lib/risc0-ethereum new file mode 160000 index 0000000..4fa7de0 --- /dev/null +++ b/lib/risc0-ethereum @@ -0,0 +1 @@ +Subproject commit 4fa7de055d461b7fa948eb56107b7a172459e8fc diff --git a/proto/ibc/lightclients/lcp/v1/LCP.proto b/proto/ibc/lightclients/lcp/v1/LCP.proto index 2d3e9ad..96db2c8 100644 --- a/proto/ibc/lightclients/lcp/v1/LCP.proto +++ b/proto/ibc/lightclients/lcp/v1/LCP.proto @@ -17,6 +17,12 @@ message RegisterEnclaveKeyMessage { bytes operator_signature = 4; } +message ZKDCAPRegisterEnclaveKeyMessage { + bytes commit = 1; + bytes proof = 2; + bytes operator_signature = 3; +} + message UpdateOperatorsMessage { uint64 nonce = 1; repeated bytes new_operators = 2; @@ -38,6 +44,15 @@ message ClientState { uint64 operators_nonce = 8; uint64 operators_threshold_numerator = 9; uint64 operators_threshold_denominator = 10; + // An optional field to store the verifier info for zkDCAP + // + // if empty, the zkDCAP is not enabled + // otherwise, the zkDCAP is enabled + // the bytes layout is the following: + // 0: zkVM type (e.g. 1 for risc0) + // 1-31: reserved + // 32-64: guest program identifier + bytes zkdcap_verifier_info = 11; } message ConsensusState { diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..321f9ec --- /dev/null +++ b/remappings.txt @@ -0,0 +1 @@ +openzeppelin/=lib/risc0-ethereum/lib/openzeppelin-contracts/ diff --git a/test/LCPClientOperator.t.sol b/test/LCPClientOperator.t.sol index 9dddfd8..6eb68bc 100644 --- a/test/LCPClientOperator.t.sol +++ b/test/LCPClientOperator.t.sol @@ -30,7 +30,7 @@ contract LCPClientOperatorTest is BasicTest { // ---------------------------- Test Cases ---------------------------- function testPreComputationValues() public pure { - assertEq(LCPOperator.domainSeparatorUniversal(), LCPOperator.DOMAIN_SEPARATOR_REGISTER_ENCLAVE_KEY); + assertEq(LCPOperator.domainSeparatorUniversal(), LCPOperator.DOMAIN_SEPARATOR_LCP_CLIENT); assertEq(LCPOperator.chainTypeSalt(LCPOperator.CHAIN_TYPE_EVM, hex""), LCPOperator.CHAIN_TYPE_EVM_SALT); } diff --git a/test/LCPClientTest.t.sol b/test/LCPClientTest.t.sol index 9752963..aaefd27 100644 --- a/test/LCPClientTest.t.sol +++ b/test/LCPClientTest.t.sol @@ -133,7 +133,7 @@ contract LCPClientTest is BasicTest { message = createUpdateClientMessage(dataList[i].path); // staticcall is expected to succeed because updateClient does not update the state if the message is already processed (bool success, bytes memory ret) = address(lc).staticcall( - abi.encodeWithSelector(LCPClientBase.updateClient.selector, clientId, message) + abi.encodeWithSelector(LCPClientCommon.updateClient.selector, clientId, message) ); require(success, "failed to update duplicated client"); heights = abi.decode(ret, (Height.Data[])); diff --git a/test/ZKDCAPVerifier.t.sol b/test/ZKDCAPVerifier.t.sol new file mode 100644 index 0000000..eb0164d --- /dev/null +++ b/test/ZKDCAPVerifier.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +import {BasicTest} from "./TestHelper.t.sol"; +import {IRiscZeroVerifier} from "risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import {RiscZeroGroth16Verifier, ControlID} from "risc0-ethereum/contracts/src/groth16/RiscZeroGroth16Verifier.sol"; +import {DCAPValidator} from "../contracts/DCAPValidator.sol"; + +contract ZKDCAPVerifierTest is BasicTest { + IRiscZeroVerifier verifier; + + function setUp() public { + verifier = new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); + } + + /* + "zkp":{"Risc0":{ + "image_id":"e45b67a3c24ff3b77f87fec1533dca31524fc19f02bd433d4e6bba729a7646a7", + "seal":"50bd1769188540e643a5e4b1548e4c9391b0359afc1488d25fbfe41395e8847079f64d55148c07b36f0d2d44bfbdcdbe9fc79b48062a75dec02bbd5bfd5e3e530f8fa1520a5f1d99b7cf0bd29b0dbdb4fa65186559593e2c415f1e8ce27ab302cacc917a1db4a97e49f4d82194363c3af262c3b0bcf57fe846130012d081cc8c1fc0337d0de1958f4e4c5755815559104d7576a3bfc0f5fffdb630eace4cc76a5f3b617210692dedcde61b1e581a1700476ae51fa573e0adc0405dcef88e6b902f1364be01080d0fbc1429093d77b320405ff81037e7d1ba6e029baa155b71283e10cbee1e6f5375ed061c83c8ce7e3123774ce8debfd9e90e34c95429eda72d688594b1", + "commit":"a10b726700000000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d70090003000000000100906ed5000015150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000dca1a1841ab2e3fa7025c1d175d2c947df760b3baa4a9a0f30f4fd05718fcfe3000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fc8f819e864fd03a5d377061e8148d6e5143679000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000" + }} + */ + function testVerify1() public view { + verifier.verify( + hex"50bd1769188540e643a5e4b1548e4c9391b0359afc1488d25fbfe41395e8847079f64d55148c07b36f0d2d44bfbdcdbe9fc79b48062a75dec02bbd5bfd5e3e530f8fa1520a5f1d99b7cf0bd29b0dbdb4fa65186559593e2c415f1e8ce27ab302cacc917a1db4a97e49f4d82194363c3af262c3b0bcf57fe846130012d081cc8c1fc0337d0de1958f4e4c5755815559104d7576a3bfc0f5fffdb630eace4cc76a5f3b617210692dedcde61b1e581a1700476ae51fa573e0adc0405dcef88e6b902f1364be01080d0fbc1429093d77b320405ff81037e7d1ba6e029baa155b71283e10cbee1e6f5375ed061c83c8ce7e3123774ce8debfd9e90e34c95429eda72d688594b1", + hex"e45b67a3c24ff3b77f87fec1533dca31524fc19f02bd433d4e6bba729a7646a7", + sha256( + hex"a10b726700000000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d70090003000000000100906ed5000015150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000dca1a1841ab2e3fa7025c1d175d2c947df760b3baa4a9a0f30f4fd05718fcfe3000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fc8f819e864fd03a5d377061e8148d6e5143679000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000" + ) + ); + } + + /* + "zkp":{"Risc0":{ + "image_id":"200d1f40b5733d31e4f3bfb1f106351e878aea304b5c9e73690b9e18e2e77bb6", + "seal":"50bd1769039405f7898272862594bc436f08dab3e5c1200e0c44b542b462fe09bb2655b700ae0374f99c5b30f8580605267de7b5d121758ae405b5b378b588deca3042a70e22eefd598a4638d7c039ffb737f45dbee0152559f25c4dc0854ab7fd7cc2f52704dc9991d84e00c48d155b0d7fbfe2d307d1fb1a573c38083d86bea39e60191110517565779f631fc0028b82ff9b224bcc627bd6fd3ee1c1ffcea67e9e921c281abaaeacabff9fb74232c9ca2914ce6fe3ec9a4584c68a888339f2e6865c9f2bb57d5a38759c09ecb3f9f9f80fd582f23b7fb47495f783a8cc2eedb99ca18b0337102df4a2e325367d954ea5154f11c03f459551f849763d75ccad9d6665c5", + "commit":"00000003000000000500906ed50000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d700900000000679885950000000067c00a9c15150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000813c146e403f203f2784fa222b3edeac70727dee21c0b08f74883aa189e7b0ed000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017dcf7408c72ebe9076aebbb208d2c85e62050db4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000" + }} + */ + function testVerify2() public view { + verifier.verify( + hex"50bd1769039405f7898272862594bc436f08dab3e5c1200e0c44b542b462fe09bb2655b700ae0374f99c5b30f8580605267de7b5d121758ae405b5b378b588deca3042a70e22eefd598a4638d7c039ffb737f45dbee0152559f25c4dc0854ab7fd7cc2f52704dc9991d84e00c48d155b0d7fbfe2d307d1fb1a573c38083d86bea39e60191110517565779f631fc0028b82ff9b224bcc627bd6fd3ee1c1ffcea67e9e921c281abaaeacabff9fb74232c9ca2914ce6fe3ec9a4584c68a888339f2e6865c9f2bb57d5a38759c09ecb3f9f9f80fd582f23b7fb47495f783a8cc2eedb99ca18b0337102df4a2e325367d954ea5154f11c03f459551f849763d75ccad9d6665c5", + hex"200d1f40b5733d31e4f3bfb1f106351e878aea304b5c9e73690b9e18e2e77bb6", + sha256( + hex"00000003000000000500906ed50000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d700900000000679885950000000067c00a9c15150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000813c146e403f203f2784fa222b3edeac70727dee21c0b08f74883aa189e7b0ed000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017dcf7408c72ebe9076aebbb208d2c85e62050db4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000" + ) + ); + } + + function testParseCommit() public view { + bytes memory commit = + hex"00000003000000000500906ed50000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d700900000000679885950000000067c00a9c15150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000813c146e403f203f2784fa222b3edeac70727dee21c0b08f74883aa189e7b0ed000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017dcf7408c72ebe9076aebbb208d2c85e62050db4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000"; + DCAPValidator.Output memory output = DCAPValidatorTestHelper.parseCommit(commit); + assertEq( + output.sgxIntelRootCAHash, bytes32(hex"a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009") + ); + assertTrue(output.tcbStatus == DCAPValidator.TCBStatus.SWHardeningNeeded); + assertFalse(output.enclaveDebugEnabled); + assertEq(output.mrenclave, bytes32(hex"813c146e403f203f2784fa222b3edeac70727dee21c0b08f74883aa189e7b0ed")); + assertEq(output.enclaveKey, address(bytes20(hex"7dcf7408c72ebe9076aebbb208d2c85e62050db4"))); + assertEq(output.operator, address(0)); + assertEq(output.advisoryIDs.length, 2); + assertEq(output.advisoryIDs[0], "INTEL-SA-00334"); + assertEq(output.advisoryIDs[1], "INTEL-SA-00615"); + } + + function testParseCommitEnclaveDebugEnabled() public view { + bytes memory commit = + hex"00000003000000000500906ed50000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d700900000000679afeb70000000067c2831415150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000000700000000000000813c146e403f203f2784fa222b3edeac70727dee21c0b08f74883aa189e7b0ed000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017c10dd734cac9a4588b7886b2a4820cba182907d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000"; + DCAPValidator.Output memory output = DCAPValidatorTestHelper.parseCommit(commit); + assertEq( + output.sgxIntelRootCAHash, bytes32(hex"a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d7009") + ); + assertTrue(output.tcbStatus == DCAPValidator.TCBStatus.SWHardeningNeeded); + assertTrue(output.enclaveDebugEnabled); + assertEq(output.mrenclave, bytes32(hex"813c146e403f203f2784fa222b3edeac70727dee21c0b08f74883aa189e7b0ed")); + assertEq(output.enclaveKey, address(bytes20(hex"7c10dd734cac9a4588b7886b2a4820cba182907d"))); + assertEq(output.operator, address(0)); + assertEq(output.advisoryIDs.length, 2); + assertEq(output.advisoryIDs[0], "INTEL-SA-00334"); + assertEq(output.advisoryIDs[1], "INTEL-SA-00615"); + } +} + +library DCAPValidatorTestHelper { + function parseCommit(bytes calldata commit) public pure returns (DCAPValidator.Output memory) { + return DCAPValidator.parseCommit(commit); + } +}