Skip to content

Commit d263e16

Browse files
committed
add LCPClientZKDCAP implementation
Signed-off-by: Jun Kimura <jun.kimura@datachain.jp>
1 parent de3933d commit d263e16

File tree

15 files changed

+869
-5
lines changed

15 files changed

+869
-5
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "lib/openzeppelin-foundry-upgrades"]
55
path = lib/openzeppelin-foundry-upgrades
66
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
7+
[submodule "lib/risc0-ethereum"]
8+
path = lib/risc0-ethereum
9+
url = https://github.com/risc0/risc0-ethereum

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SOLC_VERSION=0.8.20
1+
SOLC_VERSION=0.8.28
22
FORGE=forge
33
SLITHER=slither
44
TEST_UPGRADEABLE=false

contracts/DCAPValidator.sol

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.12;
3+
4+
library DCAPValidator {
5+
uint256 internal constant SGX_QUOTE_BODY_OFFSET = 63;
6+
uint256 internal constant ATTRIBUTES_OFFSET = SGX_QUOTE_BODY_OFFSET + 16 + 4 + 28;
7+
uint256 internal constant MRENCLAVE_OFFSET = ATTRIBUTES_OFFSET + 16;
8+
uint256 internal constant MRENCLAVE_END_OFFSET = MRENCLAVE_OFFSET + 32;
9+
uint256 internal constant REPORT_DATA_OFFSET = SGX_QUOTE_BODY_OFFSET + 320;
10+
uint256 internal constant REPORT_DATA_ENCLAVE_KEY_OFFSET = REPORT_DATA_OFFSET + 1;
11+
uint256 internal constant REPORT_DATA_OPERATOR_OFFSET = REPORT_DATA_ENCLAVE_KEY_OFFSET + 20;
12+
uint256 internal constant REPORT_DATA_OPERATOR_END_OFFSET = REPORT_DATA_OPERATOR_OFFSET + 20;
13+
uint256 internal constant ADVISORY_IDS_OFFSET = REPORT_DATA_OFFSET + 64;
14+
15+
uint8 internal constant TCB_STATUS_UP_TO_DATE = 0;
16+
uint8 internal constant TCB_STATUS_OUT_OF_DATE = 1;
17+
uint8 internal constant TCB_STATUS_REVOKED = 2;
18+
uint8 internal constant TCB_STATUS_CONFIGURATION_NEEDED = 3;
19+
uint8 internal constant TCB_STATUS_OUT_OF_DATE_CONFIGURATION_NEEDED = 4;
20+
uint8 internal constant TCB_STATUS_SW_HARDENING_NEEDED = 5;
21+
uint8 internal constant TCB_STATUS_CONFIGURATION_AND_SW_HARDENING_NEEDED = 6;
22+
23+
struct Output {
24+
uint8 tcbStatus;
25+
bytes32 sgxIntelRootCAHash;
26+
uint64 validityNotBeforeMax;
27+
uint64 validityNotAfterMin;
28+
bool enclaveDebugEnabled;
29+
bytes32 mrenclave;
30+
address enclaveKey;
31+
address operator;
32+
string[] advisoryIDs;
33+
}
34+
35+
function parseCommit(bytes calldata commit) public pure returns (Output memory) {
36+
require(bytes2(commit[0:2]) == hex"0000", "unexpected version");
37+
require(uint16(bytes2(commit[2:4])) == 3, "unexpected quote version");
38+
require(uint32(bytes4(commit[4:8])) == 0, "unexpected tee type");
39+
40+
Output memory output;
41+
output.tcbStatus = uint8(commit[8]);
42+
output.sgxIntelRootCAHash = bytes32(commit[15:47]);
43+
output.validityNotBeforeMax = uint64(bytes8(commit[47:55]));
44+
output.validityNotAfterMin = uint64(bytes8(commit[55:63]));
45+
output.enclaveDebugEnabled = uint8(commit[ATTRIBUTES_OFFSET]) & uint8(2) != 0;
46+
output.mrenclave = bytes32(commit[MRENCLAVE_OFFSET:MRENCLAVE_END_OFFSET]);
47+
48+
require(commit[REPORT_DATA_OFFSET] == 0x01, "unexpected report data version");
49+
output.enclaveKey = address(bytes20(commit[REPORT_DATA_ENCLAVE_KEY_OFFSET:REPORT_DATA_OPERATOR_OFFSET]));
50+
output.operator = address(bytes20(commit[REPORT_DATA_OPERATOR_OFFSET:REPORT_DATA_OPERATOR_END_OFFSET]));
51+
output.advisoryIDs = abi.decode(commit[ADVISORY_IDS_OFFSET:commit.length], (string[]));
52+
return output;
53+
}
54+
55+
function tcbStatusToString(uint8 tcbStatus) public pure returns (string memory) {
56+
if (tcbStatus == TCB_STATUS_UP_TO_DATE) {
57+
return "UpToDate";
58+
} else if (tcbStatus == TCB_STATUS_OUT_OF_DATE) {
59+
return "OutOfDate";
60+
} else if (tcbStatus == TCB_STATUS_REVOKED) {
61+
return "Revoked";
62+
} else if (tcbStatus == TCB_STATUS_CONFIGURATION_NEEDED) {
63+
return "ConfigurationNeeded";
64+
} else if (tcbStatus == TCB_STATUS_OUT_OF_DATE_CONFIGURATION_NEEDED) {
65+
return "OutOfDateConfigurationNeeded";
66+
} else if (tcbStatus == TCB_STATUS_SW_HARDENING_NEEDED) {
67+
return "SWHardeningNeeded";
68+
} else if (tcbStatus == TCB_STATUS_CONFIGURATION_AND_SW_HARDENING_NEEDED) {
69+
return "ConfigurationAndSWHardeningNeeded";
70+
} else {
71+
revert("unexpected TCB status");
72+
}
73+
}
74+
}

contracts/ILCPClientErrors.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,18 @@ interface ILCPClientErrors {
5858

5959
error LCPClientUpdateOperatorsPermissionless();
6060
error LCPClientUpdateOperatorsSignatureUnexpectedOperator(address actual, address expected);
61+
62+
error LCPClientZKDCAPInvalidConstructorParams();
63+
error LCPClientZKDCAPOutputNotValid();
64+
error LCPClientZKDCAPUnrecognizedTCBStatus();
65+
error LCPClientZKDCAPInvalidVerifierInfos();
66+
error LCPClientZKDCAPInvalidVerifierInfoLength();
67+
error LCPClientZKDCAPInvalidVerifierInfoZKVMType();
68+
error LCPClientZKDCAPUnsupportedZKVMType();
69+
error LCPClientZKDCAPRisc0ImageIdNotSet();
70+
error LCPClientZKDCAPUnexpectedIntelRootCAHash();
71+
72+
error LCPClientZKDCAPDisallowedTCBStatus();
73+
error LCPClientZKDCAPDisallowedAdvisoryID();
74+
error LCPClientZKDCAPUnexpectedEnclaveDebugMode();
6175
}

contracts/LCPClientBase.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ abstract contract LCPClientBase is ILightClient, ILCPClientErrors {
3434

3535
struct ClientStorage {
3636
ProtoClientState.Data clientState;
37+
uint256[50] __gap0;
3738
RemoteAttestation.ReportAllowedStatus allowedStatuses;
39+
uint256[50] __gap1;
3840
// height => consensus state
3941
mapping(uint128 => ConsensusState) consensusStates;
4042
// enclave key => EKInfo
4143
mapping(address => EKInfo) ekInfos;
44+
bytes32 zkDCAPRisc0ImageId;
4245
}
4346

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

contracts/LCPClientZKDCAP.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.12;
3+
4+
import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol";
5+
6+
contract LCPClientZKDCAP is LCPClientZKDCAPBase {
7+
constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier)
8+
LCPClientZKDCAPBase(ibcHandler_, developmentMode_, intelRootCA, riscZeroVerifier)
9+
{}
10+
}

contracts/LCPClientZKDCAPBase.sol

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.12;
3+
4+
import {IBCHeight} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/IBCHeight.sol";
5+
import {Height} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/Client.sol";
6+
import {IRiscZeroVerifier} from "risc0-ethereum/contracts/src/IRiscZeroVerifier.sol";
7+
import {
8+
IbcLightclientsLcpV1ClientState as ProtoClientState,
9+
IbcLightclientsLcpV1ZKDCAPRegisterEnclaveKeyMessage as ZKDCAPRegisterEnclaveKeyMessage
10+
} from "./proto/ibc/lightclients/lcp/v1/LCP.sol";
11+
import {LCPProtoMarshaler} from "./LCPProtoMarshaler.sol";
12+
import {LCPClientBase} from "./LCPClientBase.sol";
13+
import {LCPOperator} from "./LCPOperator.sol";
14+
import {RemoteAttestation} from "./RemoteAttestation.sol";
15+
import {DCAPValidator} from "./DCAPValidator.sol";
16+
17+
abstract contract LCPClientZKDCAPBase is LCPClientBase {
18+
using IBCHeight for Height.Data;
19+
// --------------------- Constants ---------------------
20+
21+
uint8 internal constant ZKVM_TYPE_RISC_ZERO = 0x01;
22+
23+
// --------------------- Events ---------------------
24+
25+
event ZKDCAPRegisteredEnclaveKey(string clientId, address enclaveKey, uint256 expiredAt, address operator);
26+
27+
// --------------------- Immutable fields ---------------------
28+
29+
/// @dev if developmentMode is true, the client allows the target enclave which is debug mode enabled.
30+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
31+
bool internal immutable developmentMode;
32+
33+
/// @notice The hash of the root CA's public key certificate.
34+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
35+
bytes32 public immutable intelRootCAHash;
36+
37+
/// @notice RISC Zero verifier contract address.
38+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
39+
IRiscZeroVerifier public immutable riscZeroVerifier;
40+
41+
// --------------------- Storage fields ---------------------
42+
43+
/// @dev Reserved storage space to allow for layout changes in the future
44+
uint256[50] private __gap;
45+
46+
// --------------------- Constructor ---------------------
47+
48+
/// @custom:oz-upgrades-unsafe-allow constructor
49+
/// @param ibcHandler_ the address of the IBC handler contract
50+
constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier_)
51+
LCPClientBase(ibcHandler_)
52+
{
53+
if (intelRootCA.length == 0 || riscZeroVerifier_ == address(0)) {
54+
revert LCPClientZKDCAPInvalidConstructorParams();
55+
}
56+
intelRootCAHash = keccak256(intelRootCA);
57+
riscZeroVerifier = IRiscZeroVerifier(riscZeroVerifier_);
58+
developmentMode = developmentMode_;
59+
}
60+
61+
// --------------------- Public methods ---------------------
62+
63+
/**
64+
* @dev initializeClient initializes a new client with the given state.
65+
* If succeeded, it returns heights at which the consensus state are stored.
66+
* This function is guaranteed by the IBC contract to be called only once for each `clientId`.
67+
* @param clientId the client identifier which is unique within the IBC handler
68+
*/
69+
function initializeClient(
70+
string calldata clientId,
71+
bytes calldata protoClientState,
72+
bytes calldata protoConsensusState
73+
) public override onlyIBC returns (Height.Data memory height) {
74+
ClientStorage storage clientStorage = clientStorages[clientId];
75+
(ProtoClientState.Data memory clientState,) =
76+
_initializeClient(clientStorage, protoClientState, protoConsensusState);
77+
if (clientState.zkdcap_verifier_infos.length != 1) {
78+
revert LCPClientZKDCAPInvalidVerifierInfos();
79+
}
80+
bytes memory verifierInfo = clientState.zkdcap_verifier_infos[0];
81+
if (verifierInfo.length != 64) {
82+
revert LCPClientZKDCAPInvalidVerifierInfoLength();
83+
}
84+
if (uint8(verifierInfo[0]) != ZKVM_TYPE_RISC_ZERO) {
85+
revert LCPClientZKDCAPInvalidVerifierInfoZKVMType();
86+
}
87+
// 32..64 bytes: image ID
88+
bytes32 imageId;
89+
assembly {
90+
imageId := mload(add(add(verifierInfo, 32), 32))
91+
}
92+
clientStorage.zkDCAPRisc0ImageId = imageId;
93+
return clientState.latest_height;
94+
}
95+
96+
/**
97+
* @dev routeUpdateClient returns the calldata to the receiving function of the client message.
98+
* Light client contract may encode a client message as other encoding scheme(e.g. ethereum ABI)
99+
* Check ADR-001 for details.
100+
*/
101+
function routeUpdateClient(string calldata clientId, bytes calldata protoClientMessage)
102+
public
103+
pure
104+
override
105+
returns (bytes4, bytes memory)
106+
{
107+
(bytes32 typeUrlHash, bytes memory args) = LCPProtoMarshaler.routeClientMessage(clientId, protoClientMessage);
108+
if (typeUrlHash == LCPProtoMarshaler.UPDATE_CLIENT_MESSAGE_TYPE_URL_HASH) {
109+
return (this.updateClient.selector, args);
110+
} else if (typeUrlHash == LCPProtoMarshaler.ZKDCAP_REGISTER_ENCLAVE_KEY_MESSAGE_TYPE_URL_HASH) {
111+
return (this.zkDCAPRegisterEnclaveKey.selector, args);
112+
} else if (typeUrlHash == LCPProtoMarshaler.UPDATE_OPERATORS_MESSAGE_TYPE_URL_HASH) {
113+
return (this.updateOperators.selector, args);
114+
} else {
115+
revert LCPClientUnknownProtoTypeUrl();
116+
}
117+
}
118+
119+
function zkDCAPRegisterEnclaveKey(string calldata clientId, ZKDCAPRegisterEnclaveKeyMessage.Data calldata message)
120+
public
121+
returns (Height.Data[] memory heights)
122+
{
123+
if (message.zkvm_type != ZKVM_TYPE_RISC_ZERO) {
124+
revert LCPClientZKDCAPUnsupportedZKVMType();
125+
}
126+
ClientStorage storage clientStorage = clientStorages[clientId];
127+
if (clientStorage.zkDCAPRisc0ImageId == bytes32(0)) {
128+
revert LCPClientZKDCAPRisc0ImageIdNotSet();
129+
}
130+
riscZeroVerifier.verify(message.proof, clientStorage.zkDCAPRisc0ImageId, sha256(message.commit));
131+
DCAPValidator.Output memory output = DCAPValidator.parseCommit(message.commit);
132+
if (output.sgxIntelRootCAHash != intelRootCAHash) {
133+
revert LCPClientZKDCAPUnexpectedIntelRootCAHash();
134+
}
135+
if (output.mrenclave != bytes32(clientStorage.clientState.mrenclave)) {
136+
revert LCPClientClientStateUnexpectedMrenclave();
137+
}
138+
139+
if (
140+
clientStorage.allowedStatuses.allowedQuoteStatuses[DCAPValidator.tcbStatusToString(output.tcbStatus)]
141+
!= RemoteAttestation.FLAG_ALLOWED
142+
) {
143+
revert LCPClientZKDCAPDisallowedTCBStatus();
144+
}
145+
for (uint256 i = 0; i < output.advisoryIDs.length; i++) {
146+
if (
147+
clientStorage.allowedStatuses.allowedAdvisories[output.advisoryIDs[i]] != RemoteAttestation.FLAG_ALLOWED
148+
) {
149+
revert LCPClientZKDCAPDisallowedAdvisoryID();
150+
}
151+
}
152+
if (output.enclaveDebugEnabled != developmentMode) {
153+
revert LCPClientZKDCAPUnexpectedEnclaveDebugMode();
154+
}
155+
156+
// if `operator_signature` is empty, the operator address is zero
157+
address operator;
158+
if (message.operator_signature.length != 0) {
159+
operator = verifyECDSASignature(
160+
keccak256(
161+
LCPOperator.computeEIP712ZKDCAPRegisterEnclaveKey(
162+
clientStorage.clientState.zkdcap_verifier_infos[0], keccak256(message.commit)
163+
)
164+
),
165+
message.operator_signature
166+
);
167+
}
168+
if (output.operator != address(0) && output.operator != operator) {
169+
revert LCPClientAVRUnexpectedOperator(operator, output.operator);
170+
}
171+
if (block.timestamp < output.validityNotBeforeMax || block.timestamp > output.validityNotAfterMin) {
172+
revert LCPClientZKDCAPOutputNotValid();
173+
}
174+
uint64 expiredAt = output.validityNotAfterMin;
175+
EKInfo storage ekInfo = clientStorage.ekInfos[output.enclaveKey];
176+
if (ekInfo.expiredAt != 0) {
177+
if (ekInfo.operator != operator) {
178+
revert LCPClientEnclaveKeyUnexpectedOperator(ekInfo.operator, operator);
179+
}
180+
if (ekInfo.expiredAt != expiredAt) {
181+
revert LCPClientEnclaveKeyUnexpectedExpiredAt();
182+
}
183+
// NOTE: if the key already exists, don't update any state
184+
return heights;
185+
}
186+
ekInfo.expiredAt = expiredAt;
187+
ekInfo.operator = operator;
188+
189+
emit ZKDCAPRegisteredEnclaveKey(clientId, output.enclaveKey, expiredAt, operator);
190+
191+
// Note: client and consensus state are not always updated in registerEnclaveKey
192+
return heights;
193+
}
194+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.12;
3+
4+
import {LCPClientZKDCAPBase} from "./LCPClientZKDCAPBase.sol";
5+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
7+
8+
/// @custom:oz-upgrades-unsafe-allow external-library-linking
9+
contract LCPClientZKDCAPOwnableUpgradeable is LCPClientZKDCAPBase, UUPSUpgradeable, OwnableUpgradeable {
10+
/// @custom:oz-upgrades-unsafe-allow constructor
11+
constructor(address ibcHandler_, bool developmentMode_, bytes memory intelRootCA, address riscZeroVerifier_)
12+
LCPClientZKDCAPBase(ibcHandler_, developmentMode_, intelRootCA, riscZeroVerifier_)
13+
{}
14+
15+
function initialize() public initializer {
16+
__UUPSUpgradeable_init();
17+
__Ownable_init(msg.sender);
18+
}
19+
20+
function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {}
21+
}

contracts/LCPOperator.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ library LCPOperator {
77
bytes32 internal constant TYPEHASH_DOMAIN_SEPARATOR =
88
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)");
99
bytes32 internal constant TYPEHASH_REGISTER_ENCLAVE_KEY = keccak256("RegisterEnclaveKey(string avr)");
10+
bytes32 internal constant TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY =
11+
keccak256("ZKDCAPRegisterEnclaveKey(bytes zkDCAPVerifierInfo,bytes32 commitHash)");
1012
bytes32 internal constant TYPEHASH_UPDATE_OPERATORS = keccak256(
1113
"UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)"
1214
);
@@ -54,6 +56,18 @@ library LCPOperator {
5456
);
5557
}
5658

59+
function computeEIP712ZKDCAPRegisterEnclaveKey(bytes memory zkdcapVerifierInfo, bytes32 commitHash)
60+
internal
61+
pure
62+
returns (bytes memory)
63+
{
64+
return abi.encodePacked(
65+
hex"1901",
66+
DOMAIN_SEPARATOR_LCP_CLIENT,
67+
keccak256(abi.encode(TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY, keccak256(zkdcapVerifierInfo), commitHash))
68+
);
69+
}
70+
5771
function computeEIP712UpdateOperators(
5872
string calldata clientId,
5973
uint64 nonce,

0 commit comments

Comments
 (0)