Skip to content

Commit

Permalink
add LCPClientZKDCAP implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele committed Feb 1, 2025
1 parent de3933d commit 4a111ba
Show file tree
Hide file tree
Showing 17 changed files with 871 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Test
on: [pull_request]

env:
SOLC_VERSION: 0.8.20
SOLC_VERSION: 0.8.28

jobs:
contract-test:
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SOLC_VERSION=0.8.20
SOLC_VERSION=0.8.28
FORGE=forge
SLITHER=slither
TEST_UPGRADEABLE=false
Expand Down
74 changes: 74 additions & 0 deletions contracts/DCAPValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;

library DCAPValidator {
uint256 internal constant SGX_QUOTE_BODY_OFFSET = 63;
uint256 internal constant ATTRIBUTES_OFFSET = SGX_QUOTE_BODY_OFFSET + 16 + 4 + 28;
uint256 internal constant MRENCLAVE_OFFSET = ATTRIBUTES_OFFSET + 16;
uint256 internal constant MRENCLAVE_END_OFFSET = MRENCLAVE_OFFSET + 32;
uint256 internal constant REPORT_DATA_OFFSET = SGX_QUOTE_BODY_OFFSET + 320;
uint256 internal constant REPORT_DATA_ENCLAVE_KEY_OFFSET = REPORT_DATA_OFFSET + 1;
uint256 internal constant REPORT_DATA_OPERATOR_OFFSET = REPORT_DATA_ENCLAVE_KEY_OFFSET + 20;
uint256 internal constant REPORT_DATA_OPERATOR_END_OFFSET = REPORT_DATA_OPERATOR_OFFSET + 20;
uint256 internal constant ADVISORY_IDS_OFFSET = REPORT_DATA_OFFSET + 64;

uint8 internal constant TCB_STATUS_UP_TO_DATE = 0;
uint8 internal constant TCB_STATUS_OUT_OF_DATE = 1;
uint8 internal constant TCB_STATUS_REVOKED = 2;
uint8 internal constant TCB_STATUS_CONFIGURATION_NEEDED = 3;
uint8 internal constant TCB_STATUS_OUT_OF_DATE_CONFIGURATION_NEEDED = 4;
uint8 internal constant TCB_STATUS_SW_HARDENING_NEEDED = 5;
uint8 internal constant TCB_STATUS_CONFIGURATION_AND_SW_HARDENING_NEEDED = 6;

struct Output {
uint8 tcbStatus;
bytes32 sgxIntelRootCAHash;
uint64 validityNotBeforeMax;
uint64 validityNotAfterMin;
bool enclaveDebugEnabled;
bytes32 mrenclave;
address enclaveKey;
address operator;
string[] advisoryIDs;
}

function parseCommit(bytes calldata commit) public pure returns (Output memory) {
require(bytes2(commit[0:2]) == hex"0000", "unexpected version");
require(uint16(bytes2(commit[2:4])) == 3, "unexpected quote version");
require(uint32(bytes4(commit[4:8])) == 0, "unexpected tee type");

Output memory output;
output.tcbStatus = uint8(commit[8]);
output.sgxIntelRootCAHash = bytes32(commit[15:47]);
output.validityNotBeforeMax = uint64(bytes8(commit[47:55]));
output.validityNotAfterMin = uint64(bytes8(commit[55:63]));
output.enclaveDebugEnabled = uint8(commit[ATTRIBUTES_OFFSET]) & uint8(2) != 0;
output.mrenclave = bytes32(commit[MRENCLAVE_OFFSET:MRENCLAVE_END_OFFSET]);

require(commit[REPORT_DATA_OFFSET] == 0x01, "unexpected report data version");
output.enclaveKey = address(bytes20(commit[REPORT_DATA_ENCLAVE_KEY_OFFSET:REPORT_DATA_OPERATOR_OFFSET]));
output.operator = address(bytes20(commit[REPORT_DATA_OPERATOR_OFFSET:REPORT_DATA_OPERATOR_END_OFFSET]));
output.advisoryIDs = abi.decode(commit[ADVISORY_IDS_OFFSET:commit.length], (string[]));
return output;
}

function tcbStatusToString(uint8 tcbStatus) public pure returns (string memory) {
if (tcbStatus == TCB_STATUS_UP_TO_DATE) {
return "UpToDate";
} else if (tcbStatus == TCB_STATUS_OUT_OF_DATE) {
return "OutOfDate";
} else if (tcbStatus == TCB_STATUS_REVOKED) {
return "Revoked";
} else if (tcbStatus == TCB_STATUS_CONFIGURATION_NEEDED) {
return "ConfigurationNeeded";
} else if (tcbStatus == TCB_STATUS_OUT_OF_DATE_CONFIGURATION_NEEDED) {
return "OutOfDateConfigurationNeeded";
} else if (tcbStatus == TCB_STATUS_SW_HARDENING_NEEDED) {
return "SWHardeningNeeded";
} else if (tcbStatus == TCB_STATUS_CONFIGURATION_AND_SW_HARDENING_NEEDED) {
return "ConfigurationAndSWHardeningNeeded";
} else {
revert("unexpected TCB status");
}
}
}
14 changes: 14 additions & 0 deletions contracts/ILCPClientErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,18 @@ interface ILCPClientErrors {

error LCPClientUpdateOperatorsPermissionless();
error LCPClientUpdateOperatorsSignatureUnexpectedOperator(address actual, address expected);

error LCPClientZKDCAPInvalidConstructorParams();
error LCPClientZKDCAPOutputNotValid();
error LCPClientZKDCAPUnrecognizedTCBStatus();
error LCPClientZKDCAPInvalidVerifierInfos();
error LCPClientZKDCAPInvalidVerifierInfoLength();
error LCPClientZKDCAPInvalidVerifierInfoZKVMType();
error LCPClientZKDCAPUnsupportedZKVMType();
error LCPClientZKDCAPRisc0ImageIdNotSet();
error LCPClientZKDCAPUnexpectedIntelRootCAHash();

error LCPClientZKDCAPDisallowedTCBStatus();
error LCPClientZKDCAPDisallowedAdvisoryID();
error LCPClientZKDCAPUnexpectedEnclaveDebugMode();
}
3 changes: 3 additions & 0 deletions contracts/LCPClientBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {

struct ClientStorage {
ProtoClientState.Data clientState;
uint256[50] __gap0;
RemoteAttestation.ReportAllowedStatus allowedStatuses;
uint256[50] __gap1;
// height => consensus state
mapping(uint128 => ConsensusState) consensusStates;
// enclave key => EKInfo
mapping(address => EKInfo) ekInfos;
bytes32 zkDCAPRisc0ImageId;
}

// --------------------- Immutable fields ---------------------
Expand Down
10 changes: 10 additions & 0 deletions contracts/LCPClientZKDCAP.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;

import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol";

contract LCPClientZKDCAP is LCPClientZKDCAPBase {
constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier)
LCPClientZKDCAPBase(ibcHandler_, developmentMode_, intelRootCA, riscZeroVerifier)
{}
}
194 changes: 194 additions & 0 deletions contracts/LCPClientZKDCAPBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;

import {IBCHeight} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/IBCHeight.sol";
import {Height} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/Client.sol";
import {IRiscZeroVerifier} from "risc0-ethereum/contracts/src/IRiscZeroVerifier.sol";
import {
IbcLightclientsLcpV1ClientState as ProtoClientState,
IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnclaveKeyMessage
} from "./proto/ibc/lightclients/lcp/v1/LCP.sol";
import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol";
import {LCPClientBase} from "./LCPClientBase.sol";
import {LCPOperator} from "./LCPOperator.sol";
import {RemoteAttestation} from "./RemoteAttestation.sol";
import {DCAPValidator} from "./DCAPValidator.sol";

abstract contract LCPClientZKDCAPBase is LCPClientBase {
using IBCHeight for Height.Data;
// --------------------- Constants ---------------------

uint8 internal constant ZKVM_TYPE_RISC_ZERO = 0x01;

// --------------------- Events ---------------------

event ZKDCAPRegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator);

// --------------------- Immutable fields ---------------------

/// @dev if developmentMode is true, the client allows the target enclave which is debug mode enabled.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
bool internal immutable developmentMode;

/// @notice The hash of the root CA's public key certificate.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
bytes32 public immutable intelRootCAHash;

/// @notice RISC Zero verifier contract address.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IRiscZeroVerifier public immutable riscZeroVerifier;

// --------------------- Storage fields ---------------------

/// @dev Reserved storage space to allow for layout changes in the future
uint256[50] private __gap;

// --------------------- Constructor ---------------------

/// @custom:oz-upgrades-unsafe-allow constructor
/// @param ibcHandler_ the address of the IBC handler contract
constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier_)
LCPClientBase(ibcHandler_)
{
if (intelRootCA.length == 0 || riscZeroVerifier_ == address(0)) {
revert LCPClientZKDCAPInvalidConstructorParams();
}
intelRootCAHash = keccak256(intelRootCA);
riscZeroVerifier = IRiscZeroVerifier(riscZeroVerifier_);
developmentMode = developmentMode_;
}

// --------------------- Public methods ---------------------

/**
* @dev initializeClient initializes a new client with the given state.
* If succeeded, it returns heights at which the consensus state are stored.
* This function is guaranteed by the IBC contract to be called only once for each `clientId`.
* @param clientId the client identifier which is unique within the IBC handler
*/
function initializeClient(
string calldata clientId,
bytes calldata protoClientState,
bytes calldata protoConsensusState
) public override onlyIBC returns (Height.Data memory height) {
ClientStorage storage clientStorage = clientStorages[clientId];
(ProtoClientState.Data memory clientState,) =
_initializeClient(clientStorage, protoClientState, protoConsensusState);
if (clientState.zkdcap_verifier_infos.length != 1) {
revert LCPClientZKDCAPInvalidVerifierInfos();
}
bytes memory verifierInfo = clientState.zkdcap_verifier_infos[0];
if (verifierInfo.length != 64) {
revert LCPClientZKDCAPInvalidVerifierInfoLength();
}
if (uint8(verifierInfo[0]) != ZKVM_TYPE_RISC_ZERO) {
revert LCPClientZKDCAPInvalidVerifierInfoZKVMType();
}
// 32..64 bytes: image ID
bytes32 imageId;
assembly {
imageId := mload(add(add(verifierInfo, 32), 32))
}
clientStorage.zkDCAPRisc0ImageId = imageId;
return clientState.latest_height;
}

/**
* @dev routeUpdateClient returns the calldata to the receiving function of the client message.
* Light client contract may encode a client message as other encoding scheme(e.g. ethereum ABI)
* Check ADR-001 for details.
*/
function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage)
public
pure
override
returns (bytes4, bytes memory)
{
(bytes32 typeUrlHash, bytes memory args) = LCPProtoMarshaler.routeClientMessage(clientId, protoClientMessage);
if (typeUrlHash == LCPProtoMarshaler.UPDATE_CLIENT_MESSAGE_TYPE_URL_HASH) {
return (this.updateClient.selector, args);
} else if (typeUrlHash == LCPProtoMarshaler.ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) {
return (this.zkDCAPRegisterEnclaveKey.selector, args);
} else if (typeUrlHash == LCPProtoMarshaler.UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH) {
return (this.updateOperators.selector, args);
} else {
revert LCPClientUnknownProtoTypeUrl();
}
}

function zkDCAPRegisterEnclaveKey(string calldata clientId, ZKDCAPRegisterEnclaveKeyMessage.Data calldata message)
public
returns (Height.Data[] memory heights)
{
if (message.zkvm_type != ZKVM_TYPE_RISC_ZERO) {
revert LCPClientZKDCAPUnsupportedZKVMType();
}
ClientStorage storage clientStorage = clientStorages[clientId];
if (clientStorage.zkDCAPRisc0ImageId == bytes32(0)) {
revert LCPClientZKDCAPRisc0ImageIdNotSet();
}
riscZeroVerifier.verify(message.proof, clientStorage.zkDCAPRisc0ImageId, sha256(message.commit));
DCAPValidator.Output memory output = DCAPValidator.parseCommit(message.commit);
if (output.sgxIntelRootCAHash != intelRootCAHash) {
revert LCPClientZKDCAPUnexpectedIntelRootCAHash();
}
if (output.mrenclave != bytes32(clientStorage.clientState.mrenclave)) {
revert LCPClientClientStateUnexpectedMrenclave();
}

if (
clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(output.tcbStatus)]
!= RemoteAttestation.FLAG_ALLOWED
) {
revert LCPClientZKDCAPDisallowedTCBStatus();
}
for (uint256 i = 0; i < output.advisoryIDs.length; i++) {
if (
clientStorage.allowedStatuses.allowedAdvisories[output.advisoryIDs[i]] != RemoteAttestation.FLAG_ALLOWED
) {
revert LCPClientZKDCAPDisallowedAdvisoryID();
}
}
if (output.enclaveDebugEnabled != developmentMode) {
revert LCPClientZKDCAPUnexpectedEnclaveDebugMode();
}

// if `operator_signature` is empty, the operator address is zero
address operator;
if (message.operator_signature.length != 0) {
operator = verifyECDSASignature(
keccak256(
LCPOperator.computeEIP712ZKDCAPRegisterEnclaveKey(
clientStorage.clientState.zkdcap_verifier_infos[0], keccak256(message.commit)
)
),
message.operator_signature
);
}
if (output.operator != address(0) && output.operator != operator) {
revert LCPClientAVRUnexpectedOperator(operator, output.operator);
}
if (block.timestamp < output.validityNotBeforeMax || block.timestamp > output.validityNotAfterMin) {
revert LCPClientZKDCAPOutputNotValid();
}
uint64 expiredAt = output.validityNotAfterMin;
EKInfo storage ekInfo = clientStorage.ekInfos[output.enclaveKey];
if (ekInfo.expiredAt != 0) {
if (ekInfo.operator != operator) {
revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, operator);
}
if (ekInfo.expiredAt != expiredAt) {
revert LCPClientEnclaveKeyUnexpectedExpiredAt();
}
// NOTE: if the key already exists, don't update any state
return heights;
}
ekInfo.expiredAt = expiredAt;
ekInfo.operator = operator;

emit ZKDCAPRegisteredEnclaveKey(clientId, output.enclaveKey, expiredAt, operator);

// Note: client and consensus state are not always updated in registerEnclaveKey
return heights;
}
}
21 changes: 21 additions & 0 deletions contracts/LCPClientZKDCAPOwnableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;

import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

/// @custom:oz-upgrades-unsafe-allow external-library-linking
contract LCPClientZKDCAPOwnableUpgradeable is LCPClientZKDCAPBase, UUPSUpgradeable, OwnableUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier_)
LCPClientZKDCAPBase(ibcHandler_, developmentMode_, intelRootCA, riscZeroVerifier_)
{}

function initialize() public initializer {
__UUPSUpgradeable_init();
__Ownable_init(msg.sender);
}

function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {}
}
14 changes: 14 additions & 0 deletions contracts/LCPOperator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
);
Expand Down Expand Up @@ -54,6 +56,18 @@ library LCPOperator {
);
}

function computeEIP712ZKDCAPRegisterEnclaveKey(bytes memory zkdcapVerifierInfo, bytes32 commitHash)
internal
pure
returns (bytes memory)
{
return abi.encodePacked(
hex"1901",
DOMAIN_SEPARATOR_LCP_CLIENT,
keccak256(abi.encode(TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY, keccak256(zkdcapVerifierInfo), commitHash))
);
}

function computeEIP712UpdateOperators(
string calldata clientId,
uint64 nonce,
Expand Down
Loading

0 comments on commit 4a111ba

Please sign in to comment.