From b4ba63fc6684e5f015b8b59b4b86a64200665238 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 12 Feb 2026 11:00:30 +0000 Subject: [PATCH 1/2] feat: split attestation hash caching to comply with EIP-7825 per-tx gas limit --- src/NitroValidator.sol | 34 +++++++++++++++- test/NitroValidator.t.sol | 85 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/NitroValidator.sol b/src/NitroValidator.sol index 65ac8a0..6d9b127 100644 --- a/src/NitroValidator.sol +++ b/src/NitroValidator.sol @@ -40,12 +40,32 @@ contract NitroValidator { CborElement nonce; } + // Cached SHA-384 hashes for attestation TBS data, keyed by keccak256(attestationTbs). + // This allows splitting the expensive SHA-384 + ECDSA384.verify operations across + // two transactions to stay within the EIP-7825 per-transaction gas limit (2^24 gas). + mapping(bytes32 => bytes) public sha384Cache; + + event AttestationHashCached(bytes32 indexed attestationTbsHash); + ICertManager public immutable certManager; constructor(ICertManager _certManager) { certManager = _certManager; } + /// @notice Pre-computes and caches the SHA-384 hash of an attestation TBS. + /// @dev Call this in a separate transaction before validateAttestation() to split the + /// gas cost across two transactions. This is necessary on networks with per-transaction + /// gas limits (e.g. Ethereum L1 post-EIP-7825) where computing SHA-384 (~8.4M gas) + /// and ECDSA384.verify (~7.8M gas) together exceeds the limit. + /// @param attestationTbs The attestation to-be-signed data. + function cacheAttestationHash(bytes memory attestationTbs) external { + bytes32 key = keccak256(attestationTbs); + require(sha384Cache[key].length == 0, "hash already cached"); + sha384Cache[key] = Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length); + emit AttestationHashCached(key); + } + function decodeAttestationTbs(bytes memory attestation) external pure @@ -99,7 +119,7 @@ contract NitroValidator { } ICertManager.VerifiedCert memory parent = verifyCertBundle(cert, cabundle); - bytes memory hash = Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length); + bytes memory hash = _getOrComputeHash(attestationTbs); _verifySignature(parent.pubKey, hash, signature); return ptrs; @@ -202,6 +222,18 @@ contract NitroValidator { return ptrs; } + /// @dev Returns the cached SHA-384 hash if available, otherwise computes it inline. + /// Deletes the cache entry after use to reclaim storage and prevent stale entries. + function _getOrComputeHash(bytes memory attestationTbs) internal returns (bytes memory) { + bytes32 key = keccak256(attestationTbs); + bytes memory cached = sha384Cache[key]; + if (cached.length != 0) { + delete sha384Cache[key]; + return cached; + } + return Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length); + } + function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig"); } diff --git a/test/NitroValidator.t.sol b/test/NitroValidator.t.sol index 25a0629..6e5b91d 100644 --- a/test/NitroValidator.t.sol +++ b/test/NitroValidator.t.sol @@ -4,13 +4,18 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {NitroValidator} from "../src/NitroValidator.sol"; import {CertManager} from "../src/CertManager.sol"; +import {ICertManager} from "../src/ICertManager.sol"; contract NitroValidatorTest is Test { NitroValidator public validator; + CertManager public certManager; + + // EIP-7825 per-transaction gas limit + uint256 constant EIP_7825_GAS_LIMIT = 2 ** 24; // 16,777,216 function setUp() public { vm.warp(1732990000); - CertManager certManager = new CertManager(); + certManager = new CertManager(); validator = new NitroValidator(certManager); } @@ -28,4 +33,82 @@ contract NitroValidatorTest is Test { (bytes memory attestationTbs, bytes memory signature) = validator.decodeAttestationTbs(attestation); validator.validateAttestation(attestationTbs, signature); } + + // --- Split-flow tests (EIP-7825 compatible) --- + + function _getAttestationData() internal pure returns (bytes memory attestationTbs, bytes memory signature) { + attestationTbs = + hex"846a5369676e61747572653144a101382240591144a9696d6f64756c655f69647827692d30646533386232623638353363633965382d656e633031393336383565376665653764383566646967657374665348413338346974696d657374616d701b000001937de1c5436470637273b0005830ec74bfbe7f7445a6c7610e152935e028276f638042b74797b119648e13f7a3675796b721034c320f140ea001b41aeae2015830fa2593b59f3e4fc7daba5cbdddfd3449d67cd02d43bb1128885e8f38b914d081dccdb68fff6d5b7a76bcb866a18a74a302583056ba201a72e36cd051e95e5c4724c899039b711770f4d9d4fe7a1de007119a10b364badcd35e90f728a5bdc9109057230358303c9cadd84f0d027d6a5370c3de4af9179824fd6f3f02ebab723ee4439c75d8f5183e1c55f523415d44e9e6580b06655204583098bdf1bde262272618ccd73279e8ee00dd2c36974bd253de55413a25ceb2cd7221421207c2c09dde609f87481b6f6c940558300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000658300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000758300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000858300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000958300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b63657274696669636174655902803082027c30820201a00302010202100193685e7fee7d8500000000674b3bd8300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30646533386232623638353363633965382e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313133303136323234355a170d3234313133303139323234385a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30646533386232623638353363633965382d656e63303139333638356537666565376438352e75732d656173742d312e6177733076301006072a8648ce3d020106052b810400220362000461d930c61be969237398264901d6a37282cfd42c0694d012d9143cc86a339d567913dae552bad2f10d47c50d4e670247f0344983cbdc2d2e0045d4ccbdff59ef7a26ebf1be83a81e24a651c92008fe9f465757792a0877fba02c8b5e1eb2ed90a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100e48f39a39b444a6e5ea7a38b808198a2318dd531ed62faf4a9223f71f27dff4a5e495e32dd10f250bbaf1f892a4d328f023100d09fc8e48e233b9e972eecb94798865664dbeb0d75b29041f482777a4b7cae133483dcc9d35509c4967be51db37a745468636162756e646c65845902153082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff65902c2308202be30820244a003020102021056bfc987fd05ac99c475061b1a65eedc300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132383036303734355a170d3234313231383037303734355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d636264383238303866646138623434642e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b81040022036200040713751f4391a24bf27d688c9fdde4b7eec0c4922af63f242186269602eca12354e79356170287baa07dd84fa89834726891f9b4b27032b3e86000d32471a79fbf1a30c1982ad4ed069ad96a7e11d9ae2b5cd6a93ad613ee559ed7f6385a9a89a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e04160414bfbd54a168f57f7391b66ca60a2836f30acfb9a1300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030368003065023100c05dfd13378b1eecd926b0c3ba8da01eec89ec5502ae7ca73cb958557ca323057962fff2681993a0ab223b6eacf11033023035664252d7f9e2c89c988cc4164d390f898a5e8ac2e99dc58595aa4c624e93face7964026a99b4bcca7088b51250ccc459031a308203163082029ba003020102021100cb286a4a4a09207f8b0c14950dcd6861300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d636264383238303866646138623434642e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313133303033313435345a170d3234313230363031313435345a308189313c303a06035504030c33343762313739376131663031386266302e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b810400220362000423959f700ef87dcbdba686449d944f2a89ad22aa03d73cf93d28853f2fb6a80b0cc714d3090e34cda8234eef8f804e46c0dcb216062afba3e2b36a693660d9965e2370308b8e1ffad8542ddbe3e733077481b0cbc747d8c7beb7612820d4fe95a381ea3081e730120603551d130101ff040830060101ff020101301f0603551d23041830168014bfbd54a168f57f7391b66ca60a2836f30acfb9a1301d0603551d0e04160414bbf52a3a42fdc4f301f72536b90e65aaa1b70a99300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f30366434386638652d326330382d343738312d613634352d6231646534303261656662382e63726c300a06082a8648ce3d0403030369003066023100fa31509230632a002939201eb5686b52d79f0276db5c2b954bed324caa5c3271a60d25e2e05a5e6700e488a074af4ecd02310084770462c2ef86dcdb11fa8a31dcf770866cbd28822b682a112b98c09a30e35e94affd3482bf8b01b59a0a7775b4af185902c3308202bf30820245a003020102021500c8925d382506d820d93d2c704a7523c4ba2ddfaa300a06082a8648ce3d040303308189313c303a06035504030c33343762313739376131663031386266302e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313133303132343133315a170d3234313230313132343133315a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30646533386232623638353363633965382e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004466754b5718024df3564bcd722361e7c65a4922eda7b1f826758e30afac40b04a281062897d085311fd509b70a6bbc5f8280f86ae2ff255ad147146fc97b7afb16064f0712d335c1d473b716be320be625e91c5870973084b3a0005bc020c7b2a366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e04160414345c86a9ec55bc30cafd923d6b73111d9c57abc0301f0603551d23041830168014bbf52a3a42fdc4f301f72536b90e65aaa1b70a99300a06082a8648ce3d0403030368003065023100aba82c02f40acb9846012bf070578217eeb2ebbfd16414948438cf67eeab6f64cdc5a152998766c88b2cdebd5a97ebd402307421611ed511567bc8e6a0a2805b981ef38dc3bd6a6c661522802b5c5d658cc4fcc9b5e8df148b161d366926896736836a7075626c69635f6b657958410433a4701fa871b188983d570e2c2d8cf98fd66eb19ba8ca7617bc8e20e152a5d7f0205eae76e608ce855077e4565be69db4471ef72857253742f9602c11ff04e569757365725f64617461f6656e6f6e6365f6"; + signature = + hex"874e67088943e85654beb78443c747def2c3736bf93e2b52d033b3e936a04ead91f7b5a1229a1615f237f138f64399418b8046b6e40cd93e750b58f5e1aded45ebf3f103b9ea19a9b874142b576638dad2da142254ae913664649be22e0b83f9"; + } + + /// @notice Verifies the full split-flow: cache hash in one TX, validate in a second TX + function test_ValidateAttestationWithCachedHash() public { + (bytes memory attestationTbs, bytes memory signature) = _getAttestationData(); + + // TX N-1: cache the attestation hash + validator.cacheAttestationHash(attestationTbs); + + // TX N: validate uses the cached hash (skips SHA-384) + validator.validateAttestation(attestationTbs, signature); + } + + /// @notice Verifies cacheAttestationHash gas fits within the EIP-7825 limit + function test_CacheAttestationHashGasFitsEIP7825() public { + (bytes memory attestationTbs,) = _getAttestationData(); + + uint256 gasBefore = gasleft(); + validator.cacheAttestationHash(attestationTbs); + uint256 gasUsed = gasBefore - gasleft(); + + console.log("cacheAttestationHash gas: %d", gasUsed); + assertLt(gasUsed, EIP_7825_GAS_LIMIT, "cacheAttestationHash exceeds EIP-7825 gas limit"); + } + + /// @notice Verifies validateAttestation with pre-cached certs + hash fits within EIP-7825. + /// A first validateAttestation call warms CertManager's cert cache, then we + /// cache the hash and measure the second call. + function test_ValidateWithCachedHashGasFitsEIP7825() public { + (bytes memory attestationTbs, bytes memory signature) = _getAttestationData(); + + // First call: warms all certs in CertManager's verified mapping + validator.validateAttestation(attestationTbs, signature); + + // Cache the hash (simulating a prior TX) + validator.cacheAttestationHash(attestationTbs); + + // Measure only the final validateAttestation call (certs + hash pre-cached) + uint256 gasBefore = gasleft(); + validator.validateAttestation(attestationTbs, signature); + uint256 gasUsed = gasBefore - gasleft(); + + console.log("validateAttestation (certs + hash pre-cached) gas: %d", gasUsed); + assertLt(gasUsed, EIP_7825_GAS_LIMIT, "validateAttestation with cached hash exceeds EIP-7825 gas limit"); + } + + /// @notice Verifies the cache entry is deleted after use + function test_CacheCleanedUpAfterUse() public { + (bytes memory attestationTbs, bytes memory signature) = _getAttestationData(); + bytes32 key = keccak256(attestationTbs); + + // Cache exists after caching + validator.cacheAttestationHash(attestationTbs); + bytes memory cached = validator.sha384Cache(key); + assertEq(cached.length, 48, "cached hash should be 48 bytes"); + + // Cache is deleted after validateAttestation consumes it + validator.validateAttestation(attestationTbs, signature); + cached = validator.sha384Cache(key); + assertEq(cached.length, 0, "cache should be cleared after use"); + } + + /// @notice Verifies double-caching is prevented + function test_CacheAttestationHashRevertIfAlreadyCached() public { + (bytes memory attestationTbs,) = _getAttestationData(); + + validator.cacheAttestationHash(attestationTbs); + vm.expectRevert("hash already cached"); + validator.cacheAttestationHash(attestationTbs); + } } From 52aa85807f3a778daf4aca7d46c554443c080506 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 12 Feb 2026 12:45:21 +0000 Subject: [PATCH 2/2] feat: extend SHA-384 caching to CertManager for EIP-7825 cert verification splitting --- src/CertManager.sol | 37 +++++++++++++++++++++++++++++++++++-- test/CertManager.t.sol | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/CertManager.sol b/src/CertManager.sol index ee7b2ab..cf58756 100644 --- a/src/CertManager.sol +++ b/src/CertManager.sol @@ -19,6 +19,12 @@ contract CertManager is ICertManager { using LibBytes for bytes; event CertVerified(bytes32 indexed certHash); + event CertHashCached(bytes32 indexed certHash); + + // Cached SHA-384 hashes for certificate TBS data, keyed by keccak256(cert). + // Allows splitting the expensive SHA-384 + ECDSA384.verify across two transactions + // to stay within the EIP-7825 per-transaction gas limit (2^24 gas). + mapping(bytes32 => bytes) public certSha384Cache; // root CA certificate constants (don't store it to reduce contract size) bytes32 public constant ROOT_CA_CERT_HASH = 0x311d96fcd5c5e0ccf72ef548e2ea7d4c0cd53ad7c4cc49e67471aed41d61f185; @@ -61,6 +67,21 @@ contract CertManager is ICertManager { ); } + /// @notice Pre-computes and caches the SHA-384 hash of a certificate's TBS (to-be-signed) data. + /// @dev Call this in a separate transaction before verifyCACert/verifyClientCert to split + /// the gas cost. Necessary on networks with per-transaction gas limits (e.g. EIP-7825). + /// @param cert The DER-encoded certificate. + function cacheCertHash(bytes memory cert) external { + bytes32 key = keccak256(cert); + require(certSha384Cache[key].length == 0, "cert hash already cached"); + + Asn1Ptr root = cert.root(); + Asn1Ptr tbsCertPtr = cert.firstChildOf(root); + certSha384Cache[key] = Sha2Ext.sha384(cert, tbsCertPtr.header(), tbsCertPtr.totalLength()); + + emit CertHashCached(key); + } + function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32) { bytes32 certHash = keccak256(cert); _verifyCert(cert, certHash, true, _loadVerified(parentCertHash)); @@ -271,11 +292,11 @@ contract CertManager is ICertManager { } } - function _verifyCertSignature(bytes memory certificate, Asn1Ptr ptr, bytes memory pubKey) internal view { + function _verifyCertSignature(bytes memory certificate, Asn1Ptr ptr, bytes memory pubKey) internal { Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(ptr); require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo"); - bytes memory hash = Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength()); + bytes memory hash = _getOrComputeCertHash(certificate, ptr); Asn1Ptr sigPtr = certificate.nextSiblingOf(sigAlgoPtr); Asn1Ptr sigBPtr = certificate.bitstring(sigPtr); @@ -289,6 +310,18 @@ contract CertManager is ICertManager { _verifySignature(pubKey, hash, sigPacked); } + /// @dev Returns the cached SHA-384 hash if available, otherwise computes it inline. + /// Deletes the cache entry after use to reclaim storage and prevent stale entries. + function _getOrComputeCertHash(bytes memory certificate, Asn1Ptr ptr) internal returns (bytes memory) { + bytes32 key = keccak256(certificate); + bytes memory cached = certSha384Cache[key]; + if (cached.length != 0) { + delete certSha384Cache[key]; + return cached; + } + return Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength()); + } + function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig"); } diff --git a/test/CertManager.t.sol b/test/CertManager.t.sol index 2416cfa..1193bee 100644 --- a/test/CertManager.t.sol +++ b/test/CertManager.t.sol @@ -38,4 +38,42 @@ contract CertManagerTest is Test { hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; certManager.verifyCACert(cert, keccak256(parent)); } + + // --- Split-flow tests (EIP-7825 compatible) --- + + function test_VerifyCACertWithCachedHash() public { + bytes memory parent = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + bytes memory cert = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + + // TX 1: cache the SHA-384 hash + certManager.cacheCertHash(cert); + + // TX 2: verify using cached hash (skips SHA-384, only does ECDSA384) + certManager.verifyCACert(cert, keccak256(parent)); + } + + function test_CertCacheCleanedUpAfterUse() public { + bytes memory parent = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + bytes memory cert = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + bytes32 key = keccak256(cert); + + certManager.cacheCertHash(cert); + assertEq(certManager.certSha384Cache(key).length, 48, "cached hash should be 48 bytes"); + + certManager.verifyCACert(cert, keccak256(parent)); + assertEq(certManager.certSha384Cache(key).length, 0, "cache should be cleared after use"); + } + + function test_CacheCertHashRevertIfAlreadyCached() public { + bytes memory cert = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + + certManager.cacheCertHash(cert); + vm.expectRevert("cert hash already cached"); + certManager.cacheCertHash(cert); + } }