@@ -109,6 +109,8 @@ library EpochProofLib {
109109
110110 Epoch endEpoch = assertAcceptable (_args.start, _args.end);
111111
112+ // Rehash the supplied headers against storage once, here: the public-input assembly below reads the fee
113+ // recipient/value out of them and relies on this call having run.
112114 verifyHeaders (_args.start, _args.end, _args.headers);
113115
114116 // Verify attestations for the last checkpoint in the epoch
@@ -150,6 +152,11 @@ library EpochProofLib {
150152 * own public inputs used for generating the proof vs the ones assembled
151153 * by this contract when verifying it.
152154 *
155+ * @dev The fee recipient/value public inputs are sourced from the supplied headers, so this entry point rehashes
156+ * them against storage before assembling: an off-chain caller must not walk away with public inputs built from
157+ * unverified fee fields and only discover the mismatch when the on-chain proof reverts. The submit path verifies
158+ * the headers up front and assembles via computeEpochProofPublicInputs to avoid rehashing them twice.
159+ *
153160 * @param _start - The start of the epoch (inclusive)
154161 * @param _end - The end of the epoch (inclusive)
155162 * @param _args - Array of public inputs to the proof (previousArchive, endArchive, endTimestamp, outHash, proverId)
@@ -163,6 +170,107 @@ library EpochProofLib {
163170 ProposedHeader[] calldata _headers ,
164171 bytes calldata _blobPublicInputs
165172 ) internal view returns (bytes32 [] memory ) {
173+ verifyHeaders (_start, _end, _headers);
174+ return computeEpochProofPublicInputs (_start, _end, _args, _headers, _blobPublicInputs);
175+ }
176+
177+ /**
178+ * @notice Verifies committee attestations for the last checkpoint in the epoch before accepting the epoch proof
179+ *
180+ * @dev This verification ensures that the committee has properly validated the final state of the epoch
181+ * before the proof can be accepted. The function validates that:
182+ * 1. The provided attestations match the stored attestation hash for the checkpoint
183+ * 2. The attestations have valid signatures from committee members
184+ * 3. The attestations meet the required threshold (2/3+ of committee)
185+ *
186+ * For escape hatch epochs, attestation verification is skipped since there is no committee
187+ * involvement - only the designated escape hatch proposer can propose blocks.
188+ *
189+ * @dev Errors Thrown:
190+ * - Rollup__InvalidAttestations: Provided attestations don't match stored hash or fail validation
191+ *
192+ * @param _endCheckpointNumber The last checkpoint number in the epoch to verify attestations for
193+ * @param _attestations The committee attestations containing signatures and validator information
194+ */
195+ function verifyLastCheckpointAttestationsAndOutHash (
196+ uint256 _endCheckpointNumber ,
197+ CommitteeAttestations memory _attestations ,
198+ bytes32 _outHash
199+ ) private {
200+ // Get the stored attestation hash and payload digest for the last checkpoint
201+ CompressedTempCheckpointLog storage checkpointLog = STFLib.getStorageTempCheckpointLog (_endCheckpointNumber);
202+
203+ // Verify that the out hash matches the stored value
204+ // The stored out hash is part of the payloadDigest that was attested to.
205+ require (checkpointLog.outHash == _outHash, Errors.Rollup__InvalidOutHash (checkpointLog.outHash, _outHash));
206+
207+ // Verify that the provided attestations match the stored hash
208+ bytes32 providedAttestationsHash = keccak256 (abi.encode (_attestations));
209+ require (providedAttestationsHash == checkpointLog.attestationsHash, Errors.Rollup__InvalidAttestations ());
210+
211+ // Get the epoch for the last checkpoint
212+ Epoch epoch = STFLib.getEpochForCheckpoint (_endCheckpointNumber);
213+
214+ // Check if this is an escape hatch epoch - skip attestation verification if so
215+ // since escape hatch blocks are proposed without committee attestations.
216+ // Uses epoch-stable lookup so proof verification uses the escape hatch that was
217+ // active when the epoch started, not whatever is currently configured.
218+ {
219+ IEscapeHatch escapeHatch = ValidatorSelectionLib.getEscapeHatchForEpoch (epoch);
220+ if (address (escapeHatch) != address (0 )) {
221+ (bool isOpen ,) = escapeHatch.isHatchOpen (epoch);
222+ if (isOpen) {
223+ // Skip attestation verification for escape hatch epochs
224+ return ;
225+ }
226+ }
227+ }
228+
229+ ValidatorSelectionLib.verifyAttestations (epoch, _attestations, checkpointLog.payloadDigest);
230+ }
231+
232+ /**
233+ * @notice Rehashes each provided checkpoint header and requires it to match the stored header hash
234+ *
235+ * @param _start The first checkpoint number in the epoch (inclusive)
236+ * @param _end The last checkpoint number in the epoch (inclusive)
237+ * @param _headers The proposed headers for each checkpoint in [_start, _end]
238+ */
239+ function verifyHeaders (uint256 _start , uint256 _end , ProposedHeader[] calldata _headers ) private view {
240+ uint256 numCheckpoints = _end - _start + 1 ;
241+ require (
242+ _headers.length == numCheckpoints, Errors.Rollup__InvalidCheckpointHeaderCount (numCheckpoints, _headers.length )
243+ );
244+
245+ for (uint256 i = 0 ; i < numCheckpoints; i++ ) {
246+ bytes32 expectedHeaderHash = STFLib.getHeaderHash (_start + i);
247+ bytes32 providedHeaderHash = ProposedHeaderLib.hash (_headers[i]);
248+ require (
249+ providedHeaderHash == expectedHeaderHash,
250+ Errors.Rollup__InvalidCheckpointHeader (expectedHeaderHash, providedHeaderHash)
251+ );
252+ }
253+ }
254+
255+ /**
256+ * @notice Assembles the root rollup public inputs, taking the supplied checkpoint headers as already verified
257+ *
258+ * @dev Callers must have rehashed `_headers` against the stored header hashes beforehand, since the fee
259+ * recipient/value public inputs are read straight out of them.
260+ *
261+ * @param _start - The start of the epoch (inclusive)
262+ * @param _end - The end of the epoch (inclusive)
263+ * @param _args - Array of public inputs to the proof (previousArchive, endArchive, endTimestamp, outHash, proverId)
264+ * @param _headers - The proposed checkpoint headers supplying the fee recipient and value for each checkpoint
265+ * @param _blobPublicInputs- The blob public inputs for the proof
266+ */
267+ function computeEpochProofPublicInputs (
268+ uint256 _start ,
269+ uint256 _end ,
270+ PublicInputArgs calldata _args ,
271+ ProposedHeader[] calldata _headers ,
272+ bytes calldata _blobPublicInputs
273+ ) private view returns (bytes32 [] memory ) {
166274 RollupStore storage rollupStore = STFLib.getStorage ();
167275
168276 {
@@ -183,11 +291,6 @@ library EpochProofLib {
183291 }
184292 }
185293
186- // The fee recipient/value below are sourced from the supplied headers, so the header hashes must be validated here
187- // as well as on the submit path - otherwise an off-chain caller of this getter would assemble public inputs from
188- // unverified fee fields and only discover the mismatch when the on-chain proof reverts.
189- verifyHeaders (_start, _end, _headers);
190-
191294 bytes32 [] memory publicInputs = new bytes32 [](Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH);
192295
193296 // Structure of the root rollup public inputs we need to reassemble:
@@ -289,84 +392,6 @@ library EpochProofLib {
289392 return publicInputs;
290393 }
291394
292- /**
293- * @notice Verifies committee attestations for the last checkpoint in the epoch before accepting the epoch proof
294- *
295- * @dev This verification ensures that the committee has properly validated the final state of the epoch
296- * before the proof can be accepted. The function validates that:
297- * 1. The provided attestations match the stored attestation hash for the checkpoint
298- * 2. The attestations have valid signatures from committee members
299- * 3. The attestations meet the required threshold (2/3+ of committee)
300- *
301- * For escape hatch epochs, attestation verification is skipped since there is no committee
302- * involvement - only the designated escape hatch proposer can propose blocks.
303- *
304- * @dev Errors Thrown:
305- * - Rollup__InvalidAttestations: Provided attestations don't match stored hash or fail validation
306- *
307- * @param _endCheckpointNumber The last checkpoint number in the epoch to verify attestations for
308- * @param _attestations The committee attestations containing signatures and validator information
309- */
310- function verifyLastCheckpointAttestationsAndOutHash (
311- uint256 _endCheckpointNumber ,
312- CommitteeAttestations memory _attestations ,
313- bytes32 _outHash
314- ) private {
315- // Get the stored attestation hash and payload digest for the last checkpoint
316- CompressedTempCheckpointLog storage checkpointLog = STFLib.getStorageTempCheckpointLog (_endCheckpointNumber);
317-
318- // Verify that the out hash matches the stored value
319- // The stored out hash is part of the payloadDigest that was attested to.
320- require (checkpointLog.outHash == _outHash, Errors.Rollup__InvalidOutHash (checkpointLog.outHash, _outHash));
321-
322- // Verify that the provided attestations match the stored hash
323- bytes32 providedAttestationsHash = keccak256 (abi.encode (_attestations));
324- require (providedAttestationsHash == checkpointLog.attestationsHash, Errors.Rollup__InvalidAttestations ());
325-
326- // Get the epoch for the last checkpoint
327- Epoch epoch = STFLib.getEpochForCheckpoint (_endCheckpointNumber);
328-
329- // Check if this is an escape hatch epoch - skip attestation verification if so
330- // since escape hatch blocks are proposed without committee attestations.
331- // Uses epoch-stable lookup so proof verification uses the escape hatch that was
332- // active when the epoch started, not whatever is currently configured.
333- {
334- IEscapeHatch escapeHatch = ValidatorSelectionLib.getEscapeHatchForEpoch (epoch);
335- if (address (escapeHatch) != address (0 )) {
336- (bool isOpen ,) = escapeHatch.isHatchOpen (epoch);
337- if (isOpen) {
338- // Skip attestation verification for escape hatch epochs
339- return ;
340- }
341- }
342- }
343-
344- ValidatorSelectionLib.verifyAttestations (epoch, _attestations, checkpointLog.payloadDigest);
345- }
346-
347- /**
348- * @notice Rehashes each provided checkpoint header and requires it to match the stored header hash
349- *
350- * @param _start The first checkpoint number in the epoch (inclusive)
351- * @param _end The last checkpoint number in the epoch (inclusive)
352- * @param _headers The proposed headers for each checkpoint in [_start, _end]
353- */
354- function verifyHeaders (uint256 _start , uint256 _end , ProposedHeader[] calldata _headers ) private view {
355- uint256 numCheckpoints = _end - _start + 1 ;
356- require (
357- _headers.length == numCheckpoints, Errors.Rollup__InvalidCheckpointHeaderCount (numCheckpoints, _headers.length )
358- );
359-
360- for (uint256 i = 0 ; i < numCheckpoints; i++ ) {
361- bytes32 expectedHeaderHash = STFLib.getHeaderHash (_start + i);
362- bytes32 providedHeaderHash = ProposedHeaderLib.hash (_headers[i]);
363- require (
364- providedHeaderHash == expectedHeaderHash,
365- Errors.Rollup__InvalidCheckpointHeader (expectedHeaderHash, providedHeaderHash)
366- );
367- }
368- }
369-
370395 /**
371396 * @notice Validates that an epoch proof submission meets all acceptance criteria
372397 *
@@ -434,6 +459,9 @@ library EpochProofLib {
434459 * 2. Assembling the public inputs for the root rollup circuit
435460 * 3. Verifying the validity proof against the assembled public inputs using the configured verifier
436461 *
462+ * @dev Assumes the caller has already verified the supplied checkpoint headers against storage, so assembly skips
463+ * rehashing them.
464+ *
437465 * @dev Errors Thrown:
438466 * - Rollup__InvalidBlobProof: Batched blob proof verification failed
439467 * - Rollup__InvalidProof: validity proof verification failed
@@ -449,7 +477,7 @@ library EpochProofLib {
449477 BlobLib.validateBatchedBlob (_args.blobInputs);
450478
451479 bytes32 [] memory publicInputs =
452- getEpochProofPublicInputs (_args.start, _args.end, _args.args, _args.headers, _args.blobInputs);
480+ computeEpochProofPublicInputs (_args.start, _args.end, _args.args, _args.headers, _args.blobInputs);
453481
454482 require (rollupStore.config.epochProofVerifier.verify (_args.proof, publicInputs), Errors.Rollup__InvalidProof ());
455483
0 commit comments