@@ -3,6 +3,7 @@ pragma solidity ^0.8.12;
33
44import {IBCHeight} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/IBCHeight.sol " ;
55import {Height} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/Client.sol " ;
6+ import {IIBCHandler} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/25-handler/IIBCHandler.sol " ;
67import {IRiscZeroVerifier} from "risc0-ethereum/contracts/src/IRiscZeroVerifier.sol " ;
78import {
89 IbcLightclientsLcpV1ClientState as ProtoClientState,
@@ -22,7 +23,14 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
2223
2324 // --------------------- Events ---------------------
2425
25- event ZKDCAPRegisteredEnclaveKey (string clientId , address enclaveKey , uint256 expiredAt , address operator );
26+ /// @dev Emitted when an enclave key from zkDCAP quote is registered.
27+ event LCPClientZKDCAPRegisteredEnclaveKey (string clientId , address enclaveKey , uint256 expiredAt , address operator );
28+
29+ /// @dev Emitted when the current TCB evaluation data number is updated.
30+ event LCPClientZKDCAPUpdateCurrentTcbEvaluationDataNumber (string clientId , uint32 tcbEvaluationDataNumber );
31+ /// @dev Emitted when the next TCB evaluation data number is updated.
32+ /// This event is emitted only when the new next TCB evaluation data number is set.
33+ event LCPClientZKDCAPUpdateNextTcbEvaluationDataNumber (string clientId , uint32 tcbEvaluationDataNumber );
2634
2735 // --------------------- Immutable fields ---------------------
2836
@@ -74,22 +82,27 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
7482 ClientStorage storage clientStorage = clientStorages[clientId];
7583 (ProtoClientState.Data memory clientState ,) =
7684 _initializeClient (clientStorage, protoClientState, protoConsensusState);
77- if (clientState.zkdcap_verifier_infos. length != 1 ) {
78- revert LCPClientZKDCAPInvalidVerifierInfos ();
85+ if (clientState.current_tcb_evaluation_data_number == 0 ) {
86+ revert LCPClientZKDCAPCurrentTcbEvaluationDataNumberNotSet ();
7987 }
80- bytes memory verifierInfo = clientState.zkdcap_verifier_infos[0 ];
81- if (verifierInfo.length != 64 ) {
82- revert LCPClientZKDCAPInvalidVerifierInfoLength ();
88+ // check if both next_tcb_evaluation_data_number and next_tcb_evaluation_data_number_update_time are zero or non-zero
89+ if (
90+ (clientState.next_tcb_evaluation_data_number == 0 )
91+ != (clientState.next_tcb_evaluation_data_number_update_time == 0 )
92+ ) {
93+ revert LCPClientZKDCAPInvalidNextTcbEvaluationDataNumberInfo ();
8394 }
84- if (uint8 (verifierInfo[0 ]) != ZKVM_TYPE_RISC_ZERO) {
85- revert LCPClientZKDCAPInvalidVerifierInfoZKVMType ();
95+ if (
96+ clientState.next_tcb_evaluation_data_number != 0
97+ && clientState.current_tcb_evaluation_data_number >= clientState.next_tcb_evaluation_data_number
98+ ) {
99+ revert LCPClientZKDCAPInvalidNextTcbEvaluationDataNumberInfo ();
86100 }
87- // 32..64 bytes: image ID
88- bytes32 imageId;
89- assembly {
90- imageId := mload (add (add (verifierInfo, 32 ), 32 ))
101+ if (clientState.zkdcap_verifier_infos.length != 1 ) {
102+ revert LCPClientZKDCAPInvalidVerifierInfos ();
91103 }
92- clientStorage.zkDCAPRisc0ImageId = imageId;
104+ // Currently, the client only supports RISC Zero zkVM
105+ clientStorage.zkDCAPRisc0ImageId = parseRiscZeroVerifierInfo (clientState.zkdcap_verifier_infos[0 ]);
93106 return clientState.latest_height;
94107 }
95108
@@ -131,6 +144,7 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
131144 if (clientStorage.zkDCAPRisc0ImageId == bytes32 (0 )) {
132145 revert LCPClientZKDCAPRisc0ImageIdNotSet ();
133146 }
147+ ProtoClientState.Data storage clientState = clientStorage.clientState;
134148 // NOTE: the client must revert if the proof is invalid
135149 riscZeroVerifier.verify (
136150 message.proof, clientStorage.zkDCAPRisc0ImageId, sha256 (message.quote_verification_output)
@@ -139,7 +153,7 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
139153 if (output.sgxIntelRootCAHash != intelRootCAHash) {
140154 revert LCPClientZKDCAPUnexpectedIntelRootCAHash ();
141155 }
142- if (output.mrenclave != bytes32 (clientStorage. clientState.mrenclave)) {
156+ if (output.mrenclave != bytes32 (clientState.mrenclave)) {
143157 revert LCPClientClientStateUnexpectedMrenclave ();
144158 }
145159
@@ -162,14 +176,30 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
162176 }
163177 }
164178
179+ // check if the validity period of the output is valid at the current block timestamp
180+ if (block .timestamp < output.validityNotBeforeMax || block .timestamp > output.validityNotAfterMin) {
181+ revert LCPClientZKDCAPOutputNotValid ();
182+ }
183+
165184 // check if the `output.enclaveDebugEnabled` and `developmentMode` are consistent
166185 if (output.enclaveDebugEnabled != developmentMode) {
167186 revert LCPClientZKDCAPUnexpectedEnclaveDebugMode ();
168187 }
169188
170- // check if the validity period of the output is valid at the current block timestamp
171- if (block .timestamp < output.validityNotBeforeMax || block .timestamp > output.validityNotAfterMin) {
172- revert LCPClientZKDCAPOutputNotValid ();
189+ (bool currentUpdated , bool nextUpdated ) =
190+ checkAndUpdateTcbEvaluationDataNumber (clientId, output.minTcbEvaluationDataNumber);
191+ if (currentUpdated) {
192+ emit LCPClientZKDCAPUpdateCurrentTcbEvaluationDataNumber (
193+ clientId, clientState.current_tcb_evaluation_data_number
194+ );
195+ }
196+ if (nextUpdated) {
197+ emit LCPClientZKDCAPUpdateNextTcbEvaluationDataNumber (clientId, clientState.next_tcb_evaluation_data_number);
198+ }
199+ if (currentUpdated || nextUpdated) {
200+ // update the commitment of the client state in the IBC handler
201+ // `heights` is always empty because the consensus state is never updated in this function
202+ IIBCHandler (ibcHandler).updateClientCommitments (clientId, heights);
173203 }
174204
175205 // if `operator_signature` is empty, the operator address is zero
@@ -178,7 +208,7 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
178208 operator = verifyECDSASignature (
179209 keccak256 (
180210 LCPOperator.computeEIP712ZKDCAPRegisterEnclaveKey (
181- clientStorage. clientState.zkdcap_verifier_infos[0 ], keccak256 (message.quote_verification_output)
211+ clientState.zkdcap_verifier_infos[0 ], keccak256 (message.quote_verification_output)
182212 )
183213 ),
184214 message.operator_signature
@@ -197,15 +227,135 @@ abstract contract LCPClientZKDCAPBase is LCPClientBase {
197227 if (ekInfo.expiredAt != expiredAt) {
198228 revert LCPClientEnclaveKeyUnexpectedExpiredAt ();
199229 }
200- // NOTE: if the key already exists, don't update any state
201230 return heights;
202231 }
203232 ekInfo.expiredAt = expiredAt;
204233 ekInfo.operator = operator;
205234
206- emit ZKDCAPRegisteredEnclaveKey (clientId, output.enclaveKey, expiredAt, operator);
235+ emit LCPClientZKDCAPRegisteredEnclaveKey (clientId, output.enclaveKey, expiredAt, operator);
207236
208- // Note: client and consensus state are not always updated in registerEnclaveKey
209237 return heights;
210238 }
239+
240+ // --------------------- Internal methods --------------------- //
241+
242+ function parseRiscZeroVerifierInfo (bytes memory verifierInfo ) internal pure returns (bytes32 ) {
243+ // The verifier information for the zkDCAP
244+ //
245+ // The format is as follows:
246+ // 0: zkVM type
247+ // 1-N: arbitrary data for each zkVM type
248+ //
249+ // The format of the risc0 zkVM is as follows:
250+ // | 0 | 1 - 31 | 32 - 64 |
251+ // |---|----------|-----------|
252+ // | 1 | reserved | image id |
253+ uint256 vlen = verifierInfo.length ;
254+ if (vlen == 0 ) {
255+ revert LCPClientZKDCAPInvalidVerifierInfoLength ();
256+ }
257+ // Currently, the client only supports RISC Zero zkVM
258+ if (uint8 (verifierInfo[0 ]) != ZKVM_TYPE_RISC_ZERO) {
259+ revert LCPClientZKDCAPInvalidVerifierInfoZKVMType ();
260+ }
261+ if (vlen < 64 ) {
262+ revert LCPClientZKDCAPInvalidVerifierInfoLength ();
263+ }
264+ // 32..64 bytes: image ID
265+ bytes32 imageId;
266+ assembly {
267+ imageId := mload (add (add (verifierInfo, 32 ), 32 ))
268+ }
269+ return imageId;
270+ }
271+
272+ /// @dev checkAndUpdateTcbEvaluationDataNumber checks if the current or next TCB evaluation data number update is required.
273+ /// @param clientId the client identifier
274+ /// @param outputTcbEvaluationDataNumber the minimum TCB evaluation data number of the zkDCAP output
275+ /// @return currentUpdated true if the current TCB evaluation data number is updated
276+ /// @return nextUpdated true if the next TCB evaluation data number is updated
277+ function checkAndUpdateTcbEvaluationDataNumber (string calldata clientId , uint32 outputTcbEvaluationDataNumber )
278+ internal
279+ returns (bool currentUpdated , bool nextUpdated )
280+ {
281+ ProtoClientState.Data storage clientState = clientStorages[clientId].clientState;
282+
283+ // check if the current or next TCB evaluation data number update is required
284+ if (
285+ clientState.next_tcb_evaluation_data_number != 0
286+ && block .timestamp >= clientState.next_tcb_evaluation_data_number_update_time
287+ ) {
288+ clientState.current_tcb_evaluation_data_number = clientState.next_tcb_evaluation_data_number;
289+ clientState.next_tcb_evaluation_data_number = 0 ;
290+ clientState.next_tcb_evaluation_data_number_update_time = 0 ;
291+ currentUpdated = true ;
292+ // NOTE:
293+ // - If the current number is updated again in a subsequent process, only one event is emitted
294+ // - A new next TCB evaluation data number is not set, so the `next` is false here
295+ }
296+
297+ if (outputTcbEvaluationDataNumber > clientState.current_tcb_evaluation_data_number) {
298+ if (clientState.tcb_evaluation_data_number_update_grace_period == 0 ) {
299+ // If the grace period is zero, the client immediately updates the current TCB evaluation data number
300+ clientState.current_tcb_evaluation_data_number = outputTcbEvaluationDataNumber;
301+ // If the grace period is zero, the `next_tcb_evaluation_data_number` and `next_tcb_evaluation_data_number_update_time` must always be zero
302+ // Otherwise, there is an internal error in the client
303+ require (
304+ clientState.next_tcb_evaluation_data_number == 0
305+ && clientState.next_tcb_evaluation_data_number_update_time == 0
306+ );
307+ return (true , false );
308+ } else {
309+ // If the grace period is not zero, there may be a next TCB evaluation data number update in the client state
310+
311+ uint64 nextUpdateTime =
312+ uint64 (block .timestamp ) + clientState.tcb_evaluation_data_number_update_grace_period;
313+
314+ // If the next TCB evaluation data number is not set, the client sets the next TCB evaluation data number to the output's TCB evaluation data number
315+ if (clientState.next_tcb_evaluation_data_number == 0 ) {
316+ clientState.next_tcb_evaluation_data_number = outputTcbEvaluationDataNumber;
317+ clientState.next_tcb_evaluation_data_number_update_time = nextUpdateTime;
318+ return (currentUpdated, true );
319+ }
320+
321+ // If the next TCB evaluation data number is set, the client updates the next TCB evaluation data number
322+
323+ if (outputTcbEvaluationDataNumber > clientState.next_tcb_evaluation_data_number) {
324+ // Edge case 1. clientState.current_tcb_evaluation_data_number < clientState.next_tcb_evaluation_data_number < outputTcbEvaluationDataNumber
325+ //
326+ // In this case, the client immediately updates the current TCB evaluation data number with the `clientState.next_tcb_evaluation_data_number`
327+ // and updates the next TCB evaluation data number with the `outputTcbEvaluationDataNumber`
328+ //
329+ // This case can be caused by too long grace period values or multiple TCB Recovery Events with very short intervals.
330+ // Note that in this case the current number is updated ignoring the grace period setting.
331+ // However, the current number is still a non-latest number, so there should be no problem for the operator operating as expected.
332+ clientState.current_tcb_evaluation_data_number = clientState.next_tcb_evaluation_data_number;
333+ clientState.next_tcb_evaluation_data_number = outputTcbEvaluationDataNumber;
334+ clientState.next_tcb_evaluation_data_number_update_time = nextUpdateTime;
335+ return (true , true );
336+ } else if (outputTcbEvaluationDataNumber < clientState.next_tcb_evaluation_data_number) {
337+ // Edge case 2. clientState.current_tcb_evaluation_data_number < outputTcbEvaluationDataNumber < clientState.next_tcb_evaluation_data_number
338+ //
339+ // In this case, the client immediately updates the current TCB evaluation data number with the `outputTcbEvaluationDataNumber`
340+ // and does not update the next TCB evaluation data number.
341+ //
342+ // This case can be caused by too long grace period values or multiple TCB Recovery Events with very short intervals.
343+ // Note that in this case the current number is updated ignoring the grace period setting.
344+ // However, the current number is still a non-latest number, so there should be no problem for the operator operating as expected.
345+ clientState.current_tcb_evaluation_data_number = outputTcbEvaluationDataNumber;
346+ return (true , false );
347+ } else {
348+ // General case. outputTcbEvaluationDataNumber == clientState.next_tcb_evaluation_data_number
349+ // In this case, the client already has the next TCB evaluation data number, so it does not need to be updated
350+ return (currentUpdated, false );
351+ }
352+ }
353+ } else if (outputTcbEvaluationDataNumber < clientState.current_tcb_evaluation_data_number) {
354+ // The client must revert if the output's TCB evaluation data number is less than the current TCB evaluation data number
355+ revert LCPClientZKDCAPUnexpectedTcbEvaluationDataNumber (clientState.current_tcb_evaluation_data_number);
356+ } else {
357+ // nop: case outputTcbEvaluationDataNumber == clientState.current_tcb_evaluation_data_number
358+ return (currentUpdated, false );
359+ }
360+ }
211361}
0 commit comments