Skip to content

Commit 1fb2aef

Browse files
authored
Merge pull request #32 from datachainlab/zkdcap-tcb-evaluation-data-number-validation
Add tcb evaluation data number validation Signed-off-by: Jun Kimura <[email protected]>
2 parents c2e7084 + 179e0ef commit 1fb2aef

File tree

7 files changed

+401
-52
lines changed

7 files changed

+401
-52
lines changed

contracts/DCAPValidator.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ library DCAPValidator {
4848
*/
4949
struct Output {
5050
string tcbStatus;
51+
uint32 minTcbEvaluationDataNumber;
5152
bytes32 sgxIntelRootCAHash;
5253
uint64 validityNotBeforeMax;
5354
uint64 validityNotAfterMin;
@@ -70,6 +71,7 @@ library DCAPValidator {
7071

7172
Output memory output;
7273
output.tcbStatus = tcbStatusToString(uint8(outputBytes[8]));
74+
output.minTcbEvaluationDataNumber = uint32(bytes4(outputBytes[9:13]));
7375
output.sgxIntelRootCAHash = bytes32(outputBytes[19:51]);
7476
output.validityNotBeforeMax = uint64(bytes8(outputBytes[51:59]));
7577
output.validityNotAfterMin = uint64(bytes8(outputBytes[59:67]));

contracts/ILCPClientErrors.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ interface ILCPClientErrors {
6262
error LCPClientZKDCAPInvalidConstructorParams();
6363
error LCPClientZKDCAPOutputNotValid();
6464
error LCPClientZKDCAPUnrecognizedTCBStatus();
65+
error LCPClientZKDCAPCurrentTcbEvaluationDataNumberNotSet();
66+
error LCPClientZKDCAPInvalidNextTcbEvaluationDataNumberInfo();
6567
error LCPClientZKDCAPInvalidVerifierInfos();
6668
error LCPClientZKDCAPInvalidVerifierInfoLength();
6769
error LCPClientZKDCAPInvalidVerifierInfoZKVMType();
@@ -72,4 +74,5 @@ interface ILCPClientErrors {
7274
error LCPClientZKDCAPDisallowedTCBStatus();
7375
error LCPClientZKDCAPDisallowedAdvisoryID();
7476
error LCPClientZKDCAPUnexpectedEnclaveDebugMode();
77+
error LCPClientZKDCAPUnexpectedTcbEvaluationDataNumber(uint64 currentTcbEvaluationDataNumber);
7578
}

contracts/LCPClientZKDCAPBase.sol

Lines changed: 171 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity ^0.8.12;
33

44
import {IBCHeight} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02-client/IBCHeight.sol";
55
import {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";
67
import {IRiscZeroVerifier} from "risc0-ethereum/contracts/src/IRiscZeroVerifier.sol";
78
import {
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
}

contracts/LCPOperator.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ library LCPOperator {
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)");
1010
bytes32 internal constant TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY =
11-
keccak256("ZKDCAPRegisterEnclaveKey(bytes zkDCAPVerifierInfo,bytes32 commitHash)");
11+
keccak256("ZKDCAPRegisterEnclaveKey(bytes zkDCAPVerifierInfo,bytes32 outputHash)");
1212
bytes32 internal constant TYPEHASH_UPDATE_OPERATORS = keccak256(
1313
"UpdateOperators(string clientId,uint64 nonce,address[] newOperators,uint64 thresholdNumerator,uint64 thresholdDenominator)"
1414
);
@@ -56,15 +56,15 @@ library LCPOperator {
5656
);
5757
}
5858

59-
function computeEIP712ZKDCAPRegisterEnclaveKey(bytes memory zkdcapVerifierInfo, bytes32 commitHash)
59+
function computeEIP712ZKDCAPRegisterEnclaveKey(bytes memory zkdcapVerifierInfo, bytes32 outputHash)
6060
internal
6161
pure
6262
returns (bytes memory)
6363
{
6464
return abi.encodePacked(
6565
hex"1901",
6666
DOMAIN_SEPARATOR_LCP_CLIENT,
67-
keccak256(abi.encode(TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY, keccak256(zkdcapVerifierInfo), commitHash))
67+
keccak256(abi.encode(TYPEHASH_ZKDCAP_REGISTER_ENCLAVE_KEY, keccak256(zkdcapVerifierInfo), outputHash))
6868
);
6969
}
7070

0 commit comments

Comments
 (0)