From 96f9d2bc1c9ec303eb07765ab915f63aa9df0857 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 29 Dec 2024 14:11:17 +0900 Subject: [PATCH 1/9] WIP Signed-off-by: Jun Kimura --- contracts/LCPClientBase.sol | 507 +++++++++--------- contracts/LCPClientZKDCAP.sol | 58 ++ contracts/LCPProtoMarshaler.sol | 16 + .../proto/ibc/lightclients/lcp/v1/LCP.sol | 338 +++++++++++- proto/ibc/lightclients/lcp/v1/LCP.proto | 15 + 5 files changed, 685 insertions(+), 249 deletions(-) create mode 100644 contracts/LCPClientZKDCAP.sol diff --git a/contracts/LCPClientBase.sol b/contracts/LCPClientBase.sol index a104c3e..7deb54a 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 LCPClientV2Base is ILightClient, ILCPClientErrors { using IBCHeight for Height.Data; // --------------------- Data structures --------------------- @@ -42,40 +42,23 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { mapping(address => EKInfo) ekInfos; } - // --------------------- 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 --------------------- @@ -87,20 +70,6 @@ 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); - } - /** * @dev initializeClient initializes a new client with the given state. * If succeeded, it returns heights at which the consensus state are stored. @@ -229,26 +198,214 @@ 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) + function updateClient(string calldata clientId, UpdateClientMessage.Data calldata message) public - pure - returns (bytes4, bytes memory) + returns (Height.Data[] memory heights) { - (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); + ClientStorage storage clientStorage = clientStorages[clientId]; + verifySignatures(clientStorage, keccak256(message.proxy_message), message.signatures); + + LCPCommitment.HeaderedProxyMessage memory hm = + abi.decode(message.proxy_message, (LCPCommitment.HeaderedProxyMessage)); + if (hm.header == LCPCommitment.LCP_MESSAGE_HEADER_UPDATE_STATE) { + return updateState(clientStorage, abi.decode(hm.message, (LCPCommitment.UpdateStateProxyMessage))); + } else if (hm.header == LCPCommitment.LCP_MESSAGE_HEADER_MISBEHAVIOUR) { + return submitMisbehaviour(clientStorage, abi.decode(hm.message, (LCPCommitment.MisbehaviourProxyMessage))); } else { - revert LCPClientUnknownProtoTypeUrl(); + revert LCPClientUnknownProxyMessageHeader(); + } + } + + function updateState(ClientStorage storage clientStorage, LCPCommitment.UpdateStateProxyMessage memory pmsg) + internal + returns (Height.Data[] memory heights) + { + ConsensusState storage consensusState; + ProtoClientState.Data storage clientState = clientStorage.clientState; + if (clientState.frozen) { + revert LCPClientClientStateFrozen(); + } + + if (clientState.latest_height.revision_number == 0 && clientState.latest_height.revision_height == 0) { + if (pmsg.emittedStates.length == 0) { + revert LCPClientUpdateStateEmittedStatesMustNotEmpty(); + } + } else { + consensusState = clientStorage.consensusStates[pmsg.prevHeight.toUint128()]; + if (pmsg.prevStateId == bytes32(0)) { + revert LCPClientUpdateStatePrevStateIdMustNotEmpty(); + } + if (consensusState.stateId != pmsg.prevStateId) { + revert LCPClientUpdateStateUnexpectedPrevStateId(); + } + } + + LCPCommitment.validationContextEval(pmsg.context, block.timestamp * 1e9); + + uint128 postHeight = pmsg.postHeight.toUint128(); + consensusState = clientStorage.consensusStates[postHeight]; + if (consensusState.stateId != bytes32(0)) { + if (consensusState.stateId != pmsg.postStateId || consensusState.timestamp != uint64(pmsg.timestamp)) { + revert LCPClientUpdateStateInconsistentConsensusState(); + } + // return empty heights if the consensus state is already stored + return heights; + } + consensusState.stateId = pmsg.postStateId; + consensusState.timestamp = uint64(pmsg.timestamp); + + uint128 latestHeight = clientState.latest_height.toUint128(); + if (latestHeight < postHeight) { + clientState.latest_height = pmsg.postHeight; + } + heights = new Height.Data[](1); + heights[0] = pmsg.postHeight; + return heights; + } + + function submitMisbehaviour(ClientStorage storage clientStorage, LCPCommitment.MisbehaviourProxyMessage memory pmsg) + internal + returns (Height.Data[] memory heights) + { + ProtoClientState.Data storage clientState = clientStorage.clientState; + if (clientState.frozen) { + revert LCPClientClientStateFrozen(); + } + uint256 prevStatesNum = pmsg.prevStates.length; + if (prevStatesNum == 0) { + revert LCPClientMisbehaviourPrevStatesMustNotEmpty(); + } + + for (uint256 i = 0; i < prevStatesNum; i++) { + LCPCommitment.PrevState memory prev = pmsg.prevStates[i]; + uint128 prevHeight = prev.height.toUint128(); + if (prev.stateId == bytes32(0)) { + revert LCPClientUpdateStatePrevStateIdMustNotEmpty(); + } + if (clientStorage.consensusStates[prevHeight].stateId != prev.stateId) { + revert LCPClientUpdateStateUnexpectedPrevStateId(); + } + } + + LCPCommitment.validationContextEval(pmsg.context, block.timestamp * 1e9); + + clientStorage.clientState.frozen = true; + 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(); + } + uint64 nonce = clientState.operators_nonce; + uint64 nextNonce = nonce + 1; + if (message.nonce != nextNonce) { + revert LCPClientClientStateUnexpectedOperatorsNonce(nextNonce); + } + address[] memory newOperators = new address[](message.new_operators.length); + for (uint256 i = 0; i < message.new_operators.length; i++) { + if (message.new_operators[i].length != 20) { + revert LCPClientClientStateInvalidOperatorAddressLength(); + } + newOperators[i] = address(bytes20(message.new_operators[i])); + } + bytes32 commitment = keccak256( + LCPOperator.computeEIP712UpdateOperators( + clientId, + nextNonce, + newOperators, + message.new_operators_threshold_numerator, + message.new_operators_threshold_denominator + ) + ); + uint256 success = 0; + for (uint256 i = 0; i < sigNum; i++) { + if (message.signatures[i].length > 0) { + address operator = verifyECDSASignature(commitment, message.signatures[i]); + if (operator != address(bytes20(clientState.operators[i]))) { + revert LCPClientUpdateOperatorsSignatureUnexpectedOperator( + operator, address(bytes20(clientState.operators[i])) + ); + } + unchecked { + success++; + } + } + } + ensureSufficientValidSignatures(clientState, success); + delete clientState.operators; + // ensure the new operators are sorted(ascending order) and unique + for (uint256 i = 0; i < newOperators.length; i++) { + if (i > 0) { + unchecked { + address prev = newOperators[i - 1]; + if (prev >= newOperators[i]) { + revert LCPClientOperatorsInvalidOrder(prev, newOperators[i]); + } + } + } + clientState.operators.push(message.new_operators[i]); + } + clientState.operators_nonce = nextNonce; + clientState.operators_threshold_numerator = message.new_operators_threshold_numerator; + clientState.operators_threshold_denominator = message.new_operators_threshold_denominator; + return heights; + } + + function ensureActiveKey(ClientStorage storage clientStorage, address ekAddr, address opAddr) internal view { + EKInfo storage ekInfo = clientStorage.ekInfos[ekAddr]; + uint256 expiredAt = ekInfo.expiredAt; + if (expiredAt == 0) { + revert LCPClientEnclaveKeyNotExist(); + } + if (expiredAt <= block.timestamp) { + revert LCPClientEnclaveKeyExpired(); + } + if (ekInfo.operator != opAddr) { + revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, opAddr); + } + } + + function ensureActiveKey(ClientStorage storage clientStorage, address ekAddr) internal view { + EKInfo storage ekInfo = clientStorage.ekInfos[ekAddr]; + uint256 expiredAt = ekInfo.expiredAt; + if (expiredAt == 0) { + revert LCPClientEnclaveKeyNotExist(); + } + if (expiredAt <= block.timestamp) { + revert LCPClientEnclaveKeyExpired(); + } + } + + function ensureSufficientValidSignatures(ProtoClientState.Data storage clientState, uint256 success) + internal + view + { + if ( + success * clientState.operators_threshold_denominator + < clientState.operators_threshold_numerator * clientState.operators.length + ) { + revert LCPClientOperatorSignaturesInsufficient(success); + } + } + + function verifyECDSASignature(bytes32 commitment, bytes memory signature) internal pure returns (address) { + if (uint8(signature[64]) < 27) { + signature[64] = bytes1(uint8(signature[64]) + 27); } + return ECDSA.recover(commitment, signature); } /** @@ -399,100 +556,75 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { ensureSufficientValidSignatures(clientStorage.clientState, success); } } +} - function updateClient(string calldata clientId, UpdateClientMessage.Data calldata message) - public - returns (Height.Data[] memory heights) - { - ClientStorage storage clientStorage = clientStorages[clientId]; - verifySignatures(clientStorage, keccak256(message.proxy_message), message.signatures); +abstract contract LCPClientBase is LCPClientV2Base { + using IBCHeight for Height.Data; - LCPCommitment.HeaderedProxyMessage memory hm = - abi.decode(message.proxy_message, (LCPCommitment.HeaderedProxyMessage)); - if (hm.header == LCPCommitment.LCP_MESSAGE_HEADER_UPDATE_STATE) { - return updateState(clientStorage, abi.decode(hm.message, (LCPCommitment.UpdateStateProxyMessage))); - } else if (hm.header == LCPCommitment.LCP_MESSAGE_HEADER_MISBEHAVIOUR) { - return submitMisbehaviour(clientStorage, abi.decode(hm.message, (LCPCommitment.MisbehaviourProxyMessage))); - } else { - revert LCPClientUnknownProxyMessageHeader(); - } + /// @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; + + // --------------------- 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_) LCPClientV2Base(ibcHandler_) { + developmentMode = developmentMode_; } - function updateState(ClientStorage storage clientStorage, LCPCommitment.UpdateStateProxyMessage memory pmsg) - internal - returns (Height.Data[] memory heights) - { - ConsensusState storage consensusState; - ProtoClientState.Data storage clientState = clientStorage.clientState; - if (clientState.frozen) { - revert LCPClientClientStateFrozen(); - } + // --------------------- Events --------------------- - if (clientState.latest_height.revision_number == 0 && clientState.latest_height.revision_height == 0) { - if (pmsg.emittedStates.length == 0) { - revert LCPClientUpdateStateEmittedStatesMustNotEmpty(); - } - } else { - consensusState = clientStorage.consensusStates[pmsg.prevHeight.toUint128()]; - if (pmsg.prevStateId == bytes32(0)) { - revert LCPClientUpdateStatePrevStateIdMustNotEmpty(); - } - if (consensusState.stateId != pmsg.prevStateId) { - revert LCPClientUpdateStateUnexpectedPrevStateId(); - } - } + event RegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); - LCPCommitment.validationContextEval(pmsg.context, block.timestamp * 1e9); + // --------------------- Storage fields --------------------- - uint128 postHeight = pmsg.postHeight.toUint128(); - consensusState = clientStorage.consensusStates[postHeight]; - if (consensusState.stateId != bytes32(0)) { - if (consensusState.stateId != pmsg.postStateId || consensusState.timestamp != uint64(pmsg.timestamp)) { - revert LCPClientUpdateStateInconsistentConsensusState(); - } - // return empty heights if the consensus state is already stored - return heights; - } - consensusState.stateId = pmsg.postStateId; - consensusState.timestamp = uint64(pmsg.timestamp); + /// @dev RootCA's public key parameters + AVRValidator.RSAParams internal verifiedRootCAParams; + /// @dev keccak256(signingCert) => RSAParams of signing public key + mapping(bytes32 => AVRValidator.RSAParams) internal verifiedSigningRSAParams; - uint128 latestHeight = clientState.latest_height.toUint128(); - if (latestHeight < postHeight) { - clientState.latest_height = pmsg.postHeight; - } - heights = new Height.Data[](1); - heights[0] = pmsg.postHeight; - return heights; + /// @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; } - function submitMisbehaviour(ClientStorage storage clientStorage, LCPCommitment.MisbehaviourProxyMessage memory pmsg) - internal - returns (Height.Data[] memory heights) - { - ProtoClientState.Data storage clientState = clientStorage.clientState; - if (clientState.frozen) { - revert LCPClientClientStateFrozen(); - } - uint256 prevStatesNum = pmsg.prevStates.length; - if (prevStatesNum == 0) { - revert LCPClientMisbehaviourPrevStatesMustNotEmpty(); + /// @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); + } - for (uint256 i = 0; i < prevStatesNum; i++) { - LCPCommitment.PrevState memory prev = pmsg.prevStates[i]; - uint128 prevHeight = prev.height.toUint128(); - if (prev.stateId == bytes32(0)) { - revert LCPClientUpdateStatePrevStateIdMustNotEmpty(); - } - if (clientStorage.consensusStates[prevHeight].stateId != prev.stateId) { - revert LCPClientUpdateStateUnexpectedPrevStateId(); - } + /** + * @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(); } - - LCPCommitment.validationContextEval(pmsg.context, block.timestamp * 1e9); - - clientStorage.clientState.frozen = true; - return heights; } function registerEnclaveKey(string calldata clientId, RegisterEnclaveKeyMessage.Data calldata message) @@ -547,119 +679,4 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors { // 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(); - } - uint64 nonce = clientState.operators_nonce; - uint64 nextNonce = nonce + 1; - if (message.nonce != nextNonce) { - revert LCPClientClientStateUnexpectedOperatorsNonce(nextNonce); - } - address[] memory newOperators = new address[](message.new_operators.length); - for (uint256 i = 0; i < message.new_operators.length; i++) { - if (message.new_operators[i].length != 20) { - revert LCPClientClientStateInvalidOperatorAddressLength(); - } - newOperators[i] = address(bytes20(message.new_operators[i])); - } - bytes32 commitment = keccak256( - LCPOperator.computeEIP712UpdateOperators( - clientId, - nextNonce, - newOperators, - message.new_operators_threshold_numerator, - message.new_operators_threshold_denominator - ) - ); - uint256 success = 0; - for (uint256 i = 0; i < sigNum; i++) { - if (message.signatures[i].length > 0) { - address operator = verifyECDSASignature(commitment, message.signatures[i]); - if (operator != address(bytes20(clientState.operators[i]))) { - revert LCPClientUpdateOperatorsSignatureUnexpectedOperator( - operator, address(bytes20(clientState.operators[i])) - ); - } - unchecked { - success++; - } - } - } - ensureSufficientValidSignatures(clientState, success); - delete clientState.operators; - // ensure the new operators are sorted(ascending order) and unique - for (uint256 i = 0; i < newOperators.length; i++) { - if (i > 0) { - unchecked { - address prev = newOperators[i - 1]; - if (prev >= newOperators[i]) { - revert LCPClientOperatorsInvalidOrder(prev, newOperators[i]); - } - } - } - clientState.operators.push(message.new_operators[i]); - } - clientState.operators_nonce = nextNonce; - clientState.operators_threshold_numerator = message.new_operators_threshold_numerator; - clientState.operators_threshold_denominator = message.new_operators_threshold_denominator; - return heights; - } - - function ensureActiveKey(ClientStorage storage clientStorage, address ekAddr, address opAddr) internal view { - EKInfo storage ekInfo = clientStorage.ekInfos[ekAddr]; - uint256 expiredAt = ekInfo.expiredAt; - if (expiredAt == 0) { - revert LCPClientEnclaveKeyNotExist(); - } - if (expiredAt <= block.timestamp) { - revert LCPClientEnclaveKeyExpired(); - } - if (ekInfo.operator != opAddr) { - revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, opAddr); - } - } - - function ensureActiveKey(ClientStorage storage clientStorage, address ekAddr) internal view { - EKInfo storage ekInfo = clientStorage.ekInfos[ekAddr]; - uint256 expiredAt = ekInfo.expiredAt; - if (expiredAt == 0) { - revert LCPClientEnclaveKeyNotExist(); - } - if (expiredAt <= block.timestamp) { - revert LCPClientEnclaveKeyExpired(); - } - } - - function ensureSufficientValidSignatures(ProtoClientState.Data storage clientState, uint256 success) - internal - view - { - if ( - success * clientState.operators_threshold_denominator - < clientState.operators_threshold_numerator * clientState.operators.length - ) { - revert LCPClientOperatorSignaturesInsufficient(success); - } - } - - function verifyECDSASignature(bytes32 commitment, bytes memory signature) internal pure returns (address) { - if (uint8(signature[64]) < 27) { - signature[64] = bytes1(uint8(signature[64]) + 27); - } - return ECDSA.recover(commitment, signature); - } } diff --git a/contracts/LCPClientZKDCAP.sol b/contracts/LCPClientZKDCAP.sol new file mode 100644 index 0000000..1087a90 --- /dev/null +++ b/contracts/LCPClientZKDCAP.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +import {ILightClient} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/ILightClient.sol"; +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 {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnclaveKeyMessage} from + "./proto/ibc/lightclients/lcp/v1/LCP.sol"; +import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol"; +import {LCPClientV2Base} from "./LCPClientBase.sol"; + +contract LCPClientZKDCAP is LCPClientV2Base { + using IBCHeight for Height.Data; + + // --------------------- Events --------------------- + + event ZKDCAPRegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); + + /// @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_) LCPClientV2Base(ibcHandler_) {} + + /** + * @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) + { + return heights; + } +} 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/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 { From b97e9a125dcb99624167a7424bf9de7da28c19ee Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 29 Dec 2024 14:30:51 +0900 Subject: [PATCH 2/9] WIP2 Signed-off-by: Jun Kimura --- Makefile | 2 +- contracts/LCPClientZKDCAP.sol | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) 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/LCPClientZKDCAP.sol b/contracts/LCPClientZKDCAP.sol index 1087a90..bc984c6 100644 --- a/contracts/LCPClientZKDCAP.sol +++ b/contracts/LCPClientZKDCAP.sol @@ -9,6 +9,7 @@ import {IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnc "./proto/ibc/lightclients/lcp/v1/LCP.sol"; import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol"; import {LCPClientV2Base} from "./LCPClientBase.sol"; +import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; contract LCPClientZKDCAP is LCPClientV2Base { using IBCHeight for Height.Data; @@ -17,6 +18,13 @@ contract LCPClientZKDCAP is LCPClientV2Base { event ZKDCAPRegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); + // --------------------- Immutable fields --------------------- + + // /// @notice RISC Zero verifier contract address. + // IRiscZeroVerifier public immutable verifier; + + // --------------------- Storage fields --------------------- + /// @dev Reserved storage space to allow for layout changes in the future uint256[50] private __gap; @@ -53,6 +61,8 @@ contract LCPClientZKDCAP is LCPClientV2Base { public returns (Height.Data[] memory heights) { + ClientStorage storage clientStorage = clientStorages[clientId]; + revert("not implemented"); return heights; } } From 6ec68b39b98ac4cf358cb2e849e8dd104648e2ae Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 29 Dec 2024 14:32:14 +0900 Subject: [PATCH 3/9] forge install: risc0-ethereum v1.1.4 --- .gitmodules | 3 +++ lib/risc0-ethereum | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/risc0-ethereum 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/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 From 299f6e055800c4f48ab119483f14d4f3f5654e42 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 29 Dec 2024 14:48:02 +0900 Subject: [PATCH 4/9] implement `zkdcapRegisterEnclaveKey` function Signed-off-by: Jun Kimura --- contracts/DCAPValidator.sol | 112 ++++++++++++++++++++ contracts/LCPClientBase.sol | 72 ++++++++----- contracts/LCPClientZKDCAP.sol | 68 +------------ contracts/LCPClientZKDCAPBase.sol | 164 ++++++++++++++++++++++++++++++ contracts/LCPOperator.sol | 18 +++- test/LCPClientOperator.t.sol | 2 +- test/LCPClientTest.t.sol | 2 +- 7 files changed, 344 insertions(+), 94 deletions(-) create mode 100644 contracts/DCAPValidator.sol create mode 100644 contracts/LCPClientZKDCAPBase.sol diff --git a/contracts/DCAPValidator.sol b/contracts/DCAPValidator.sol new file mode 100644 index 0000000..14d9d7d --- /dev/null +++ b/contracts/DCAPValidator.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +library DCAPValidator { + uint256 internal constant SGX_DCAP_VERIFIER_MIN_COMMIT_SIZE = 40 + 13 + 384; + + struct DCAPVerifierCommit { + uint64 attestationTime; + bytes32 sgxIntelRootCAHash; + // VerifiedOutput offset: 40(8+32) bytes + // [quote_vesion][tee_type][tcb_status][fmspc][quote_body_raw_bytes][advisory_ids] + // 2 bytes + 4 bytes + 1 byte + 6 bytes + var (SGX_ENCLAVE_REPORT = 384; TD10_REPORT = 584) + var + uint8 tcbStatus; + bytes32 mrenclave; + // reportData + address enclaveKey; + address operator; + string[] advisoryIDs; + } + + /* + pub struct SGX_ENCLAVE_REPORT { + pub cpu_svn: [u8; 16], // [16 bytes] + // Security Version of the CPU (raw value) + pub misc_select: [u8; 4], // [4 bytes] + // SSA Frame extended feature set. + // Reports what SECS.MISCSELECT settings are used in the enclave. You can limit the + // allowed MISCSELECT settings in the sigstruct using MISCSELECT/MISCMASK. + pub reserved_1: [u8; 28], // [28 bytes] + // Reserved for future use - 0 + pub attributes: [u8; 16], // [16 bytes] + // Set of flags describing attributes of the enclave. + // Reports what SECS.ATTRIBUTES settings are used in the enclave. The ISV can limit what + // SECS.ATTRIBUTES can be used when loading the enclave through parameters to the SGX Signtool. + // The Signtool will produce a SIGSTRUCT with ATTRIBUTES and ATTRIBUTESMASK + // which determine allowed ATTRIBUTES. + // - For each SIGSTRUCT.ATTRIBUTESMASK bit that is set, then corresponding bit in the + // SECS.ATTRIBUTES must match the same bit in SIGSTRUCT.ATTRIBUTES. + pub mrenclave: [u8; 32], // [32 bytes] + // Measurement of the enclave. + // The MRENCLAVE value is the SHA256 hash of the ENCLAVEHASH field in the SIGSTRUCT. + pub reserved_2: [u8; 32], // [32 bytes] + // Reserved for future use - 0 + pub mrsigner: [u8; 32], // [32 bytes] + // Measurement of the enclave signer. + // The MRSIGNER value is the SHA256 hash of the MODULUS field in the SIGSTRUCT. + pub reserved_3: [u8; 96], // [96 bytes] + // Reserved for future use - 0 + pub isv_prod_id: u16, // [2 bytes] + // Product ID of the enclave. + // The ISV should configure a unique ISVProdID for each product which may + // want to share sealed data between enclaves signed with a specific MRSIGNER. The ISV + // may want to supply different data to identical enclaves signed for different products. + pub isv_svn: u16, // [2 bytes] + // Security Version of the enclave + pub reserved_4: [u8; 60], // [60 bytes] + // Reserved for future use - 0 + pub report_data: [u8; 64], // [64 bytes] + // Additional report data. + // The enclave is free to provide 64 bytes of custom data to the REPORT. + // This can be used to provide specific data from the enclave or it can be used to hold + // a hash of a larger block of data which is provided with the quote. + // The verification of the quote signature confirms the integrity of the + // report data (and the rest of the REPORT body). + } + */ + function parseCommit(bytes calldata commit) internal pure returns (DCAPVerifierCommit memory) { + DCAPVerifierCommit memory verifierCommit; + verifierCommit.attestationTime = uint64(bytes8(commit[0:8])); + verifierCommit.sgxIntelRootCAHash = bytes32(commit[8:40]); + // TODO should check quote version?? + require(uint32(bytes4(commit[42:46])) == 0, "unexpected tee type"); + verifierCommit.tcbStatus = uint8(commit[46]); + uint256 sgxQuoteBodyOffset = 53; + uint256 mrenclaveOffset = sgxQuoteBodyOffset + 16 + 4 + 28 + 16; + verifierCommit.mrenclave = bytes32(commit[mrenclaveOffset:mrenclaveOffset + 32]); + + uint256 reportDataOffset = sgxQuoteBodyOffset + 320; + /// ReportData is a 64-byte value that is embedded in the Quote + /// | version: 1 byte | enclave key: 20 bytes | operator: 20 bytes | nonce: 22 bytes | + require(commit[reportDataOffset] == 0x01, "unexpected report data version"); + verifierCommit.enclaveKey = address(bytes20(commit[reportDataOffset + 1:reportDataOffset + 1 + 20])); + verifierCommit.operator = address(bytes20(commit[reportDataOffset + 1 + 20:reportDataOffset + 1 + 20 + 20])); + if (commit.length > SGX_DCAP_VERIFIER_MIN_COMMIT_SIZE) { + // remain bytes are advisory IDs + verifierCommit.advisoryIDs = abi.decode(commit[SGX_DCAP_VERIFIER_MIN_COMMIT_SIZE:commit.length], (string[])); + } + return verifierCommit; + } + + function tcbStatusToString(uint8 tcbStatus) internal pure returns (string memory) { + if (tcbStatus == 0) { + return "UpToDate"; + } else if (tcbStatus == 1) { + return "SWHardeningNeeded"; + } else if (tcbStatus == 2) { + return "ConfigurationAndSWHardeningNeeded"; + } else if (tcbStatus == 3) { + return "ConfigurationNeeded"; + } else if (tcbStatus == 4) { + return "OutOfDate"; + } else if (tcbStatus == 5) { + return "OutOfDateConfigurationNeeded"; + } else if (tcbStatus == 6) { + return "Revoked"; + } else if (tcbStatus == 7) { + return "Unrecognized"; + } else { + revert("unknown tcb status"); + } + } +} diff --git a/contracts/LCPClientBase.sol b/contracts/LCPClientBase.sol index 7deb54a..e0e39ff 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 LCPClientV2Base is ILightClient, ILCPClientErrors { +abstract contract LCPClientCommon is ILightClient, ILCPClientErrors { using IBCHeight for Height.Data; // --------------------- Data structures --------------------- @@ -40,6 +40,7 @@ abstract contract LCPClientV2Base is ILightClient, ILCPClientErrors { mapping(uint128 => ConsensusState) consensusStates; // enclave key => EKInfo mapping(address => EKInfo) ekInfos; + bytes32 zkDCAPRisc0ImageId; } // --------------------- Immutable fields --------------------- @@ -68,19 +69,13 @@ abstract contract LCPClientV2Base is ILightClient, ILCPClientErrors { _; } - // --------------------- Public methods --------------------- + // --------------------- 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); @@ -133,7 +128,6 @@ abstract contract LCPClientV2Base is ILightClient, ILCPClientErrors { } prev = addr; } - ClientStorage storage clientStorage = clientStorages[clientId]; clientStorage.clientState = clientState; // set allowed quote status and advisories @@ -151,14 +145,20 @@ abstract contract LCPClientV2Base 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(); @@ -169,7 +169,7 @@ abstract contract LCPClientV2Base 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(); @@ -180,7 +180,7 @@ abstract contract LCPClientV2Base 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; } @@ -190,6 +190,7 @@ abstract contract LCPClientV2Base 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]; @@ -558,7 +559,7 @@ abstract contract LCPClientV2Base is ILightClient, ILCPClientErrors { } } -abstract contract LCPClientBase is LCPClientV2Base { +abstract contract LCPClientBase is LCPClientCommon { using IBCHeight for Height.Data; /// @dev if developmentMode is true, the client allows the remote attestation of IAS in development. @@ -570,7 +571,7 @@ abstract contract LCPClientBase is LCPClientV2Base { /// @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_) LCPClientV2Base(ibcHandler_) { + constructor(address ibcHandler_, bool developmentMode_) LCPClientCommon(ibcHandler_) { developmentMode = developmentMode_; } @@ -595,13 +596,21 @@ abstract contract LCPClientBase is LCPClientV2Base { 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); + /** + * @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; } /** @@ -679,4 +688,15 @@ abstract contract LCPClientBase is LCPClientV2Base { // 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 index bc984c6..0b6b1c0 100644 --- a/contracts/LCPClientZKDCAP.sol +++ b/contracts/LCPClientZKDCAP.sol @@ -1,68 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; -import {ILightClient} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/ILightClient.sol"; -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 {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnclaveKeyMessage} from - "./proto/ibc/lightclients/lcp/v1/LCP.sol"; -import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol"; -import {LCPClientV2Base} from "./LCPClientBase.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol"; -contract LCPClientZKDCAP is LCPClientV2Base { - using IBCHeight for Height.Data; - - // --------------------- Events --------------------- - - event ZKDCAPRegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator); - - // --------------------- Immutable fields --------------------- - - // /// @notice RISC Zero verifier contract address. - // IRiscZeroVerifier public immutable verifier; - - // --------------------- 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_) LCPClientV2Base(ibcHandler_) {} - - /** - * @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]; - revert("not implemented"); - return heights; - } +contract LCPClientZKDCAP is LCPClientZKDCAPBase { + constructor(address ibcHandler_, bytes memory intelRootCA, address riscZeroVerifier) + LCPClientZKDCAPBase(ibcHandler_, intelRootCA, riscZeroVerifier) + {} } diff --git a/contracts/LCPClientZKDCAPBase.sol b/contracts/LCPClientZKDCAPBase.sol new file mode 100644 index 0000000..ddf83cc --- /dev/null +++ b/contracts/LCPClientZKDCAPBase.sol @@ -0,0 +1,164 @@ +// 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 --------------------- + + /// @notice The hash of the root CA's public key certificate. + bytes32 public immutable intelRootCAHash; + /// @notice RISC Zero verifier contract address. + 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_, bytes memory intelRootCA, address riscZeroVerifier_) + LCPClientCommon(ibcHandler_) + { + require(intelRootCA.length != 0 && riscZeroVerifier_ != address(0), "invalid parameters"); + intelRootCAHash = keccak256(intelRootCA); + riscZeroVerifier = IRiscZeroVerifier(riscZeroVerifier_); + } + + // --------------------- 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"); + // 33..64 bytes: image ID + bytes32 imageId; + bytes memory verifierInfo = clientState.zkdcap_verifier_info; + assembly { + imageId := mload(add(verifierInfo, 33)) + } + assert(imageId != 0); + 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.DCAPVerifierCommit memory commit = DCAPValidator.parseCommit(message.commit); + require(commit.sgxIntelRootCAHash == intelRootCAHash, "unexpected root CA hash"); + + if (bytes32(clientStorage.clientState.mrenclave) != commit.mrenclave) { + revert LCPClientClientStateUnexpectedMrenclave(); + } + + require( + clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(commit.tcbStatus)] + == AVRValidator.FLAG_ALLOWED, + "disallowed TCB status" + ); + for (uint256 i = 0; i < commit.advisoryIDs.length; i++) { + require( + clientStorage.allowedStatuses.allowedAdvisories[commit.advisoryIDs[i]] == AVRValidator.FLAG_ALLOWED, + "disallowed advisory ID" + ); + } + + // 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 (commit.operator != address(0) && commit.operator != operator) { + revert LCPClientAVRUnexpectedOperator(operator, commit.operator); + } + uint64 expiredAt = commit.attestationTime + clientStorage.clientState.key_expiration; + if (expiredAt <= block.timestamp) { + revert LCPClientAVRAlreadyExpired(); + } + EKInfo storage ekInfo = clientStorage.ekInfos[commit.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, commit.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/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[])); From b6903335d4e1c4c862881eb6db6fa06730bd0f00 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 7 Jan 2025 20:40:55 +0900 Subject: [PATCH 5/9] fix quote status validation Signed-off-by: Jun Kimura --- contracts/DCAPValidator.sol | 2 +- test/ZKDCAPVerifier.t.sol | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/ZKDCAPVerifier.t.sol diff --git a/contracts/DCAPValidator.sol b/contracts/DCAPValidator.sol index 14d9d7d..ebd61fd 100644 --- a/contracts/DCAPValidator.sol +++ b/contracts/DCAPValidator.sol @@ -68,7 +68,7 @@ library DCAPValidator { DCAPVerifierCommit memory verifierCommit; verifierCommit.attestationTime = uint64(bytes8(commit[0:8])); verifierCommit.sgxIntelRootCAHash = bytes32(commit[8:40]); - // TODO should check quote version?? + require(uint16(bytes2(commit[40:42])) == 3, "unexpected quote version"); require(uint32(bytes4(commit[42:46])) == 0, "unexpected tee type"); verifierCommit.tcbStatus = uint8(commit[46]); uint256 sgxQuoteBodyOffset = 53; diff --git a/test/ZKDCAPVerifier.t.sol b/test/ZKDCAPVerifier.t.sol new file mode 100644 index 0000000..6f05175 --- /dev/null +++ b/test/ZKDCAPVerifier.t.sol @@ -0,0 +1,31 @@ +// 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"; + +contract ZKDCAPVerifierTest is BasicTest { + RiscZeroGroth16Verifier verifier; + + function setUp() public { + verifier = new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); + } + + /* + "zkp":{"Risc0":{ + "image_id":"e45b67a3c24ff3b77f87fec1533dca31524fc19f02bd433d4e6bba729a7646a7", + "seal":"50bd1769188540e643a5e4b1548e4c9391b0359afc1488d25fbfe41395e8847079f64d55148c07b36f0d2d44bfbdcdbe9fc79b48062a75dec02bbd5bfd5e3e530f8fa1520a5f1d99b7cf0bd29b0dbdb4fa65186559593e2c415f1e8ce27ab302cacc917a1db4a97e49f4d82194363c3af262c3b0bcf57fe846130012d081cc8c1fc0337d0de1958f4e4c5755815559104d7576a3bfc0f5fffdb630eace4cc76a5f3b617210692dedcde61b1e581a1700476ae51fa573e0adc0405dcef88e6b902f1364be01080d0fbc1429093d77b320405ff81037e7d1ba6e029baa155b71283e10cbee1e6f5375ed061c83c8ce7e3123774ce8debfd9e90e34c95429eda72d688594b1", + "commit":"a10b726700000000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d70090003000000000100906ed5000015150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000dca1a1841ab2e3fa7025c1d175d2c947df760b3baa4a9a0f30f4fd05718fcfe3000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fc8f819e864fd03a5d377061e8148d6e5143679000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000 + "}} + */ + function testVerify() public { + verifier.verify( + hex"50bd1769188540e643a5e4b1548e4c9391b0359afc1488d25fbfe41395e8847079f64d55148c07b36f0d2d44bfbdcdbe9fc79b48062a75dec02bbd5bfd5e3e530f8fa1520a5f1d99b7cf0bd29b0dbdb4fa65186559593e2c415f1e8ce27ab302cacc917a1db4a97e49f4d82194363c3af262c3b0bcf57fe846130012d081cc8c1fc0337d0de1958f4e4c5755815559104d7576a3bfc0f5fffdb630eace4cc76a5f3b617210692dedcde61b1e581a1700476ae51fa573e0adc0405dcef88e6b902f1364be01080d0fbc1429093d77b320405ff81037e7d1ba6e029baa155b71283e10cbee1e6f5375ed061c83c8ce7e3123774ce8debfd9e90e34c95429eda72d688594b1", + hex"e45b67a3c24ff3b77f87fec1533dca31524fc19f02bd433d4e6bba729a7646a7", + sha256( + hex"a10b726700000000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d70090003000000000100906ed5000015150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000dca1a1841ab2e3fa7025c1d175d2c947df760b3baa4a9a0f30f4fd05718fcfe3000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fc8f819e864fd03a5d377061e8148d6e5143679000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000" + ) + ); + } +} From 59c2d52b86d8def85146d66f7582985a505e968c Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 28 Jan 2025 18:38:41 +0900 Subject: [PATCH 6/9] fix commit parser Signed-off-by: Jun Kimura --- contracts/DCAPValidator.sol | 134 +++++++++++------------------- contracts/ILCPClientErrors.sol | 3 + contracts/LCPClientZKDCAPBase.sol | 26 +++--- test/ZKDCAPVerifier.t.sol | 48 ++++++++++- 4 files changed, 107 insertions(+), 104 deletions(-) diff --git a/contracts/DCAPValidator.sol b/contracts/DCAPValidator.sol index ebd61fd..d1a3105 100644 --- a/contracts/DCAPValidator.sol +++ b/contracts/DCAPValidator.sol @@ -1,112 +1,72 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; +import {ILCPClientErrors} from "./ILCPClientErrors.sol"; + library DCAPValidator { - uint256 internal constant SGX_DCAP_VERIFIER_MIN_COMMIT_SIZE = 40 + 13 + 384; + enum TCBStatus { + UpToDate, + OutOfDate, + Revoked, + ConfigurationNeeded, + OutOfDateConfigurationNeeded, + SWHardeningNeeded, + ConfigurationAndSWHardeningNeeded + } - struct DCAPVerifierCommit { - uint64 attestationTime; + struct Output { + TCBStatus tcbStatus; bytes32 sgxIntelRootCAHash; - // VerifiedOutput offset: 40(8+32) bytes - // [quote_vesion][tee_type][tcb_status][fmspc][quote_body_raw_bytes][advisory_ids] - // 2 bytes + 4 bytes + 1 byte + 6 bytes + var (SGX_ENCLAVE_REPORT = 384; TD10_REPORT = 584) + var - uint8 tcbStatus; + uint64 validityNotBeforeMax; + uint64 validityNotAfterMin; bytes32 mrenclave; - // reportData address enclaveKey; address operator; string[] advisoryIDs; } - /* - pub struct SGX_ENCLAVE_REPORT { - pub cpu_svn: [u8; 16], // [16 bytes] - // Security Version of the CPU (raw value) - pub misc_select: [u8; 4], // [4 bytes] - // SSA Frame extended feature set. - // Reports what SECS.MISCSELECT settings are used in the enclave. You can limit the - // allowed MISCSELECT settings in the sigstruct using MISCSELECT/MISCMASK. - pub reserved_1: [u8; 28], // [28 bytes] - // Reserved for future use - 0 - pub attributes: [u8; 16], // [16 bytes] - // Set of flags describing attributes of the enclave. - // Reports what SECS.ATTRIBUTES settings are used in the enclave. The ISV can limit what - // SECS.ATTRIBUTES can be used when loading the enclave through parameters to the SGX Signtool. - // The Signtool will produce a SIGSTRUCT with ATTRIBUTES and ATTRIBUTESMASK - // which determine allowed ATTRIBUTES. - // - For each SIGSTRUCT.ATTRIBUTESMASK bit that is set, then corresponding bit in the - // SECS.ATTRIBUTES must match the same bit in SIGSTRUCT.ATTRIBUTES. - pub mrenclave: [u8; 32], // [32 bytes] - // Measurement of the enclave. - // The MRENCLAVE value is the SHA256 hash of the ENCLAVEHASH field in the SIGSTRUCT. - pub reserved_2: [u8; 32], // [32 bytes] - // Reserved for future use - 0 - pub mrsigner: [u8; 32], // [32 bytes] - // Measurement of the enclave signer. - // The MRSIGNER value is the SHA256 hash of the MODULUS field in the SIGSTRUCT. - pub reserved_3: [u8; 96], // [96 bytes] - // Reserved for future use - 0 - pub isv_prod_id: u16, // [2 bytes] - // Product ID of the enclave. - // The ISV should configure a unique ISVProdID for each product which may - // want to share sealed data between enclaves signed with a specific MRSIGNER. The ISV - // may want to supply different data to identical enclaves signed for different products. - pub isv_svn: u16, // [2 bytes] - // Security Version of the enclave - pub reserved_4: [u8; 60], // [60 bytes] - // Reserved for future use - 0 - pub report_data: [u8; 64], // [64 bytes] - // Additional report data. - // The enclave is free to provide 64 bytes of custom data to the REPORT. - // This can be used to provide specific data from the enclave or it can be used to hold - // a hash of a larger block of data which is provided with the quote. - // The verification of the quote signature confirms the integrity of the - // report data (and the rest of the REPORT body). - } - */ - function parseCommit(bytes calldata commit) internal pure returns (DCAPVerifierCommit memory) { - DCAPVerifierCommit memory verifierCommit; - verifierCommit.attestationTime = uint64(bytes8(commit[0:8])); - verifierCommit.sgxIntelRootCAHash = bytes32(commit[8:40]); - require(uint16(bytes2(commit[40:42])) == 3, "unexpected quote version"); - require(uint32(bytes4(commit[42:46])) == 0, "unexpected tee type"); - verifierCommit.tcbStatus = uint8(commit[46]); - uint256 sgxQuoteBodyOffset = 53; + 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 mrenclaveOffset = sgxQuoteBodyOffset + 16 + 4 + 28 + 16; - verifierCommit.mrenclave = bytes32(commit[mrenclaveOffset:mrenclaveOffset + 32]); + output.mrenclave = bytes32(commit[mrenclaveOffset:mrenclaveOffset + 32]); uint256 reportDataOffset = sgxQuoteBodyOffset + 320; - /// ReportData is a 64-byte value that is embedded in the Quote - /// | version: 1 byte | enclave key: 20 bytes | operator: 20 bytes | nonce: 22 bytes | require(commit[reportDataOffset] == 0x01, "unexpected report data version"); - verifierCommit.enclaveKey = address(bytes20(commit[reportDataOffset + 1:reportDataOffset + 1 + 20])); - verifierCommit.operator = address(bytes20(commit[reportDataOffset + 1 + 20:reportDataOffset + 1 + 20 + 20])); - if (commit.length > SGX_DCAP_VERIFIER_MIN_COMMIT_SIZE) { - // remain bytes are advisory IDs - verifierCommit.advisoryIDs = abi.decode(commit[SGX_DCAP_VERIFIER_MIN_COMMIT_SIZE:commit.length], (string[])); - } - return verifierCommit; + 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(uint8 tcbStatus) internal pure returns (string memory) { - if (tcbStatus == 0) { + function tcbStatusToString(TCBStatus tcbStatus) internal pure returns (string memory) { + if (tcbStatus == TCBStatus.UpToDate) { return "UpToDate"; - } else if (tcbStatus == 1) { - return "SWHardeningNeeded"; - } else if (tcbStatus == 2) { - return "ConfigurationAndSWHardeningNeeded"; - } else if (tcbStatus == 3) { - return "ConfigurationNeeded"; - } else if (tcbStatus == 4) { + } else if (tcbStatus == TCBStatus.OutOfDate) { return "OutOfDate"; - } else if (tcbStatus == 5) { - return "OutOfDateConfigurationNeeded"; - } else if (tcbStatus == 6) { + } else if (tcbStatus == TCBStatus.Revoked) { return "Revoked"; - } else if (tcbStatus == 7) { - return "Unrecognized"; + } 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("unknown tcb status"); + 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/LCPClientZKDCAPBase.sol b/contracts/LCPClientZKDCAPBase.sol index ddf83cc..419ed6f 100644 --- a/contracts/LCPClientZKDCAPBase.sol +++ b/contracts/LCPClientZKDCAPBase.sol @@ -104,21 +104,21 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { ClientStorage storage clientStorage = clientStorages[clientId]; require(clientStorage.zkDCAPRisc0ImageId != bytes32(0), "image ID not set"); riscZeroVerifier.verify(message.proof, clientStorage.zkDCAPRisc0ImageId, sha256(message.commit)); - DCAPValidator.DCAPVerifierCommit memory commit = DCAPValidator.parseCommit(message.commit); - require(commit.sgxIntelRootCAHash == intelRootCAHash, "unexpected root CA hash"); + DCAPValidator.Output memory output = DCAPValidator.parseCommit(message.commit); + require(output.sgxIntelRootCAHash == intelRootCAHash, "unexpected root CA hash"); - if (bytes32(clientStorage.clientState.mrenclave) != commit.mrenclave) { + if (bytes32(clientStorage.clientState.mrenclave) != output.mrenclave) { revert LCPClientClientStateUnexpectedMrenclave(); } require( - clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(commit.tcbStatus)] + clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(output.tcbStatus)] == AVRValidator.FLAG_ALLOWED, "disallowed TCB status" ); - for (uint256 i = 0; i < commit.advisoryIDs.length; i++) { + for (uint256 i = 0; i < output.advisoryIDs.length; i++) { require( - clientStorage.allowedStatuses.allowedAdvisories[commit.advisoryIDs[i]] == AVRValidator.FLAG_ALLOWED, + clientStorage.allowedStatuses.allowedAdvisories[output.advisoryIDs[i]] == AVRValidator.FLAG_ALLOWED, "disallowed advisory ID" ); } @@ -135,14 +135,14 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { message.operator_signature ); } - if (commit.operator != address(0) && commit.operator != operator) { - revert LCPClientAVRUnexpectedOperator(operator, commit.operator); + if (output.operator != address(0) && output.operator != operator) { + revert LCPClientAVRUnexpectedOperator(operator, output.operator); } - uint64 expiredAt = commit.attestationTime + clientStorage.clientState.key_expiration; - if (expiredAt <= block.timestamp) { - revert LCPClientAVRAlreadyExpired(); + if (block.timestamp < output.validityNotBeforeMax || block.timestamp > output.validityNotAfterMin) { + revert LCPClientZKDCAPBaseOutputNotValid(); } - EKInfo storage ekInfo = clientStorage.ekInfos[commit.enclaveKey]; + uint64 expiredAt = output.validityNotAfterMin; + EKInfo storage ekInfo = clientStorage.ekInfos[output.enclaveKey]; if (ekInfo.expiredAt != 0) { if (ekInfo.operator != operator) { revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, operator); @@ -156,7 +156,7 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { ekInfo.expiredAt = expiredAt; ekInfo.operator = operator; - emit ZKDCAPRegisteredEnclaveKey(clientId, commit.enclaveKey, expiredAt, operator); + emit ZKDCAPRegisteredEnclaveKey(clientId, output.enclaveKey, expiredAt, operator); // Note: client and consensus state are not always updated in registerEnclaveKey return heights; diff --git a/test/ZKDCAPVerifier.t.sol b/test/ZKDCAPVerifier.t.sol index 6f05175..cbee95c 100644 --- a/test/ZKDCAPVerifier.t.sol +++ b/test/ZKDCAPVerifier.t.sol @@ -4,9 +4,10 @@ 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 { - RiscZeroGroth16Verifier verifier; + IRiscZeroVerifier verifier; function setUp() public { verifier = new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); @@ -16,10 +17,10 @@ contract ZKDCAPVerifierTest is BasicTest { "zkp":{"Risc0":{ "image_id":"e45b67a3c24ff3b77f87fec1533dca31524fc19f02bd433d4e6bba729a7646a7", "seal":"50bd1769188540e643a5e4b1548e4c9391b0359afc1488d25fbfe41395e8847079f64d55148c07b36f0d2d44bfbdcdbe9fc79b48062a75dec02bbd5bfd5e3e530f8fa1520a5f1d99b7cf0bd29b0dbdb4fa65186559593e2c415f1e8ce27ab302cacc917a1db4a97e49f4d82194363c3af262c3b0bcf57fe846130012d081cc8c1fc0337d0de1958f4e4c5755815559104d7576a3bfc0f5fffdb630eace4cc76a5f3b617210692dedcde61b1e581a1700476ae51fa573e0adc0405dcef88e6b902f1364be01080d0fbc1429093d77b320405ff81037e7d1ba6e029baa155b71283e10cbee1e6f5375ed061c83c8ce7e3123774ce8debfd9e90e34c95429eda72d688594b1", - "commit":"a10b726700000000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d70090003000000000100906ed5000015150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000dca1a1841ab2e3fa7025c1d175d2c947df760b3baa4a9a0f30f4fd05718fcfe3000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fc8f819e864fd03a5d377061e8148d6e5143679000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000 - "}} + "commit":"a10b726700000000a1acc73eb45794fa1734f14d882e91925b6006f79d3bb2460df9d01b333d70090003000000000100906ed5000015150b07ff800e000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000dca1a1841ab2e3fa7025c1d175d2c947df760b3baa4a9a0f30f4fd05718fcfe3000000000000000000000000000000000000000000000000000000000000000083d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fc8f819e864fd03a5d377061e8148d6e5143679000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030333334000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e494e54454c2d53412d3030363135000000000000000000000000000000000000" + }} */ - function testVerify() public { + function testVerify1() public view { verifier.verify( hex"50bd1769188540e643a5e4b1548e4c9391b0359afc1488d25fbfe41395e8847079f64d55148c07b36f0d2d44bfbdcdbe9fc79b48062a75dec02bbd5bfd5e3e530f8fa1520a5f1d99b7cf0bd29b0dbdb4fa65186559593e2c415f1e8ce27ab302cacc917a1db4a97e49f4d82194363c3af262c3b0bcf57fe846130012d081cc8c1fc0337d0de1958f4e4c5755815559104d7576a3bfc0f5fffdb630eace4cc76a5f3b617210692dedcde61b1e581a1700476ae51fa573e0adc0405dcef88e6b902f1364be01080d0fbc1429093d77b320405ff81037e7d1ba6e029baa155b71283e10cbee1e6f5375ed061c83c8ce7e3123774ce8debfd9e90e34c95429eda72d688594b1", hex"e45b67a3c24ff3b77f87fec1533dca31524fc19f02bd433d4e6bba729a7646a7", @@ -28,4 +29,43 @@ contract ZKDCAPVerifierTest is BasicTest { ) ); } + + /* + "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); + 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"); + } +} + +library DCAPValidatorTestHelper { + function parseCommit(bytes calldata commit) public pure returns (DCAPValidator.Output memory) { + return DCAPValidator.parseCommit(commit); + } } From 946486f4c79a92b4b495cdcf9fed01a2c4510bd6 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Thu, 30 Jan 2025 12:48:47 +0900 Subject: [PATCH 7/9] fix imageID parser Signed-off-by: Jun Kimura --- contracts/LCPClientZKDCAPBase.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/LCPClientZKDCAPBase.sol b/contracts/LCPClientZKDCAPBase.sol index 419ed6f..a60818b 100644 --- a/contracts/LCPClientZKDCAPBase.sol +++ b/contracts/LCPClientZKDCAPBase.sol @@ -63,13 +63,12 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { _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"); - // 33..64 bytes: image ID - bytes32 imageId; bytes memory verifierInfo = clientState.zkdcap_verifier_info; + // 32..64 bytes: image ID + bytes32 imageId; assembly { - imageId := mload(add(verifierInfo, 33)) + imageId := mload(add(add(verifierInfo, 32), 32)) } - assert(imageId != 0); clientStorage.zkDCAPRisc0ImageId = imageId; return clientState.latest_height; } From 489da60b73e2b74e37b47c8c3c4948123df92660 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Thu, 30 Jan 2025 13:44:21 +0900 Subject: [PATCH 8/9] fix to check if target enclave's debug option matches `developmentMode` Signed-off-by: Jun Kimura --- contracts/DCAPValidator.sol | 5 ++++- contracts/LCPClientBase.sol | 2 +- contracts/LCPClientZKDCAP.sol | 4 ++-- contracts/LCPClientZKDCAPBase.sol | 11 ++++++++++- test/ZKDCAPVerifier.t.sol | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/contracts/DCAPValidator.sol b/contracts/DCAPValidator.sol index d1a3105..3eeb8f5 100644 --- a/contracts/DCAPValidator.sol +++ b/contracts/DCAPValidator.sol @@ -19,6 +19,7 @@ library DCAPValidator { bytes32 sgxIntelRootCAHash; uint64 validityNotBeforeMax; uint64 validityNotAfterMin; + bool enclaveDebugEnabled; bytes32 mrenclave; address enclaveKey; address operator; @@ -37,7 +38,9 @@ library DCAPValidator { output.validityNotAfterMin = uint64(bytes8(commit[55:63])); uint256 sgxQuoteBodyOffset = 63; - uint256 mrenclaveOffset = sgxQuoteBodyOffset + 16 + 4 + 28 + 16; + 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; diff --git a/contracts/LCPClientBase.sol b/contracts/LCPClientBase.sol index e0e39ff..093c3fa 100644 --- a/contracts/LCPClientBase.sol +++ b/contracts/LCPClientBase.sol @@ -562,7 +562,7 @@ abstract contract LCPClientCommon is ILightClient, ILCPClientErrors { abstract contract LCPClientBase is LCPClientCommon { using IBCHeight for Height.Data; - /// @dev if developmentMode is true, the client allows the remote attestation of IAS in development. + /// @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; diff --git a/contracts/LCPClientZKDCAP.sol b/contracts/LCPClientZKDCAP.sol index 0b6b1c0..6b39239 100644 --- a/contracts/LCPClientZKDCAP.sol +++ b/contracts/LCPClientZKDCAP.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.12; import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol"; contract LCPClientZKDCAP is LCPClientZKDCAPBase { - constructor(address ibcHandler_, bytes memory intelRootCA, address riscZeroVerifier) - LCPClientZKDCAPBase(ibcHandler_, intelRootCA, riscZeroVerifier) + 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 index a60818b..e406a62 100644 --- a/contracts/LCPClientZKDCAPBase.sol +++ b/contracts/LCPClientZKDCAPBase.sol @@ -23,9 +23,16 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { // --------------------- 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 --------------------- @@ -37,12 +44,13 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { /// @custom:oz-upgrades-unsafe-allow constructor /// @param ibcHandler_ the address of the IBC handler contract - constructor(address ibcHandler_, bytes memory intelRootCA, address riscZeroVerifier_) + 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 --------------------- @@ -121,6 +129,7 @@ abstract contract LCPClientZKDCAPBase is LCPClientCommon { "disallowed advisory ID" ); } + require(output.enclaveDebugEnabled == developmentMode, "unexpected enclave debug mode"); // if `operator_signature` is empty, the operator address is zero address operator; diff --git a/test/ZKDCAPVerifier.t.sol b/test/ZKDCAPVerifier.t.sol index cbee95c..eb0164d 100644 --- a/test/ZKDCAPVerifier.t.sol +++ b/test/ZKDCAPVerifier.t.sol @@ -55,6 +55,7 @@ contract ZKDCAPVerifierTest is BasicTest { 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)); @@ -62,6 +63,23 @@ contract ZKDCAPVerifierTest is BasicTest { 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 { From c9e19f1222760224bce3f14ab5b3a14aaa2775b8 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Thu, 30 Jan 2025 13:45:19 +0900 Subject: [PATCH 9/9] add remappings.txt Signed-off-by: Jun Kimura --- remappings.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 remappings.txt 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/