From 4a111bad50ac0fc8603f26892d185622e9c56af0 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Fri, 31 Jan 2025 16:50:19 +0900 Subject: [PATCH] add `LCPClientZKDCAP` implementation Signed-off-by: Jun Kimura --- .github/workflows/test.yml | 2 +- .gitmodules | 3 + Makefile | 2 +- contracts/DCAPValidator.sol | 74 ++++ contracts/ILCPClientErrors.sol | 14 + contracts/LCPClientBase.sol | 3 + contracts/LCPClientZKDCAP.sol | 10 + contracts/LCPClientZKDCAPBase.sol | 194 ++++++++ .../LCPClientZKDCAPOwnableUpgradeable.sol | 21 + contracts/LCPOperator.sol | 14 + contracts/LCPProtoMarshaler.sol | 16 + .../proto/ibc/lightclients/lcp/v1/LCP.sol | 413 +++++++++++++++++- lib/risc0-ethereum | 1 + proto/ibc/lightclients/lcp/v1/LCP.proto | 19 + remappings.txt | 1 + slither.config.json | 2 +- test/ZKDCAPVerifier.t.sol | 89 ++++ 17 files changed, 871 insertions(+), 7 deletions(-) create mode 100644 contracts/DCAPValidator.sol create mode 100644 contracts/LCPClientZKDCAP.sol create mode 100644 contracts/LCPClientZKDCAPBase.sol create mode 100644 contracts/LCPClientZKDCAPOwnableUpgradeable.sol create mode 160000 lib/risc0-ethereum create mode 100644 remappings.txt create mode 100644 test/ZKDCAPVerifier.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 945a786..808191e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: Test on: [pull_request] env: - SOLC_VERSION: 0.8.20 + SOLC_VERSION: 0.8.28 jobs: contract-test: 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..03e4d72 --- /dev/null +++ b/contracts/DCAPValidator.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +library DCAPValidator { + uint256 internal constant SGX_QUOTE_BODY_OFFSET = 63; + uint256 internal constant ATTRIBUTES_OFFSET = SGX_QUOTE_BODY_OFFSET + 16 + 4 + 28; + uint256 internal constant MRENCLAVE_OFFSET = ATTRIBUTES_OFFSET + 16; + uint256 internal constant MRENCLAVE_END_OFFSET = MRENCLAVE_OFFSET + 32; + uint256 internal constant REPORT_DATA_OFFSET = SGX_QUOTE_BODY_OFFSET + 320; + uint256 internal constant REPORT_DATA_ENCLAVE_KEY_OFFSET = REPORT_DATA_OFFSET + 1; + uint256 internal constant REPORT_DATA_OPERATOR_OFFSET = REPORT_DATA_ENCLAVE_KEY_OFFSET + 20; + uint256 internal constant REPORT_DATA_OPERATOR_END_OFFSET = REPORT_DATA_OPERATOR_OFFSET + 20; + uint256 internal constant ADVISORY_IDS_OFFSET = REPORT_DATA_OFFSET + 64; + + uint8 internal constant TCB_STATUS_UP_TO_DATE = 0; + uint8 internal constant TCB_STATUS_OUT_OF_DATE = 1; + uint8 internal constant TCB_STATUS_REVOKED = 2; + uint8 internal constant TCB_STATUS_CONFIGURATION_NEEDED = 3; + uint8 internal constant TCB_STATUS_OUT_OF_DATE_CONFIGURATION_NEEDED = 4; + uint8 internal constant TCB_STATUS_SW_HARDENING_NEEDED = 5; + uint8 internal constant TCB_STATUS_CONFIGURATION_AND_SW_HARDENING_NEEDED = 6; + + struct Output { + uint8 tcbStatus; + bytes32 sgxIntelRootCAHash; + uint64 validityNotBeforeMax; + uint64 validityNotAfterMin; + bool enclaveDebugEnabled; + bytes32 mrenclave; + address enclaveKey; + address operator; + string[] advisoryIDs; + } + + function parseCommit(bytes calldata commit) public 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 = uint8(commit[8]); + output.sgxIntelRootCAHash = bytes32(commit[15:47]); + output.validityNotBeforeMax = uint64(bytes8(commit[47:55])); + output.validityNotAfterMin = uint64(bytes8(commit[55:63])); + output.enclaveDebugEnabled = uint8(commit[ATTRIBUTES_OFFSET]) & uint8(2) != 0; + output.mrenclave = bytes32(commit[MRENCLAVE_OFFSET:MRENCLAVE_END_OFFSET]); + + require(commit[REPORT_DATA_OFFSET] == 0x01, "unexpected report data version"); + output.enclaveKey = address(bytes20(commit[REPORT_DATA_ENCLAVE_KEY_OFFSET:REPORT_DATA_OPERATOR_OFFSET])); + output.operator = address(bytes20(commit[REPORT_DATA_OPERATOR_OFFSET:REPORT_DATA_OPERATOR_END_OFFSET])); + output.advisoryIDs = abi.decode(commit[ADVISORY_IDS_OFFSET:commit.length], (string[])); + return output; + } + + function tcbStatusToString(uint8 tcbStatus) public pure returns (string memory) { + if (tcbStatus == TCB_STATUS_UP_TO_DATE) { + return "UpToDate"; + } else if (tcbStatus == TCB_STATUS_OUT_OF_DATE) { + return "OutOfDate"; + } else if (tcbStatus == TCB_STATUS_REVOKED) { + return "Revoked"; + } else if (tcbStatus == TCB_STATUS_CONFIGURATION_NEEDED) { + return "ConfigurationNeeded"; + } else if (tcbStatus == TCB_STATUS_OUT_OF_DATE_CONFIGURATION_NEEDED) { + return "OutOfDateConfigurationNeeded"; + } else if (tcbStatus == TCB_STATUS_SW_HARDENING_NEEDED) { + return "SWHardeningNeeded"; + } else if (tcbStatus == TCB_STATUS_CONFIGURATION_AND_SW_HARDENING_NEEDED) { + return "ConfigurationAndSWHardeningNeeded"; + } else { + revert("unexpected TCB status"); + } + } +} diff --git a/contracts/ILCPClientErrors.sol b/contracts/ILCPClientErrors.sol index be2b1dd..a7ba8a8 100644 --- a/contracts/ILCPClientErrors.sol +++ b/contracts/ILCPClientErrors.sol @@ -58,4 +58,18 @@ interface ILCPClientErrors { error LCPClientUpdateOperatorsPermissionless(); error LCPClientUpdateOperatorsSignatureUnexpectedOperator(address actual, address expected); + + error LCPClientZKDCAPInvalidConstructorParams(); + error LCPClientZKDCAPOutputNotValid(); + error LCPClientZKDCAPUnrecognizedTCBStatus(); + error LCPClientZKDCAPInvalidVerifierInfos(); + error LCPClientZKDCAPInvalidVerifierInfoLength(); + error LCPClientZKDCAPInvalidVerifierInfoZKVMType(); + error LCPClientZKDCAPUnsupportedZKVMType(); + error LCPClientZKDCAPRisc0ImageIdNotSet(); + error LCPClientZKDCAPUnexpectedIntelRootCAHash(); + + error LCPClientZKDCAPDisallowedTCBStatus(); + error LCPClientZKDCAPDisallowedAdvisoryID(); + error LCPClientZKDCAPUnexpectedEnclaveDebugMode(); } diff --git a/contracts/LCPClientBase.sol b/contracts/LCPClientBase.sol index dd423be..146dadf 100644 --- a/contracts/LCPClientBase.sol +++ b/contracts/LCPClientBase.sol @@ -34,11 +34,14 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { struct ClientStorage { ProtoClientState.Data clientState; + uint256[50] __gap0; RemoteAttestation.ReportAllowedStatus allowedStatuses; + uint256[50] __gap1; // height => consensus state mapping(uint128 => ConsensusState) consensusStates; // enclave key => EKInfo mapping(address => EKInfo) ekInfos; + bytes32 zkDCAPRisc0ImageId; } // --------------------- Immutable fields --------------------- 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..558650c --- /dev/null +++ b/contracts/LCPClientZKDCAPBase.sol @@ -0,0 +1,194 @@ +// 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 {LCPClientBase} from "./LCPClientBase.sol"; +import {LCPOperator} from "./LCPOperator.sol"; +import {RemoteAttestation} from "./RemoteAttestation.sol"; +import {DCAPValidator} from "./DCAPValidator.sol"; + +abstract contract LCPClientZKDCAPBase is LCPClientBase { + using IBCHeight for Height.Data; + // --------------------- Constants --------------------- + + uint8 internal constant ZKVM_TYPE_RISC_ZERO = 0x01; + + // --------------------- 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_) + LCPClientBase(ibcHandler_) + { + if (intelRootCA.length == 0 || riscZeroVerifier_ == address(0)) { + revert LCPClientZKDCAPInvalidConstructorParams(); + } + 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); + if (clientState.zkdcap_verifier_infos.length != 1) { + revert LCPClientZKDCAPInvalidVerifierInfos(); + } + bytes memory verifierInfo = clientState.zkdcap_verifier_infos[0]; + if (verifierInfo.length != 64) { + revert LCPClientZKDCAPInvalidVerifierInfoLength(); + } + if (uint8(verifierInfo[0]) != ZKVM_TYPE_RISC_ZERO) { + revert LCPClientZKDCAPInvalidVerifierInfoZKVMType(); + } + // 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) + { + if (message.zkvm_type != ZKVM_TYPE_RISC_ZERO) { + revert LCPClientZKDCAPUnsupportedZKVMType(); + } + ClientStorage storage clientStorage = clientStorages[clientId]; + if (clientStorage.zkDCAPRisc0ImageId == bytes32(0)) { + revert LCPClientZKDCAPRisc0ImageIdNotSet(); + } + riscZeroVerifier.verify(message.proof, clientStorage.zkDCAPRisc0ImageId, sha256(message.commit)); + DCAPValidator.Output memory output = DCAPValidator.parseCommit(message.commit); + if (output.sgxIntelRootCAHash != intelRootCAHash) { + revert LCPClientZKDCAPUnexpectedIntelRootCAHash(); + } + if (output.mrenclave != bytes32(clientStorage.clientState.mrenclave)) { + revert LCPClientClientStateUnexpectedMrenclave(); + } + + if ( + clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(output.tcbStatus)] + != RemoteAttestation.FLAG_ALLOWED + ) { + revert LCPClientZKDCAPDisallowedTCBStatus(); + } + for (uint256 i = 0; i < output.advisoryIDs.length; i++) { + if ( + clientStorage.allowedStatuses.allowedAdvisories[output.advisoryIDs[i]] != RemoteAttestation.FLAG_ALLOWED + ) { + revert LCPClientZKDCAPDisallowedAdvisoryID(); + } + } + if (output.enclaveDebugEnabled != developmentMode) { + revert LCPClientZKDCAPUnexpectedEnclaveDebugMode(); + } + + // 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_infos[0], 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 LCPClientZKDCAPOutputNotValid(); + } + 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/LCPClientZKDCAPOwnableUpgradeable.sol b/contracts/LCPClientZKDCAPOwnableUpgradeable.sol new file mode 100644 index 0000000..a97c80e --- /dev/null +++ b/contracts/LCPClientZKDCAPOwnableUpgradeable.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +/// @custom:oz-upgrades-unsafe-allow external-library-linking +contract LCPClientZKDCAPOwnableUpgradeable is LCPClientZKDCAPBase, UUPSUpgradeable, OwnableUpgradeable { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier_) + LCPClientZKDCAPBase(ibcHandler_, developmentMode_, intelRootCA, riscZeroVerifier_) + {} + + function initialize() public initializer { + __UUPSUpgradeable_init(); + __Ownable_init(msg.sender); + } + + function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} +} diff --git a/contracts/LCPOperator.sol b/contracts/LCPOperator.sol index dd1440e..c5c085b 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)" ); @@ -54,6 +56,18 @@ library LCPOperator { ); } + function computeEIP712ZKDCAPRegisterEnclaveKey(bytes memory zkdcapVerifierInfo, bytes32 commitHash) + internal + pure + returns (bytes memory) + { + return abi.encodePacked( + hex"1901", + DOMAIN_SEPARATOR_LCP_CLIENT, + keccak256(abi.encode(TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY, keccak256(zkdcapVerifierInfo), commitHash)) + ); + } + function computeEIP712UpdateOperators( string calldata clientId, uint64 nonce, 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..5149bc6 100644 --- a/contracts/proto/ibc/lightclients/lcp/v1/LCP.sol +++ b/contracts/proto/ibc/lightclients/lcp/v1/LCP.sol @@ -641,6 +641,336 @@ library IbcLightclientsLcpV1RegisterEnclaveKeyMessage { } //library IbcLightclientsLcpV1RegisterEnclaveKeyMessage +library IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage { + + + //struct definition + struct Data { + uint32 zkvm_type; + 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_zkvm_type(pointer, bs, r); + } else + if (fieldId == 2) { + pointer += _read_commit(pointer, bs, r); + } else + if (fieldId == 3) { + pointer += _read_proof(pointer, bs, r); + } else + if (fieldId == 4) { + 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_zkvm_type( + uint256 p, + bytes memory bs, + Data memory r + ) internal pure returns (uint) { + (uint32 x, uint256 sz) = ProtoBufRuntime._decode_uint32(p, bs); + r.zkvm_type = 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_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.zkvm_type != 0) { + pointer += ProtoBufRuntime._encode_key( + 1, + ProtoBufRuntime.WireType.Varint, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_uint32(r.zkvm_type, pointer, bs); + } + if (r.commit.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 2, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_bytes(r.commit, pointer, bs); + } + if (r.proof.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 3, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs + ); + pointer += ProtoBufRuntime._encode_bytes(r.proof, pointer, bs); + } + if (r.operator_signature.length != 0) { + pointer += ProtoBufRuntime._encode_key( + 4, + 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_uint32(r.zkvm_type); + 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.zkvm_type != 0) { + return false; + } + + 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.zkvm_type = input.zkvm_type; + 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 +1440,7 @@ library IbcLightclientsLcpV1ClientState { uint64 operators_nonce; uint64 operators_threshold_numerator; uint64 operators_threshold_denominator; + bytes[] zkdcap_verifier_infos; } // Decoder section @@ -1149,7 +1480,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 +1519,9 @@ library IbcLightclientsLcpV1ClientState { if (fieldId == 10) { pointer += _read_operators_threshold_denominator(pointer, bs, r); } else + if (fieldId == 11) { + pointer += _read_unpacked_repeated_zkdcap_verifier_infos(pointer, bs, nil(), counters); + } else { pointer += ProtoBufRuntime._skip_field_decode(wireType, pointer, bs); } @@ -1206,6 +1540,10 @@ library IbcLightclientsLcpV1ClientState { require(r.operators.length == 0); r.operators = new bytes[](counters[7]); } + if (counters[11] > 0) { + require(r.zkdcap_verifier_infos.length == 0); + r.zkdcap_verifier_infos = new bytes[](counters[11]); + } while (pointer < offset + sz) { (fieldId, wireType, bytesRead) = ProtoBufRuntime._decode_key(pointer, bs); @@ -1219,6 +1557,9 @@ library IbcLightclientsLcpV1ClientState { if (fieldId == 7) { pointer += _read_unpacked_repeated_operators(pointer, bs, r, counters); } else + if (fieldId == 11) { + pointer += _read_unpacked_repeated_zkdcap_verifier_infos(pointer, bs, r, counters); + } else { pointer += ProtoBufRuntime._skip_field_decode(wireType, pointer, bs); } @@ -1308,7 +1649,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 +1676,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 +1703,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 +1769,33 @@ 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 + * @param counters The counters for repeated fields + * @return The number of bytes decoded + */ + function _read_unpacked_repeated_zkdcap_verifier_infos( + uint256 p, + bytes memory bs, + Data memory r, + uint[12] memory counters + ) internal pure returns (uint) { + /** + * if `r` is NULL, then only counting the number of fields. + */ + (bytes memory x, uint256 sz) = ProtoBufRuntime._decode_bytes(p, bs); + if (isNil(r)) { + counters[11] += 1; + } else { + r.zkdcap_verifier_infos[r.zkdcap_verifier_infos.length - counters[11]] = x; + counters[11] -= 1; + } + return sz; + } + // struct decoder /** * @dev The decoder for reading a inner struct field @@ -1577,6 +1945,17 @@ library IbcLightclientsLcpV1ClientState { ); pointer += ProtoBufRuntime._encode_uint64(r.operators_threshold_denominator, pointer, bs); } + if (r.zkdcap_verifier_infos.length != 0) { + for(i = 0; i < r.zkdcap_verifier_infos.length; i++) { + pointer += ProtoBufRuntime._encode_key( + 11, + ProtoBufRuntime.WireType.LengthDelim, + pointer, + bs) + ; + pointer += ProtoBufRuntime._encode_bytes(r.zkdcap_verifier_infos[i], pointer, bs); + } + } return pointer - offset; } // nested encoder @@ -1636,6 +2015,9 @@ 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); + for(i = 0; i < r.zkdcap_verifier_infos.length; i++) { + e += 1 + ProtoBufRuntime._sz_lendelim(r.zkdcap_verifier_infos[i].length); + } return e; } // empty checker @@ -1680,6 +2062,10 @@ library IbcLightclientsLcpV1ClientState { return false; } + if (r.zkdcap_verifier_infos.length != 0) { + return false; + } + return true; } @@ -1701,6 +2087,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_infos = input.zkdcap_verifier_infos; } @@ -1759,6 +2146,24 @@ library IbcLightclientsLcpV1ClientState { self.operators = tmp; } + //array helpers for ZkdcapVerifierInfos + /** + * @dev Add value to an array + * @param self The in-memory struct + * @param value The value to add + */ + function addZkdcapVerifierInfos(Data memory self, bytes memory value) internal pure { + /** + * First resize the array. Then add the new element to the end. + */ + bytes[] memory tmp = new bytes[](self.zkdcap_verifier_infos.length + 1); + for (uint256 i = 0; i < self.zkdcap_verifier_infos.length; i++) { + tmp[i] = self.zkdcap_verifier_infos[i]; + } + tmp[self.zkdcap_verifier_infos.length] = value; + self.zkdcap_verifier_infos = tmp; + } + //utility functions /** 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..98f3b90 100644 --- a/proto/ibc/lightclients/lcp/v1/LCP.proto +++ b/proto/ibc/lightclients/lcp/v1/LCP.proto @@ -17,6 +17,13 @@ message RegisterEnclaveKeyMessage { bytes operator_signature = 4; } +message ZKDCAPRegisterEnclaveKeyMessage { + uint32 zkvm_type = 1; + bytes commit = 2; + bytes proof = 3; + bytes operator_signature = 4; +} + message UpdateOperatorsMessage { uint64 nonce = 1; repeated bytes new_operators = 2; @@ -27,6 +34,9 @@ message UpdateOperatorsMessage { message ClientState { bytes mrenclave = 1; + // enclave key's expiration from the remote attestation time in seconds + // + // If the key is got from the DCAP/zkDCAP's quote, it should be the collateral's expiration time instead. uint64 key_expiration = 2; bool frozen = 3; Height latest_height = 4; @@ -38,6 +48,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 layout of each element is as follows: + // 0: zkVM type (e.g. 1 for risc0) + // 1-31: reserved + // 32-64: guest program identifier + repeated bytes zkdcap_verifier_infos = 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/slither.config.json b/slither.config.json index 80ba075..9fa09c9 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,4 +1,4 @@ { "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,deprecated-standards,erc20-indexed,function-init-state,pragma,reentrancy-unlimited-gas,immutable-states,var-read-using-this,dead-code", - "filter_paths": "(test/|node_modules/|contracts/proto/)" + "filter_paths": "(test/|node_modules/|lib/|contracts/proto/)" } \ No newline at end of file diff --git a/test/ZKDCAPVerifier.t.sol b/test/ZKDCAPVerifier.t.sol new file mode 100644 index 0000000..545de24 --- /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.TCB_STATUS_SW_HARDENING_NEEDED); + 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.TCB_STATUS_SW_HARDENING_NEEDED); + 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); + } +}