Skip to content

Commit 52aa858

Browse files
committed
feat: extend SHA-384 caching to CertManager for EIP-7825 cert verification splitting
1 parent b4ba63f commit 52aa858

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

src/CertManager.sol

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ contract CertManager is ICertManager {
1919
using LibBytes for bytes;
2020

2121
event CertVerified(bytes32 indexed certHash);
22+
event CertHashCached(bytes32 indexed certHash);
23+
24+
// Cached SHA-384 hashes for certificate TBS data, keyed by keccak256(cert).
25+
// Allows splitting the expensive SHA-384 + ECDSA384.verify across two transactions
26+
// to stay within the EIP-7825 per-transaction gas limit (2^24 gas).
27+
mapping(bytes32 => bytes) public certSha384Cache;
2228

2329
// root CA certificate constants (don't store it to reduce contract size)
2430
bytes32 public constant ROOT_CA_CERT_HASH = 0x311d96fcd5c5e0ccf72ef548e2ea7d4c0cd53ad7c4cc49e67471aed41d61f185;
@@ -61,6 +67,21 @@ contract CertManager is ICertManager {
6167
);
6268
}
6369

70+
/// @notice Pre-computes and caches the SHA-384 hash of a certificate's TBS (to-be-signed) data.
71+
/// @dev Call this in a separate transaction before verifyCACert/verifyClientCert to split
72+
/// the gas cost. Necessary on networks with per-transaction gas limits (e.g. EIP-7825).
73+
/// @param cert The DER-encoded certificate.
74+
function cacheCertHash(bytes memory cert) external {
75+
bytes32 key = keccak256(cert);
76+
require(certSha384Cache[key].length == 0, "cert hash already cached");
77+
78+
Asn1Ptr root = cert.root();
79+
Asn1Ptr tbsCertPtr = cert.firstChildOf(root);
80+
certSha384Cache[key] = Sha2Ext.sha384(cert, tbsCertPtr.header(), tbsCertPtr.totalLength());
81+
82+
emit CertHashCached(key);
83+
}
84+
6485
function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32) {
6586
bytes32 certHash = keccak256(cert);
6687
_verifyCert(cert, certHash, true, _loadVerified(parentCertHash));
@@ -271,11 +292,11 @@ contract CertManager is ICertManager {
271292
}
272293
}
273294

274-
function _verifyCertSignature(bytes memory certificate, Asn1Ptr ptr, bytes memory pubKey) internal view {
295+
function _verifyCertSignature(bytes memory certificate, Asn1Ptr ptr, bytes memory pubKey) internal {
275296
Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(ptr);
276297
require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo");
277298

278-
bytes memory hash = Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength());
299+
bytes memory hash = _getOrComputeCertHash(certificate, ptr);
279300

280301
Asn1Ptr sigPtr = certificate.nextSiblingOf(sigAlgoPtr);
281302
Asn1Ptr sigBPtr = certificate.bitstring(sigPtr);
@@ -289,6 +310,18 @@ contract CertManager is ICertManager {
289310
_verifySignature(pubKey, hash, sigPacked);
290311
}
291312

313+
/// @dev Returns the cached SHA-384 hash if available, otherwise computes it inline.
314+
/// Deletes the cache entry after use to reclaim storage and prevent stale entries.
315+
function _getOrComputeCertHash(bytes memory certificate, Asn1Ptr ptr) internal returns (bytes memory) {
316+
bytes32 key = keccak256(certificate);
317+
bytes memory cached = certSha384Cache[key];
318+
if (cached.length != 0) {
319+
delete certSha384Cache[key];
320+
return cached;
321+
}
322+
return Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength());
323+
}
324+
292325
function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view {
293326
require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig");
294327
}

test/CertManager.t.sol

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,42 @@ contract CertManagerTest is Test {
3838
hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4";
3939
certManager.verifyCACert(cert, keccak256(parent));
4040
}
41+
42+
// --- Split-flow tests (EIP-7825 compatible) ---
43+
44+
function test_VerifyCACertWithCachedHash() public {
45+
bytes memory parent =
46+
hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6";
47+
bytes memory cert =
48+
hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4";
49+
50+
// TX 1: cache the SHA-384 hash
51+
certManager.cacheCertHash(cert);
52+
53+
// TX 2: verify using cached hash (skips SHA-384, only does ECDSA384)
54+
certManager.verifyCACert(cert, keccak256(parent));
55+
}
56+
57+
function test_CertCacheCleanedUpAfterUse() public {
58+
bytes memory parent =
59+
hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6";
60+
bytes memory cert =
61+
hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4";
62+
bytes32 key = keccak256(cert);
63+
64+
certManager.cacheCertHash(cert);
65+
assertEq(certManager.certSha384Cache(key).length, 48, "cached hash should be 48 bytes");
66+
67+
certManager.verifyCACert(cert, keccak256(parent));
68+
assertEq(certManager.certSha384Cache(key).length, 0, "cache should be cleared after use");
69+
}
70+
71+
function test_CacheCertHashRevertIfAlreadyCached() public {
72+
bytes memory cert =
73+
hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4";
74+
75+
certManager.cacheCertHash(cert);
76+
vm.expectRevert("cert hash already cached");
77+
certManager.cacheCertHash(cert);
78+
}
4179
}

0 commit comments

Comments
 (0)