Skip to content

Commit a43e701

Browse files
authored
feat(nitro-verifier): add expiry-aware intermediate certificate caching (#240)
* feat(nitro-verifier): add expiry-aware intermediate certificate caching Change trustedIntermediateCerts mapping from bytes32=>bool to bytes32=>uint64, where the value is the certificate's notAfter timestamp in seconds (0 = not cached). Cached certs are now automatically treated as untrusted once block.timestamp exceeds their expiry, closing a security gap where cached entries could outlive their X.509 validity. Changes: - INitroEnclaveVerifier.sol: Add certExpiries field to VerifierJournal - NitroEnclaveVerifier.sol: uint64 mapping, expiry checks in _verifyJournal, checkTrustedIntermediateCerts, _cacheNewCert, revokeCert; constructor accepts parallel expiries array - DeployRiscZeroStack.s.sol: Pass empty expiries array to constructor - Tests: 6 new tests for expiry semantics, all existing tests updated - Semver: 0.1.0 -> 0.2.0 CHAIN-3889 * refactor(nitro-verifier): remove redundant expiry == 0 checks since block.timestamp is always positive * chore: regenerate semver-lock after expiry check simplification
1 parent 4cca72b commit a43e701

File tree

7 files changed

+196
-23
lines changed

7 files changed

+196
-23
lines changed

interfaces/multiproof/tee/INitroEnclaveVerifier.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ struct VerifierJournal {
6161
uint64 timestamp;
6262
// Array of certificate hashes in the chain (root to leaf)
6363
bytes32[] certs;
64+
// Certificate notAfter timestamps in seconds (one per cert, matching certs[] order)
65+
uint64[] certExpiries;
6466
// User-defined data embedded in the attestation
6567
bytes userData;
6668
// Cryptographic nonce used for replay protection

scripts/multiproof/DeployRiscZeroStack.s.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,17 @@ contract DeployRiscZeroStack is Script {
125125
verifierId: nitroVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter
126126
});
127127

128-
// Start with an empty trusted certs array; certs will be auto-cached on first valid proof.
128+
// Start with empty trusted certs and expiries arrays; certs will be auto-cached on first valid proof.
129129
bytes32[] memory trustedCerts = new bytes32[](0);
130+
uint64[] memory trustedCertExpiries = new uint64[](0);
130131

131132
// Use owner as placeholder proofSubmitter; must be updated to TEEProverRegistry
132133
// address after deployment via setProofSubmitter().
133134
NitroEnclaveVerifier nev = new NitroEnclaveVerifier(
134135
owner,
135136
NITRO_MAX_TIME_DIFF,
136137
trustedCerts,
138+
trustedCertExpiries,
137139
nitroRootCert,
138140
owner,
139141
ZkCoProcessorType.RiscZero,

snapshots/abi/NitroEnclaveVerifier.json

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"name": "initializeTrustedCerts",
1717
"type": "bytes32[]"
1818
},
19+
{
20+
"internalType": "uint64[]",
21+
"name": "initializeTrustedCertExpiries",
22+
"type": "uint64[]"
23+
},
1924
{
2025
"internalType": "bytes32",
2126
"name": "initialRootCert",
@@ -127,6 +132,11 @@
127132
"name": "certs",
128133
"type": "bytes32[]"
129134
},
135+
{
136+
"internalType": "uint64[]",
137+
"name": "certExpiries",
138+
"type": "uint64[]"
139+
},
130140
{
131141
"internalType": "bytes",
132142
"name": "userData",
@@ -522,9 +532,9 @@
522532
"name": "trustedIntermediateCerts",
523533
"outputs": [
524534
{
525-
"internalType": "bool",
535+
"internalType": "uint64",
526536
"name": "",
527-
"type": "bool"
537+
"type": "uint64"
528538
}
529539
],
530540
"stateMutability": "view",
@@ -613,6 +623,11 @@
613623
"name": "certs",
614624
"type": "bytes32[]"
615625
},
626+
{
627+
"internalType": "uint64[]",
628+
"name": "certExpiries",
629+
"type": "uint64[]"
630+
},
616631
{
617632
"internalType": "bytes",
618633
"name": "userData",
@@ -1000,6 +1015,22 @@
10001015
"name": "CallerNotProofSubmitter",
10011016
"type": "error"
10021017
},
1018+
{
1019+
"inputs": [
1020+
{
1021+
"internalType": "uint256",
1022+
"name": "certsLen",
1023+
"type": "uint256"
1024+
},
1025+
{
1026+
"internalType": "uint256",
1027+
"name": "expiriesLen",
1028+
"type": "uint256"
1029+
}
1030+
],
1031+
"name": "CertExpiriesLengthMismatch",
1032+
"type": "error"
1033+
},
10031034
{
10041035
"inputs": [
10051036
{

snapshots/semver-lock.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,11 @@
236236
"sourceCodeHash": "0x032f11e23c188b939ba6cbdc446271b0caad2f4da00535b164a3489291162762"
237237
},
238238
"src/multiproof/tee/NitroEnclaveVerifier.sol:NitroEnclaveVerifier": {
239-
"initCodeHash": "0x8cae758a4b1005bf2904e376fff49c2fb44e8d9d7bef1a33e18a85ee23d540a7",
240-
"sourceCodeHash": "0x78d6c704d56ae8de255be71f5cc94e73e0bea1abd5b7e6dfcb4132174f89202b"
239+
"initCodeHash": "0xac14e4b9130c476ba4ab77ecf70b36153921a9fe6ef3ab96c825aba8d9d18473",
240+
"sourceCodeHash": "0x03614e89919c06e56e91b217702345edf74755c12c082475c4c742f45c201415"
241241
},
242242
"src/multiproof/tee/TEEProverRegistry.sol:TEEProverRegistry": {
243-
"initCodeHash": "0x0ed3443601cd3b08c016dbe4a743fb6f1c1800d211e2edd739e588ed455783b1",
243+
"initCodeHash": "0xfaec22f48eeef0f4ee0575b0e91028a1330fbf6faf52c2fce0f68b500a023970",
244244
"sourceCodeHash": "0xf1ec1f02f540da659a204b26acf986fdce7d7d63bba67a87923f52453fb92ccb"
245245
},
246246
"src/multiproof/tee/TEEVerifier.sol:TEEVerifier": {

snapshots/storageLayout/NitroEnclaveVerifier.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"label": "trustedIntermediateCerts",
1919
"offset": 0,
2020
"slot": "2",
21-
"type": "mapping(bytes32 => bool)"
21+
"type": "mapping(bytes32 => uint64)"
2222
},
2323
{
2424
"bytes": "8",

src/multiproof/tee/NitroEnclaveVerifier.sol

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
5252
/// @dev Configuration mapping for each supported ZK coprocessor type
5353
mapping(ZkCoProcessorType => ZkCoProcessorConfig) public zkConfig;
5454

55-
/// @dev Mapping of trusted intermediate certificate hashes (excludes root certificate)
56-
mapping(bytes32 => bool) public trustedIntermediateCerts;
55+
/// @dev Mapping of trusted intermediate certificate hashes to their notAfter timestamps in seconds (0 = not cached)
56+
mapping(bytes32 => uint64) public trustedIntermediateCerts;
5757

5858
/// @dev Maximum allowed time difference in seconds for attestation timestamp validation
5959
uint64 public maxTimeDiff;
@@ -143,11 +143,15 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
143143
/// @dev Event emitted when the maximum time difference is updated
144144
event MaxTimeDiffUpdated(uint64 newMaxTimeDiff);
145145

146+
/// @dev Thrown when initializeTrustedCerts and initializeTrustedCertExpiries have different lengths
147+
error CertExpiriesLengthMismatch(uint256 certsLen, uint256 expiriesLen);
148+
146149
/**
147150
* @dev Initializes the contract with owner, time tolerance and initial trusted certificates
148151
* @param owner Address to be set as the contract owner
149152
* @param initialMaxTimeDiff Maximum time difference in seconds for timestamp validation
150153
* @param initializeTrustedCerts Array of initial trusted intermediate certificate hashes
154+
* @param initializeTrustedCertExpiries Array of notAfter timestamps (seconds) for each initial cert
151155
* @param initialRootCert Hash of the AWS Nitro Enclave root certificate
152156
* @param initialProofSubmitter Address that is authorized to submit proofs
153157
* @param zkCoProcessor Type of ZK coprocessor to configure (RiscZero or Succinct)
@@ -158,16 +162,20 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
158162
address owner,
159163
uint64 initialMaxTimeDiff,
160164
bytes32[] memory initializeTrustedCerts,
165+
uint64[] memory initializeTrustedCertExpiries,
161166
bytes32 initialRootCert,
162167
address initialProofSubmitter,
163168
ZkCoProcessorType zkCoProcessor,
164169
ZkCoProcessorConfig memory config,
165170
bytes32 verifierProofId
166171
) {
167172
if (initialMaxTimeDiff == 0) revert ZeroMaxTimeDiff();
173+
if (initializeTrustedCerts.length != initializeTrustedCertExpiries.length) {
174+
revert CertExpiriesLengthMismatch(initializeTrustedCerts.length, initializeTrustedCertExpiries.length);
175+
}
168176
maxTimeDiff = initialMaxTimeDiff;
169177
for (uint256 i = 0; i < initializeTrustedCerts.length; i++) {
170-
trustedIntermediateCerts[initializeTrustedCerts[i]] = true;
178+
trustedIntermediateCerts[initializeTrustedCerts[i]] = initializeTrustedCertExpiries[i];
171179
}
172180
_initializeOwner(owner);
173181
_setRootCert(initialRootCert);
@@ -239,7 +247,8 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
239247
revert RootCertMismatch(rootCertHash, certs[0]);
240248
}
241249
for (uint256 j = 1; j < certs.length; j++) {
242-
if (!trustedIntermediateCerts[certs[j]]) {
250+
uint64 expiry = trustedIntermediateCerts[certs[j]];
251+
if (block.timestamp > expiry) {
243252
break;
244253
}
245254
trustedCertPrefixLen += 1;
@@ -319,7 +328,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
319328
* without affecting the root certificate or other trusted certificates.
320329
*/
321330
function revokeCert(bytes32 certHash) external onlyOwner {
322-
if (!trustedIntermediateCerts[certHash]) {
331+
if (trustedIntermediateCerts[certHash] == 0) {
323332
revert CertificateNotFound(certHash);
324333
}
325334
delete trustedIntermediateCerts[certHash];
@@ -536,7 +545,7 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
536545
function _cacheNewCert(VerifierJournal memory journal) internal {
537546
for (uint256 i = journal.trustedCertsPrefixLen; i < journal.certs.length; i++) {
538547
bytes32 certHash = journal.certs[i];
539-
trustedIntermediateCerts[certHash] = true;
548+
trustedIntermediateCerts[certHash] = journal.certExpiries[i];
540549
}
541550
}
542551

@@ -574,7 +583,8 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
574583
}
575584
continue;
576585
}
577-
if (!trustedIntermediateCerts[certHash]) {
586+
uint64 expiry = trustedIntermediateCerts[certHash];
587+
if (block.timestamp > expiry) {
578588
journal.result = VerificationResult.IntermediateCertsNotTrusted;
579589
return journal;
580590
}
@@ -652,8 +662,8 @@ contract NitroEnclaveVerifier is Ownable, INitroEnclaveVerifier, ISemver {
652662
}
653663

654664
/// @notice Semantic version.
655-
/// @custom:semver 0.1.0
665+
/// @custom:semver 0.2.0
656666
function version() public pure virtual returns (string memory) {
657-
return "0.1.0";
667+
return "0.2.0";
658668
}
659669
}

0 commit comments

Comments
 (0)