Repository: OpenZeppelin/minimal-rollup
Pull request #112: [1/2] Simplify commitment store
Author: login: LeoPatOZ
Created: 2025-04-30T10:29:14Z | Merged: 2025-05-08T11:11:49Z

Currently this PR address these points:

- [x] Avoid duplicate checkpoints. The TaikoAnchor and CheckpointTracker now use the signal service for their checkpoint/l1blocks storage.
- [x] Remove commitment access control. Anyone can post commitments, which are saved under the source address. Access control can be achieved on the recipient end (they should only use checkpoints saved by a valid address). 
- [x]  Allow the CheckpointTracker genesis to start the current publication ID.



Diff:
diff --git a/justfile b/justfile
index 5c8ff27a..1108e302 100644
--- a/justfile
+++ b/justfile
@@ -10,11 +10,11 @@ stop-anvil:
     lsof -ti:8545 | xargs -r kill
     lsof -ti:8546 | xargs -r kill
 
-test +ARGS:
+test +ARGS='':
     # Run all unit tests
     forge test {{ARGS}} --no-match-path 'test/signal/**'
 
-test-int +ARGS:
+test-int +ARGS='':
     # Run all tests
     forge test {{ARGS}}
 
diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol
index 73b3701a..2f0c1aec 100644
--- a/src/protocol/CheckpointTracker.sol
+++ b/src/protocol/CheckpointTracker.sol
@@ -7,19 +7,14 @@ import {IPublicationFeed} from "./IPublicationFeed.sol";
 import {IVerifier} from "./IVerifier.sol";
 
 contract CheckpointTracker is ICheckpointTracker {
-    /// @notice The current proven checkpoint representing the latest verified state of the rollup
-    /// @dev Previous checkpoints are not stored here but are synchronized to the `SignalService`
-    /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies
-    /// the state of the rollup at a specific point in time
-    /// @dev We store the actual checkpoint(not the hash) to avoid race conditions when closing a period or evicting a
-    /// prover(https://github.com/OpenZeppelin/minimal-rollup/pull/77#discussion_r2002192018)
-
-    Checkpoint private _provenCheckpoint;
+    /// @dev The publication id of the current proven checkpoint representing
+    /// the latest verified state of the rollup
+    uint256 private _provenPublicationId;
 
     IPublicationFeed public immutable publicationFeed;
     IVerifier public immutable verifier;
     ICommitmentStore public immutable commitmentStore;
-    address public proverManager;
+    address public immutable proverManager;
 
     /// @param _genesis the checkpoint commitment describing the initial state of the rollup
     /// @param _publicationFeed the input data source that updates the state of this rollup
@@ -35,14 +30,14 @@ contract CheckpointTracker is ICheckpointTracker {
     ) {
         // set the genesis checkpoint commitment of the rollup - genesis is trusted to be correct
         require(_genesis != 0, "genesis checkpoint commitment cannot be 0");
-
         publicationFeed = IPublicationFeed(_publicationFeed);
+        uint256 latestPublicationId = publicationFeed.getNextPublicationId() - 1;
+
         verifier = IVerifier(_verifier);
         commitmentStore = ICommitmentStore(_commitmentStore);
         proverManager = _proverManager;
-        Checkpoint memory genesisCheckpoint = Checkpoint({publicationId: 0, commitment: _genesis});
-        _provenCheckpoint = genesisCheckpoint;
-        emit CheckpointUpdated(genesisCheckpoint);
+
+        _updateCheckpoint(latestPublicationId, _genesis);
     }
 
     /// @inheritdoc ICheckpointTracker
@@ -58,8 +53,9 @@ contract CheckpointTracker is ICheckpointTracker {
 
         require(end.commitment != 0, "Checkpoint commitment cannot be 0");
 
+        Checkpoint memory provenCheckpoint = getProvenCheckpoint();
         require(
-            start.publicationId == _provenCheckpoint.publicationId && start.commitment == _provenCheckpoint.commitment,
+            start.publicationId == provenCheckpoint.publicationId && start.commitment == provenCheckpoint.commitment,
             "Start checkpoint must be the latest proven checkpoint"
         );
 
@@ -73,14 +69,22 @@ contract CheckpointTracker is ICheckpointTracker {
             startPublicationHash, endPublicationHash, start.commitment, end.commitment, numPublications, proof
         );
 
-        _provenCheckpoint = end;
-        emit CheckpointUpdated(end);
+        _updateCheckpoint(end.publicationId, end.commitment);
+    }
 
-        // Stores the state of the other chain
-        commitmentStore.storeCommitment(end.publicationId, end.commitment);
+    /// @inheritdoc ICheckpointTracker
+    function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) {
+        provenCheckpoint.publicationId = _provenPublicationId;
+        provenCheckpoint.commitment = commitmentStore.commitmentAt(address(this), provenCheckpoint.publicationId);
     }
 
-    function getProvenCheckpoint() external view returns (Checkpoint memory) {
-        return _provenCheckpoint;
+    /// @dev Updates the proven checkpoint to a new publication ID and commitment
+    /// @dev Stores the commitment in the commitment store and emits an event
+    /// @param publicationId The ID of the publication to set as the latest proven checkpoint
+    /// @param commitment The checkpoint commitment representing the state at the given publication ID
+    function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal {
+        _provenPublicationId = publicationId;
+        commitmentStore.storeCommitment(publicationId, commitment);
+        emit CheckpointUpdated(publicationId, commitment);
     }
 }
diff --git a/src/protocol/CommitmentStore.sol b/src/protocol/CommitmentStore.sol
index c0222ef5..6c40528c 100644
--- a/src/protocol/CommitmentStore.sol
+++ b/src/protocol/CommitmentStore.sol
@@ -1,54 +1,20 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.28;
 
-import {ICheckpointTracker} from "./ICheckpointTracker.sol";
 import {ICommitmentStore} from "./ICommitmentStore.sol";
 
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
-
 /// @dev Base contract for storing commitments.
-abstract contract CommitmentStore is ICommitmentStore, Ownable {
-    using SafeCast for uint256;
-
-    address private _authorizedCommitter;
-
-    mapping(uint256 height => bytes32 commitment) private _commitments;
-
-    /// @param _rollupOperator The address of the rollup operator
-    constructor(address _rollupOperator) Ownable(_rollupOperator) {}
-
-    /// @dev Reverts if the caller is not the `authorizedCommitter`.
-    modifier onlyAuthorizedCommitter() {
-        _checkAuthorizedCommitter(msg.sender);
-        _;
-    }
-
-    /// @inheritdoc ICommitmentStore
-    function authorizedCommitter() public view virtual returns (address) {
-        return _authorizedCommitter;
-    }
+abstract contract CommitmentStore is ICommitmentStore {
+    mapping(address source => mapping(uint256 height => bytes32 commitment)) private _commitments;
 
     /// @inheritdoc ICommitmentStore
-    function setAuthorizedCommitter(address newAuthorizedCommitter) external virtual onlyOwner {
-        require(newAuthorizedCommitter != address(0), EmptyCommitter());
-        _authorizedCommitter = newAuthorizedCommitter;
-        emit AuthorizedCommitterUpdated(newAuthorizedCommitter);
+    function commitmentAt(address source, uint256 height) public view virtual returns (bytes32) {
+        return _commitments[source][height];
     }
 
     /// @inheritdoc ICommitmentStore
-    function commitmentAt(uint256 height) public view virtual returns (bytes32) {
-        return _commitments[height];
-    }
-
-    /// @inheritdoc ICommitmentStore
-    function storeCommitment(uint256 height, bytes32 commitment) external virtual onlyAuthorizedCommitter {
-        _commitments[height] = commitment;
-        emit CommitmentStored(height, commitment);
-    }
-
-    /// @dev Internal helper to validate the authorizedCommitter.
-    function _checkAuthorizedCommitter(address caller) internal view {
-        require(caller == _authorizedCommitter, UnauthorizedCommitter());
+    function storeCommitment(uint256 height, bytes32 commitment) external virtual {
+        _commitments[msg.sender][height] = commitment;
+        emit CommitmentStored(msg.sender, height, commitment);
     }
 }
diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol
index ba57634a..eb87882b 100644
--- a/src/protocol/ETHBridge.sol
+++ b/src/protocol/ETHBridge.sol
@@ -6,7 +6,7 @@ import {IETHBridge} from "./IETHBridge.sol";
 /// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs.
 ///
 /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed.
-abstract contract ETHBridge is IETHBridge {
+contract ETHBridge is IETHBridge {
     mapping(bytes32 id => bool claimed) private _claimed;
 
     /// Incremental nonce to generate unique deposit IDs.
@@ -36,10 +36,12 @@ abstract contract ETHBridge is IETHBridge {
     }
 
     /// @inheritdoc IETHBridge
-    function claimDeposit(ETHDeposit memory deposit, uint256 height, bytes memory proof)
+    function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof)
         external
-        virtual
-        returns (bytes32 id);
+        returns (bytes32 id)
+    {
+        id = _generateId(ethDeposit);
+    }
 
     /// @dev Processes deposit claim by id.
     /// @param id Identifier of the deposit
diff --git a/src/protocol/ICheckpointTracker.sol b/src/protocol/ICheckpointTracker.sol
index c65e8b0b..14160ed0 100644
--- a/src/protocol/ICheckpointTracker.sol
+++ b/src/protocol/ICheckpointTracker.sol
@@ -8,8 +8,9 @@ interface ICheckpointTracker {
     }
 
     /// @notice Emitted when the proven checkpoint is updated
-    /// @param latestCheckpoint the latest proven checkpoint
-    event CheckpointUpdated(Checkpoint latestCheckpoint);
+    /// @param publicationId the publication ID of the latest proven checkpoint
+    /// @param commitment the commitment of the latest proven checkpoint
+    event CheckpointUpdated(uint256 indexed publicationId, bytes32 commitment);
 
     /// @return _ The last proven checkpoint
     function getProvenCheckpoint() external view returns (Checkpoint memory);
diff --git a/src/protocol/ICommitmentStore.sol b/src/protocol/ICommitmentStore.sol
index ad3a5a11..d269385b 100644
--- a/src/protocol/ICommitmentStore.sol
+++ b/src/protocol/ICommitmentStore.sol
@@ -7,34 +7,21 @@ import {ICheckpointTracker} from "./ICheckpointTracker.sol";
 ///
 /// A commitment is any value (typically a state root) that uniquely identifies the state of a chain at a
 /// specific height (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp).
-/// Only an authorized committer can store commitments. For example, only the `CheckpointTracker` can store roots on the
-/// L1,
-/// and the anchor can store block hashes on the L2.
+///
+/// There is no access control so only commitments from trusted sources should be used.
+/// For example, L2 contracts should use L1 commitments saved by the anchor contract and L1 contracts should use L2
+/// commitments saved by the relevant `CheckpointTracker`.
 interface ICommitmentStore {
-    /// @dev A new `commitment` has been stored at a specified `height`.
-    event CommitmentStored(uint256 indexed height, bytes32 commitment);
-
-    /// @dev Emitted when the authorized committer is updated.
-    event AuthorizedCommitterUpdated(address newAuthorizedCommitter);
-
-    /// @dev The caller is not a recognized authorized committer.
-    error UnauthorizedCommitter();
-
-    /// @dev The trusted committer address is empty.
-    error EmptyCommitter();
-
-    /// @dev Returns the current authorized committer.
-    function authorizedCommitter() external view returns (address);
-
-    /// @dev Sets a new authorized committer.
-    /// @param newAuthorizedCommitter The new authorized committer address
-    function setAuthorizedCommitter(address newAuthorizedCommitter) external;
+    /// @dev A new `commitment` has been stored by `source` at a specified `height`.
+    event CommitmentStored(address indexed source, uint256 indexed height, bytes32 commitment);
 
-    /// @dev Returns the commitment at the given `height`.
+    /// @notice Returns the commitment at the given `height`.
+    /// @dev If the commitment does not exist at the given `height`, it returns zero.
+    /// @param source The source address for the saved commitment
     /// @param height The height of the commitment
-    function commitmentAt(uint256 height) external view returns (bytes32 commitment);
+    function commitmentAt(address source, uint256 height) external view returns (bytes32 commitment);
 
-    /// @dev Stores a commitment.
+    /// @notice Stores a commitment attributed to `msg.sender`
     /// @param height The height of the commitment
     /// @param commitment The commitment to store
     function storeCommitment(uint256 height, bytes32 commitment) external;
diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol
index 1e60ef5a..7642f48c 100644
--- a/src/protocol/ISignalService.sol
+++ b/src/protocol/ISignalService.sol
@@ -18,18 +18,24 @@ interface ISignalService {
     /// @param sender The address that sent the signal on the source chain
     /// @param namespace The namespace of the signal
     /// @param value The signal value
-    event SignalSent(address indexed sender, bytes32 namespace, bytes32 value);
+    event SignalSent(address indexed sender, bytes32 indexed namespace, bytes32 value);
 
     /// @dev Emitted when a signal is verified.
     /// @param sender The address of the sender on the source chain
     /// @param value Value that was signaled
     event SignalVerified(address indexed sender, bytes32 value);
 
-    /// @dev Stores a data signal and returns its storage location.
+    /// @dev We require the commitment to contain a state root (with an embedded storage root)
+    error StorageRootCommitmentNotSupported();
+
+    /// @dev If the commitment returns 0 we assume it does not exist
+    error CommitmentNotFound();
+
+    /// @notice Stores a signal and returns its storage location.
     /// @param value Data to be stored (signalled)
     function sendSignal(bytes32 value) external returns (bytes32 slot);
 
-    /// @dev Checks if a signal has been stored
+    /// @notice Checks if a signal has been stored
     /// @dev Note: This does not mean it has been 'sent' to destination chain,
     /// only that it has been stored on the source chain.
     /// @param value Value to be checked is stored
@@ -37,12 +43,21 @@ interface ISignalService {
     /// @param namespace The namespace of the signal
     function isSignalStored(bytes32 value, address sender, bytes32 namespace) external view returns (bool);
 
-    /// @dev Verifies if the signal can be proved to be part of a merkle tree
+    /// @notice Verifies if the signal can be proved to be part of a merkle tree. This is usually used to verify signals
+    /// sent by `sender` on the source chain, which state is represented by `commitmentPublisher` at `height`.
     /// @dev Signals are not deleted when verified, and can be
     /// verified multiple times by calling this function
-    /// @param height This refers to the block number / commitmentId where the trusted root is mapped to
+    /// @param height A reference value indicating which trusted root to use for verification
+    /// see ICommitmentStore for more information
+    /// @param commitmentPublisher The address that published the commitment
     /// @param sender The address that originally sent the signal on the source chain
     /// @param value The signal value to verify
     /// @param proof The encoded value of the SignalProof struct
-    function verifySignal(uint256 height, address sender, bytes32 value, bytes memory proof) external;
+    function verifySignal(
+        uint256 height,
+        address commitmentPublisher,
+        address sender,
+        bytes32 value,
+        bytes memory proof
+    ) external;
 }
diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol
index b25d343e..652c561d 100644
--- a/src/protocol/SignalService.sol
+++ b/src/protocol/SignalService.sol
@@ -4,22 +4,23 @@ pragma solidity ^0.8.28;
 import {LibSignal} from "../libs/LibSignal.sol";
 import {CommitmentStore} from "./CommitmentStore.sol";
 import {ETHBridge} from "./ETHBridge.sol";
+
+import {ICommitmentStore} from "./ICommitmentStore.sol";
 import {ISignalService} from "./ISignalService.sol";
 
-/// @dev SignalService combines secure cross-chain messaging with native token bridging.
+/// @dev SignalService is used for secure cross-chain messaging
 ///
-/// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using
-/// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates:
-/// - `CommitmentStore` to access trusted state roots,
-/// - `ETHBridge` for native ETH bridging with deposit and claim flows,
-/// - `LibSignal` for signal hashing, storage, and verification logic.
+/// This contract allows sending arbitrary data as signals via `sendSignal` and verifying signals from other chains
+/// using`verifySignal`
+///   It integrates:
+///    - `CommitmentStore` to access state roots,
+///    - `LibSignal` for signal hashing, storage, and verification logic.
 ///
-/// Signals stored cannot be deleted and can be verified multiple times.
-contract SignalService is ISignalService, ETHBridge, CommitmentStore {
+/// Signals stored cannot be deleted
+/// WARN: this contract does not provide replay protection(signals can be verified multiple times).
+contract SignalService is ISignalService, CommitmentStore {
     using LibSignal for bytes32;
 
-    constructor(address _rollupOperator) CommitmentStore(_rollupOperator) {}
-
     /// @inheritdoc ISignalService
     /// @dev Signals are stored in a namespaced slot derived from the signal value, sender address and SIGNAL_NAMESPACE
     /// const
@@ -36,44 +37,43 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore {
 
     /// @inheritdoc ISignalService
     /// @dev Cannot be used to verify signals that are under the eth-bridge namespace.
-    function verifySignal(uint256 height, address sender, bytes32 value, bytes memory proof) external {
-        _verifySignal(height, sender, value, LibSignal.SIGNAL_NAMESPACE, proof);
+    function verifySignal(
+        uint256 height,
+        address commitmentPublisher,
+        address sender,
+        bytes32 value,
+        bytes memory proof
+    ) external {
+        _verifySignal(height, commitmentPublisher, sender, value, LibSignal.SIGNAL_NAMESPACE, proof);
         emit SignalVerified(sender, value);
     }
 
-    /// @dev Overrides ETHBridge.depositETH to add signaling functionality.
-    function deposit(address to, bytes memory data) public payable override returns (bytes32 id) {
-        id = super.deposit(to, data);
-        id.signal(msg.sender, ETH_BRIDGE_NAMESPACE);
-    }
-
-    // CHECK: Should this function be non-reentrant?
-    /// @inheritdoc ETHBridge
-    /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic.
-    function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof)
-        external
-        override
-        returns (bytes32 id)
-    {
-        id = _generateId(ethDeposit);
-
-        _verifySignal(height, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof);
-
-        super._processClaimDepositWithId(id, ethDeposit);
-    }
-
-    function _verifySignal(uint256 height, address sender, bytes32 value, bytes32 namespace, bytes memory proof)
-        internal
-        view
-        virtual
-    {
+    function _verifySignal(
+        uint256 height,
+        address commitmentPublisher,
+        address sender,
+        bytes32 value,
+        bytes32 namespace,
+        bytes memory proof
+    ) internal virtual {
         // TODO: commitmentAt(height) might not be the 'state root' of the chain
         // For now it could be the block hash or other hashed value
         // further work is needed to ensure we get the 'state root' of the chain
-        bytes32 root = commitmentAt(height);
+        bytes32 root = commitmentAt(commitmentPublisher, height);
+
+        // A 0 root would probably fail further down the line but its better to explicitly check
+        require(root != 0, CommitmentNotFound());
+
         SignalProof memory signalProof = abi.decode(proof, (SignalProof));
         bytes[] memory accountProof = signalProof.accountProof;
         bytes[] memory storageProof = signalProof.storageProof;
+
+        // We only support state roots for verification
+        // this is to avoid state roots being used as storage roots (for safety)
+        require(accountProof.length != 0, StorageRootCommitmentNotSupported());
+
         value.verifySignal(namespace, sender, root, accountProof, storageProof);
+
+        emit SignalVerified(sender, value);
     }
 }
diff --git a/src/protocol/taiko_alethia/TaikoAnchor.sol b/src/protocol/taiko_alethia/TaikoAnchor.sol
index f0974785..18980d7d 100644
--- a/src/protocol/taiko_alethia/TaikoAnchor.sol
+++ b/src/protocol/taiko_alethia/TaikoAnchor.sol
@@ -15,7 +15,6 @@ contract TaikoAnchor {
     uint256 public lastPublicationId;
     bytes32 public circularBlocksHash;
     mapping(uint256 blockId => bytes32 blockHash) public blockHashes;
-    mapping(uint256 blockId => bytes32 blockHash) public l1BlockHashes;
 
     modifier onlyFromPermittedSender() {
         require(msg.sender == permittedSender, "sender not golden touch");
@@ -61,7 +60,6 @@ contract TaikoAnchor {
         // Persist anchor block hashes
         if (_anchorBlockId > lastAnchorBlockId) {
             lastAnchorBlockId = _anchorBlockId;
-            l1BlockHashes[_anchorBlockId] = _anchorBlockHash;
             // Stores the state of the other chain
             commitmentStore.storeCommitment(_anchorBlockId, _anchorBlockHash);
         }
@@ -80,6 +78,10 @@ contract TaikoAnchor {
         emit Anchor(_publicationId, _anchorBlockId, _anchorBlockHash, _parentGasUsed);
     }
 
+    function l1BlockHashes(uint256 blockId) external view returns (bytes32 blockHash) {
+        return commitmentStore.commitmentAt(address(this), blockId);
+    }
+
     /// @dev Calculates the aggregated ancestor block hash for the given block ID
     /// It uses a ring buffer of 255 bytes32 to store the previous 255 block hashes and the current chain ID
     /// @param _blockId The ID of the block for which the public input hash is calculated
diff --git a/test/CheckpointTracker.t.sol b/test/CheckpointTracker.t.sol
index 55f770d5..69802492 100644
--- a/test/CheckpointTracker.t.sol
+++ b/test/CheckpointTracker.t.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.28;
 
-import {Test, console} from "forge-std/Test.sol";
+import {Test, Vm, console} from "forge-std/Test.sol";
 import {CheckpointTracker} from "src/protocol/CheckpointTracker.sol";
 import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol";
 
@@ -30,19 +30,19 @@ contract CheckpointTrackerTest is Test {
 
     function setUp() public {
         NUM_PUBLICATIONS = 20;
-
         verifier = new NullVerifier();
 
         feed = new PublicationFeed();
-        createSampleFeed();
 
-        signalService = new SignalService(rollupOperator);
+        signalService = new SignalService();
 
         tracker = new CheckpointTracker(
             keccak256(abi.encode("genesis")), address(feed), address(verifier), proverManager, address(signalService)
         );
+
+        createSampleFeed();
+
         vm.prank(rollupOperator);
-        ICommitmentStore(address(signalService)).setAuthorizedCommitter(address(tracker));
         proof = abi.encode("proof");
     }
 
@@ -59,18 +59,18 @@ contract CheckpointTrackerTest is Test {
         new CheckpointTracker(bytes32(0), address(feed), address(verifier), proverManager, address(signalService));
     }
 
-    function test_constructor_EmitsEvent() public {
-        bytes32 genesisCommitment = keccak256(abi.encode("genesis"));
-        ICheckpointTracker.Checkpoint memory genesisCheckpoint =
-            ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment});
-
-        vm.expectEmit();
-        emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint);
-        new CheckpointTracker(
-            genesisCommitment, address(feed), address(verifier), proverManager, address(signalService)
-        );
-    }
-
+    // function test_constructor_EmitsEvent() public {
+    //     bytes32 genesisCommitment = keccak256(abi.encode("genesis"));
+    //     ICheckpointTracker.Checkpoint memory genesisCheckpoint =
+    //         ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment});
+    //
+    //     vm.expectEmit();
+    //     emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint.publicationId, genesisCheckpoint.commitment);
+    //     new CheckpointTracker(
+    //         genesisCommitment, address(feed), address(verifier), proverManager, address(signalService)
+    //     );
+    // }
+    //
     function test_proveTransition_SuccessfulTransition() public {
         ICheckpointTracker.Checkpoint memory start =
             ICheckpointTracker.Checkpoint({publicationId: 0, commitment: keccak256(abi.encode("genesis"))});
@@ -79,7 +79,7 @@ contract CheckpointTrackerTest is Test {
         uint256 numRelevantPublications = 2;
 
         vm.expectEmit();
-        emit ICheckpointTracker.CheckpointUpdated(end);
+        emit ICheckpointTracker.CheckpointUpdated(end.publicationId, end.commitment);
         tracker.proveTransition(start, end, numRelevantPublications, proof);
 
         ICheckpointTracker.Checkpoint memory provenCheckpoint = tracker.getProvenCheckpoint();
diff --git a/test/ProverManager.t.sol b/test/ProverManager.t.sol
index c5fc81c7..8762399e 100644
--- a/test/ProverManager.t.sol
+++ b/test/ProverManager.t.sol
@@ -8,17 +8,17 @@ import {ProverManager} from "../src/protocol/taiko_alethia/ProverManager.sol";
 import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol";
 import {IPublicationFeed} from "src/protocol/IPublicationFeed.sol";
 import {PublicationFeed} from "src/protocol/PublicationFeed.sol";
-import {SignalService} from "src/protocol/SignalService.sol";
 
+import {SignalService} from "src/protocol/SignalService.sol";
 import {MockCheckpointTracker} from "test/mocks/MockCheckpointTracker.sol";
 import {NullVerifier} from "test/mocks/NullVerifier.sol";
 
 contract ProverManagerTest is Test {
     ProverManager proverManager;
     MockCheckpointTracker checkpointTracker;
+    SignalService signalService;
     NullVerifier verifier;
     PublicationFeed publicationFeed;
-    SignalService signalService;
     uint256 constant DEPOSIT_AMOUNT = 2 ether;
 
     // Addresses used for testing.
@@ -43,11 +43,9 @@ contract ProverManagerTest is Test {
     uint256 constant INITIAL_PERIOD = 1;
 
     function setUp() public {
-        signalService = new SignalService(rollupOperator);
-        checkpointTracker = new MockCheckpointTracker(address(signalService));
         publicationFeed = new PublicationFeed();
-        vm.prank(rollupOperator);
-        signalService.setAuthorizedCommitter(address(checkpointTracker));
+        signalService = new SignalService();
+        checkpointTracker = new MockCheckpointTracker(address(signalService));
 
         // Fund the initial prover so the constructor can receive the required livenessBond.
         vm.deal(initialProver, 10 ether);
diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol
index 7c81e45c..77ddcc16 100644
--- a/test/signal/ETHBridge.t.sol
+++ b/test/signal/ETHBridge.t.sol
@@ -19,18 +19,26 @@ contract BridgeETHState is BaseState {
     uint256 public depositAmount = 4 ether;
     ETHBridge.ETHDeposit public depositOne;
 
+    ETHBridge public L1ethBridge;
+    ETHBridge public L2ethBridge;
+
     // this is a valid deposit ID but is sent via a signal not a deposit
     // hence "invalid" and should not be claimable
     bytes32 invalidDepositId = 0xbf8ce3088406c4ddbc32e32404ca006c3ef57f07d5139479f16c9124d6490f2e;
 
     function setUp() public virtual override {
         super.setUp();
+
+        vm.selectFork(L2Fork);
+        L2ethBridge = new ETHBridge();
+
         vm.selectFork(L1Fork);
+        L1ethBridge = new ETHBridge();
 
         vm.prank(defaultSender);
         bytes memory emptyData = "";
         vm.recordLogs();
-        depositIdOne = L1signalService.deposit{value: depositAmount}(defaultSender, emptyData);
+        depositIdOne = L1ethBridge.deposit{value: depositAmount}(defaultSender, emptyData);
 
         Vm.Log[] memory entries = vm.getRecordedLogs();
 
@@ -47,7 +55,7 @@ contract ETHBridgeTest is BridgeETHState {
         assertEq(defaultSender.balance, senderBalanceL1 - depositAmount);
 
         vm.selectFork(L2Fork);
-        assertEq(L2signalService.claimed(depositIdOne), false);
+        assertEq(L2ethBridge.claimed(depositIdOne), false);
         assertEq(defaultSender.balance, senderBalanceL2);
     }
 }
@@ -99,9 +107,9 @@ contract ClaimDepositTest is CommitmentStoredState {
         ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof);
         bytes memory encodedProof = abi.encode(signalProof);
 
-        L2signalService.claimDeposit(depositOne, commitmentHeight, encodedProof);
+        L2ethBridge.claimDeposit(depositOne, commitmentHeight, encodedProof);
 
-        assertEq(address(L2signalService).balance, ETHBridgeInitBalance - depositAmount);
+        assertEq(address(L2ethBridge).balance, ETHBridgeInitBalance - depositAmount);
         assertEq(defaultSender.balance, senderBalanceL2 + depositAmount);
     }
 
@@ -129,9 +137,9 @@ contract ClaimDepositTest is CommitmentStoredState {
 
         vm.selectFork(L2Fork);
         // to be extra sure its not a problem with the proof
-        L2signalService.verifySignal(commitmentHeight, defaultSender, invalidDepositId, encodedProof);
+        L2signalService.verifySignal(commitmentHeight, address(anchor), defaultSender, invalidDepositId, encodedProof);
         // I believe this error means that the proof is not valid for this deposit id
         vm.expectRevert("MerkleTrie: invalid large internal hash");
-        L2signalService.claimDeposit(invalidDeposit, commitmentHeight, encodedProof);
+        L2ethBridge.claimDeposit(invalidDeposit, commitmentHeight, encodedProof);
     }
 }
diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol
index 9e9bf55a..6fca6aa0 100644
--- a/test/signal/SignalService.t.sol
+++ b/test/signal/SignalService.t.sol
@@ -48,7 +48,7 @@ contract BaseState is Test {
         vm.deal(defaultSender, senderBalanceL1);
 
         vm.prank(defaultSender);
-        L1signalService = new SignalService(rollupOperator);
+        L1signalService = new SignalService();
         vm.deal(address(L1signalService), ETHBridgeInitBalance);
 
         checkpointTracker = new MockCheckpointTracker(address(L1signalService));
@@ -60,14 +60,11 @@ contract BaseState is Test {
         vm.deal(defaultSender, senderBalanceL2);
 
         vm.prank(defaultSender);
-        L2signalService = new SignalService(rollupOperator);
+        L2signalService = new SignalService();
         vm.deal(address(L2signalService), ETHBridgeInitBalance);
 
         anchor = new MockAnchor(address(L2signalService));
 
-        vm.prank(rollupOperator);
-        L2signalService.setAuthorizedCommitter(address(anchor));
-
         // Labels for debugging
         vm.label(address(L1signalService), "L1signalService");
         vm.label(address(L2signalService), "L2signalService");
@@ -132,7 +129,7 @@ contract SendL1SignalTest is SendL1SignalState {
         uint256 height = 1;
         anchor.anchor(height, stateRoot);
 
-        L2signalService.verifySignal(height, defaultSender, signal, encodedProof);
+        L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof);
     }
 
     function test_verifyL1Signal_UsingStorageProof() public {
@@ -145,6 +142,6 @@ contract SendL1SignalTest is SendL1SignalState {
         uint256 height = 1;
         anchor.anchor(height, storageRoot);
 
-        L2signalService.verifySignal(height, defaultSender, signal, encodedProof);
+        L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof);
     }
 }


Reviews:
user: login: nikeshnazareth
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-05T13:47:49Z
user: login: ggonzalez94
body: Mostly a bunch of nits for clarity, but looks good to me.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:07:26Z
user: login: LeoPatOZ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:27:32Z
user: login: LeoPatOZ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:27:37Z
user: login: LeoPatOZ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:27:43Z
user: login: ggonzalez94
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:31:09Z
user: login: LeoPatOZ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:43:50Z
user: login: LeoPatOZ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:46:18Z

Review comments:
diff_hunk: @@ -7,14 +7,10 @@ import {IPublicationFeed} from "./IPublicationFeed.sol";
 import {IVerifier} from "./IVerifier.sol";
 
 contract CheckpointTracker is ICheckpointTracker {
-    /// @notice The current proven checkpoint representing the latest verified state of the rollup
-    /// @dev Previous checkpoints are not stored here but are synchronized to the `SignalService`
+    /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup
     /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies
     /// the state of the rollup at a specific point in time
path: src/protocol/CheckpointTracker.sol
user: login: ggonzalez94
body: Remove this since the commitments are not store here anymore?
```suggestion
```
created_at: 2025-05-06T09:07:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,14 +7,10 @@ import {IPublicationFeed} from "./IPublicationFeed.sol";
 import {IVerifier} from "./IVerifier.sol";
 
 contract CheckpointTracker is ICheckpointTracker {
-    /// @notice The current proven checkpoint representing the latest verified state of the rollup
-    /// @dev Previous checkpoints are not stored here but are synchronized to the `SignalService`
+    /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup
path: src/protocol/CheckpointTracker.sol
user: login: ggonzalez94
body: Nit since this is private
```suggestion
    /// @dev The publication id of the current proven checkpoint representing the latest verified state of the rollup
```
created_at: 2025-05-06T09:09:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,14 +7,10 @@ import {IPublicationFeed} from "./IPublicationFeed.sol";
 import {IVerifier} from "./IVerifier.sol";
 
 contract CheckpointTracker is ICheckpointTracker {
-    /// @notice The current proven checkpoint representing the latest verified state of the rollup
-    /// @dev Previous checkpoints are not stored here but are synchronized to the `SignalService`
+    /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup
     /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies
     /// the state of the rollup at a specific point in time
-    /// @dev We store the actual checkpoint(not the hash) to avoid race conditions when closing a period or evicting a
-    /// prover(https://github.com/OpenZeppelin/minimal-rollup/pull/77#discussion_r2002192018)
-
-    Checkpoint private _provenCheckpoint;
+    uint256 private _provenPublicationId;
 
     IPublicationFeed public immutable publicationFeed;
     IVerifier public immutable verifier;
path: src/protocol/CheckpointTracker.sol
user: login: ggonzalez94
body: I know you didn't change this as part of this PR, but should the `proverManager` also be immutable? I don't see a reason to treat it differently
created_at: 2025-05-06T10:35:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,14 +70,17 @@ contract CheckpointTracker is ICheckpointTracker {
             startPublicationHash, endPublicationHash, start.commitment, end.commitment, numPublications, proof
         );
 
-        _provenCheckpoint = end;
-        emit CheckpointUpdated(end);
+        _updateCheckpoint(end.publicationId, end.commitment);
+    }
 
-        // Stores the state of the other chain
-        commitmentStore.storeCommitment(end.publicationId, end.commitment);
+    function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) {
path: src/protocol/CheckpointTracker.sol
user: login: ggonzalez94
body: Nit for consistency
```suggestion
    /// @inheritdoc ICheckpointTracker
    function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) {
```
created_at: 2025-05-06T10:40:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,14 +70,17 @@ contract CheckpointTracker is ICheckpointTracker {
             startPublicationHash, endPublicationHash, start.commitment, end.commitment, numPublications, proof
         );
 
-        _provenCheckpoint = end;
-        emit CheckpointUpdated(end);
+        _updateCheckpoint(end.publicationId, end.commitment);
+    }
 
-        // Stores the state of the other chain
-        commitmentStore.storeCommitment(end.publicationId, end.commitment);
+    function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) {
+        provenCheckpoint.publicationId = _provenPublicationId;
+        provenCheckpoint.commitment = commitmentStore.commitmentAt(address(this), provenCheckpoint.publicationId);
     }
 
-    function getProvenCheckpoint() external view returns (Checkpoint memory) {
-        return _provenCheckpoint;
+    function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal {
path: src/protocol/CheckpointTracker.sol
user: login: ggonzalez94
body: Nit, we should add natspec.
```suggestion
    /// @dev Updates the proven checkpoint to a new publication ID and commitment
    /// @dev Stores the commitment in the commitment store and emits an event
    /// @param publicationId The ID of the publication to set as the latest proven checkpoint
    /// @param commitment The checkpoint commitment representing the state at the given publication ID
    function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal {
```
created_at: 2025-05-06T10:46:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,32 +7,18 @@ import {ICheckpointTracker} from "./ICheckpointTracker.sol";
 ///
 /// A commitment is any value (typically a state root) that uniquely identifies the state of a chain at a
 /// specific height (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp).
-/// Only an authorized committer can store commitments. For example, only the `CheckpointTracker` can store roots on the
-/// L1,
-/// and the anchor can store block hashes on the L2.
+///
+/// There is no access control so only commitments from trusted sources should be used.
+/// For example, L2 contracts should use L1 commitments saved by the anchor contract and L1 contracts should use L2
+/// commitments saved by the relevant `CheckpointTracker`.
 interface ICommitmentStore {
-    /// @dev A new `commitment` has been stored at a specified `height`.
-    event CommitmentStored(uint256 indexed height, bytes32 commitment);
-
-    /// @dev Emitted when the authorized committer is updated.
-    event AuthorizedCommitterUpdated(address newAuthorizedCommitter);
-
-    /// @dev The caller is not a recognized authorized committer.
-    error UnauthorizedCommitter();
-
-    /// @dev The trusted committer address is empty.
-    error EmptyCommitter();
-
-    /// @dev Returns the current authorized committer.
-    function authorizedCommitter() external view returns (address);
-
-    /// @dev Sets a new authorized committer.
-    /// @param newAuthorizedCommitter The new authorized committer address
-    function setAuthorizedCommitter(address newAuthorizedCommitter) external;
+    /// @dev A new `commitment` has been stored by `source` at a specified `height`.
+    event CommitmentStored(address indexed source, uint256 indexed height, bytes32 commitment);
 
     /// @dev Returns the commitment at the given `height`.
+    /// @param source The source address for the saved commitment
     /// @param height The height of the commitment
-    function commitmentAt(uint256 height) external view returns (bytes32 commitment);
+    function commitmentAt(address source, uint256 height) external view returns (bytes32 commitment);
path: src/protocol/ICommitmentStore.sol
user: login: ggonzalez94
body: I imagine if the commitment does not exist at `height` the implementation is expected to return zero. Should we include that as part of the description? Also since this function is external I would expect it having a `notice` as part of the natspec
created_at: 2025-05-06T10:56:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,32 +7,18 @@ import {ICheckpointTracker} from "./ICheckpointTracker.sol";
 ///
 /// A commitment is any value (typically a state root) that uniquely identifies the state of a chain at a
 /// specific height (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp).
-/// Only an authorized committer can store commitments. For example, only the `CheckpointTracker` can store roots on the
-/// L1,
-/// and the anchor can store block hashes on the L2.
+///
+/// There is no access control so only commitments from trusted sources should be used.
+/// For example, L2 contracts should use L1 commitments saved by the anchor contract and L1 contracts should use L2
+/// commitments saved by the relevant `CheckpointTracker`.
 interface ICommitmentStore {
-    /// @dev A new `commitment` has been stored at a specified `height`.
-    event CommitmentStored(uint256 indexed height, bytes32 commitment);
-
-    /// @dev Emitted when the authorized committer is updated.
-    event AuthorizedCommitterUpdated(address newAuthorizedCommitter);
-
-    /// @dev The caller is not a recognized authorized committer.
-    error UnauthorizedCommitter();
-
-    /// @dev The trusted committer address is empty.
-    error EmptyCommitter();
-
-    /// @dev Returns the current authorized committer.
-    function authorizedCommitter() external view returns (address);
-
-    /// @dev Sets a new authorized committer.
-    /// @param newAuthorizedCommitter The new authorized committer address
-    function setAuthorizedCommitter(address newAuthorizedCommitter) external;
+    /// @dev A new `commitment` has been stored by `source` at a specified `height`.
+    event CommitmentStored(address indexed source, uint256 indexed height, bytes32 commitment);
 
     /// @dev Returns the commitment at the given `height`.
+    /// @param source The source address for the saved commitment
     /// @param height The height of the commitment
-    function commitmentAt(uint256 height) external view returns (bytes32 commitment);
+    function commitmentAt(address source, uint256 height) external view returns (bytes32 commitment);
 
     /// @dev Stores a commitment.
path: src/protocol/ICommitmentStore.sol
user: login: ggonzalez94
body: ```suggestion
    /// @dev Stores a commitment attributed to `msg.sender`
```
created_at: 2025-05-06T10:56:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,54 +1,24 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.28;
 
-import {ICheckpointTracker} from "./ICheckpointTracker.sol";
 import {ICommitmentStore} from "./ICommitmentStore.sol";
 
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
 import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
 
 /// @dev Base contract for storing commitments.
-abstract contract CommitmentStore is ICommitmentStore, Ownable {
+abstract contract CommitmentStore is ICommitmentStore {
     using SafeCast for uint256;
path: src/protocol/CommitmentStore.sol
user: login: ggonzalez94
body: I believe we are not using this on this contract so we can remove this and the import?
```suggestion
```
created_at: 2025-05-06T11:04:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,8 +44,15 @@ interface ISignalService {
     /// @dev Signals are not deleted when verified, and can be
     /// verified multiple times by calling this function
     /// @param height This refers to the block number / commitmentId where the trusted root is mapped to
+    /// @param commitmentPublisher The address that published the commitment containing the signal.
path: src/protocol/ISignalService.sol
user: login: ggonzalez94
body: Isn't the commitment what we will use to verify the signal and not what "contains the signal"? I guess technically it is true, I just find the language a bit more confusing, but feel free to ignore
created_at: 2025-05-06T11:07:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,22 +4,21 @@ pragma solidity ^0.8.28;
 import {LibSignal} from "../libs/LibSignal.sol";
 import {CommitmentStore} from "./CommitmentStore.sol";
 import {ETHBridge} from "./ETHBridge.sol";
+
+import {ICommitmentStore} from "./ICommitmentStore.sol";
 import {ISignalService} from "./ISignalService.sol";
 
 /// @dev SignalService combines secure cross-chain messaging with native token bridging.
path: src/protocol/SignalService.sol
user: login: ggonzalez94
body: It does not provide bridging anymore
```suggestion
/// @dev SignalService provides secure cross-chain messaging.
```
created_at: 2025-05-06T11:11:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,22 +4,21 @@ pragma solidity ^0.8.28;
 import {LibSignal} from "../libs/LibSignal.sol";
 import {CommitmentStore} from "./CommitmentStore.sol";
 import {ETHBridge} from "./ETHBridge.sol";
+
+import {ICommitmentStore} from "./ICommitmentStore.sol";
 import {ISignalService} from "./ISignalService.sol";
 
 /// @dev SignalService combines secure cross-chain messaging with native token bridging.
 ///
 /// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using
 /// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates:
path: src/protocol/SignalService.sol
user: login: ggonzalez94
body: ```suggestion
/// This contract allows sending arbitrary data as signals via `sendSignal` and verifying signals from other chains using
/// `verifySignal`. It integrates:
```
created_at: 2025-05-06T11:12:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,22 +4,21 @@ pragma solidity ^0.8.28;
 import {LibSignal} from "../libs/LibSignal.sol";
 import {CommitmentStore} from "./CommitmentStore.sol";
 import {ETHBridge} from "./ETHBridge.sol";
+
+import {ICommitmentStore} from "./ICommitmentStore.sol";
 import {ISignalService} from "./ISignalService.sol";
 
 /// @dev SignalService combines secure cross-chain messaging with native token bridging.
 ///
 /// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using
 /// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates:
-/// - `CommitmentStore` to access trusted state roots,
-/// - `ETHBridge` for native ETH bridging with deposit and claim flows,
+/// - `CommitmentStore` to access state roots,
 /// - `LibSignal` for signal hashing, storage, and verification logic.
 ///
 /// Signals stored cannot be deleted and can be verified multiple times.
path: src/protocol/SignalService.sol
user: login: ggonzalez94
body: ```suggestion
/// Signals stored cannot be deleted and this contract does not provide replay protection(signals can be verified multiple times).
```
created_at: 2025-05-06T11:13:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,44 +35,35 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore {
 
     /// @inheritdoc ISignalService
     /// @dev Cannot be used to verify signals that are under the eth-bridge namespace.
path: src/protocol/SignalService.sol
user: login: ggonzalez94
body: ```suggestion
    /// @dev Can only be used to verify signals that are under the `SIGNAL_NAMESPACE` namespace. This means this function cannot be used to verify signals from the `EthBridge` for example
```
created_at: 2025-05-06T12:47:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,8 +44,15 @@ interface ISignalService {
     /// @dev Signals are not deleted when verified, and can be
     /// verified multiple times by calling this function
     /// @param height This refers to the block number / commitmentId where the trusted root is mapped to
path: src/protocol/ISignalService.sol
user: login: ggonzalez94
body: Now we are using the `publicationId` for the height. Should we update the this comment to be more broad?
created_at: 2025-05-06T12:53:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,44 +35,35 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore {
 
     /// @inheritdoc ISignalService
     /// @dev Cannot be used to verify signals that are under the eth-bridge namespace.
-    function verifySignal(uint256 height, address sender, bytes32 value, bytes memory proof) external {
-        _verifySignal(height, sender, value, LibSignal.SIGNAL_NAMESPACE, proof);
+    function verifySignal(
+        uint256 height,
+        address commitmentPublisher,
+        address sender,
+        bytes32 value,
+        bytes memory proof
+    ) external {
+        _verifySignal(height, commitmentPublisher, sender, value, LibSignal.SIGNAL_NAMESPACE, proof);
         emit SignalVerified(sender, value);
     }
 
-    /// @dev Overrides ETHBridge.depositETH to add signaling functionality.
-    function deposit(address to, bytes memory data) public payable override returns (bytes32 id) {
-        id = super.deposit(to, data);
-        id.signal(msg.sender, ETH_BRIDGE_NAMESPACE);
-    }
-
-    // CHECK: Should this function be non-reentrant?
-    /// @inheritdoc ETHBridge
-    /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic.
-    function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof)
-        external
-        override
-        returns (bytes32 id)
-    {
-        id = _generateId(ethDeposit);
-
-        _verifySignal(height, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof);
-
-        super._processClaimDepositWithId(id, ethDeposit);
-    }
-
-    function _verifySignal(uint256 height, address sender, bytes32 value, bytes32 namespace, bytes memory proof)
-        internal
-        view
-        virtual
-    {
+    function _verifySignal(
+        uint256 height,
+        address commitmentPublisher,
+        address sender,
+        bytes32 value,
+        bytes32 namespace,
+        bytes memory proof
+    ) internal view virtual {
         // TODO: commitmentAt(height) might not be the 'state root' of the chain
         // For now it could be the block hash or other hashed value
         // further work is needed to ensure we get the 'state root' of the chain
-        bytes32 root = commitmentAt(height);
+        bytes32 root = commitmentAt(commitmentPublisher, height);
         SignalProof memory signalProof = abi.decode(proof, (SignalProof));
         bytes[] memory accountProof = signalProof.accountProof;
         bytes[] memory storageProof = signalProof.storageProof;
+        // if there is no account proof, verify signal will treat root as a storage root
+        // for now, we only support full state roots
path: src/protocol/SignalService.sol
user: login: ggonzalez94
body: This better reflect our reasoning IMO
```suggestion
        // for safety(avoiding state roots to be used as storage roots) we only support state roots for verification
```
created_at: 2025-05-06T12:58:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,14 +7,10 @@ import {IPublicationFeed} from "./IPublicationFeed.sol";
 import {IVerifier} from "./IVerifier.sol";
 
 contract CheckpointTracker is ICheckpointTracker {
-    /// @notice The current proven checkpoint representing the latest verified state of the rollup
-    /// @dev Previous checkpoints are not stored here but are synchronized to the `SignalService`
+    /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup
     /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies
     /// the state of the rollup at a specific point in time
-    /// @dev We store the actual checkpoint(not the hash) to avoid race conditions when closing a period or evicting a
-    /// prover(https://github.com/OpenZeppelin/minimal-rollup/pull/77#discussion_r2002192018)
-
-    Checkpoint private _provenCheckpoint;
+    uint256 private _provenPublicationId;
 
     IPublicationFeed public immutable publicationFeed;
     IVerifier public immutable verifier;
path: src/protocol/CheckpointTracker.sol
user: login: LeoPatOZ
body: yeah (unless we want to support changing the prover manager mechanism?) but changed it to immutable for now
created_at: 2025-05-06T13:27:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,8 +44,15 @@ interface ISignalService {
     /// @dev Signals are not deleted when verified, and can be
     /// verified multiple times by calling this function
     /// @param height This refers to the block number / commitmentId where the trusted root is mapped to
path: src/protocol/ISignalService.sol
user: login: LeoPatOZ
body: okay updated
created_at: 2025-05-06T13:27:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,44 +35,35 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore {
 
     /// @inheritdoc ISignalService
     /// @dev Cannot be used to verify signals that are under the eth-bridge namespace.
path: src/protocol/SignalService.sol
user: login: LeoPatOZ
body: I dont mind but we remove the notion of namespacing in the next PR so didnt bother changing it here
created_at: 2025-05-06T13:27:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,44 +35,35 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore {
 
     /// @inheritdoc ISignalService
     /// @dev Cannot be used to verify signals that are under the eth-bridge namespace.
path: src/protocol/SignalService.sol
user: login: ggonzalez94
body: oh sorry, I'm just starting to review the next PR. So feel free to ignore things that have been addressed or are redundant
created_at: 2025-05-06T13:31:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,32 +7,18 @@ import {ICheckpointTracker} from "./ICheckpointTracker.sol";
 ///
 /// A commitment is any value (typically a state root) that uniquely identifies the state of a chain at a
 /// specific height (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp).
-/// Only an authorized committer can store commitments. For example, only the `CheckpointTracker` can store roots on the
-/// L1,
-/// and the anchor can store block hashes on the L2.
+///
+/// There is no access control so only commitments from trusted sources should be used.
+/// For example, L2 contracts should use L1 commitments saved by the anchor contract and L1 contracts should use L2
+/// commitments saved by the relevant `CheckpointTracker`.
 interface ICommitmentStore {
-    /// @dev A new `commitment` has been stored at a specified `height`.
-    event CommitmentStored(uint256 indexed height, bytes32 commitment);
-
-    /// @dev Emitted when the authorized committer is updated.
-    event AuthorizedCommitterUpdated(address newAuthorizedCommitter);
-
-    /// @dev The caller is not a recognized authorized committer.
-    error UnauthorizedCommitter();
-
-    /// @dev The trusted committer address is empty.
-    error EmptyCommitter();
-
-    /// @dev Returns the current authorized committer.
-    function authorizedCommitter() external view returns (address);
-
-    /// @dev Sets a new authorized committer.
-    /// @param newAuthorizedCommitter The new authorized committer address
-    function setAuthorizedCommitter(address newAuthorizedCommitter) external;
+    /// @dev A new `commitment` has been stored by `source` at a specified `height`.
+    event CommitmentStored(address indexed source, uint256 indexed height, bytes32 commitment);
 
     /// @dev Returns the commitment at the given `height`.
+    /// @param source The source address for the saved commitment
     /// @param height The height of the commitment
-    function commitmentAt(uint256 height) external view returns (bytes32 commitment);
+    function commitmentAt(address source, uint256 height) external view returns (bytes32 commitment);
path: src/protocol/ICommitmentStore.sol
user: login: LeoPatOZ
body: yes true i added it as a comment,

additionally i added 

```solidity
        require(root != 0, CommitmentNotFound());
```
in the signal service, as ai realised we never explicitly check this (even though we a zero commitment will probably fail)

Maybe this should be in the commitment store directly ? 
created_at: 2025-05-06T13:43:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,8 +44,15 @@ interface ISignalService {
     /// @dev Signals are not deleted when verified, and can be
     /// verified multiple times by calling this function
     /// @param height This refers to the block number / commitmentId where the trusted root is mapped to
+    /// @param commitmentPublisher The address that published the commitment containing the signal.
path: src/protocol/ISignalService.sol
user: login: LeoPatOZ
body: I removed the that part now i think its clear that the the commitment is what will be used to verify a signal 
created_at: 2025-05-06T13:46:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: nikeshnazareth
created_at: 2025-05-05T13:47:33Z
body: I made a couple of changes. I'm approving this even though the test are in an inconsistent state because #113 should complete the Bridge change and fix the tests.
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-06T13:53:40Z
body: # Changes to gas cost

> Generated at commit: [be89ba22213d7ca3d20a16de28aaff6f6b84a634](/OpenZeppelin/minimal-rollup/commit/be89ba22213d7ca3d20a16de28aaff6f6b84a634), compared to commit: [2265da85b125966e4be5d95061f7a130edf2945e](/OpenZeppelin/minimal-rollup/commit/2265da85b125966e4be5d95061f7a130edf2945e)

### 🧾 Summary (10% most significant diffs)

| Contract | Method | Avg (+/-) | % |
|:-|:-|-:|-:|
| **CheckpointTracker** | _getProvenCheckpoint_<br />_proveTransition_ | +4,302 ❌<br />-6,599 ✅ | **+87.07%**<br />**-6.38%** |
---

<details>
<summary><strong>Full diff report</strong> 👇</summary>
<br />

| Contract | Deployment Cost (+/-) | Method | Min (+/-) | % | Avg (+/-) | % | Median (+/-) | % | Max (+/-) | % | # Calls (+/-) |
|:-|-:|:-|-:|-:|-:|-:|-:|-:|-:|-:|-:|
| **CheckpointTracker** | 931,224&nbsp;(-48,213) | _getProvenCheckpoint_<br />_proveTransition_ | 9,243&nbsp;(+4,302)<br />96,785&nbsp;(-6,599) | **+87.07%**<br />**-6.38%** | 9,243&nbsp;(+4,302)<br />96,785&nbsp;(-6,599) | **+87.07%**<br />**-6.38%** | 9,243&nbsp;(+4,302)<br />96,785&nbsp;(-6,599) | **+87.07%**<br />**-6.38%** | 9,243&nbsp;(+4,302)<br />96,785&nbsp;(-6,599) | **+87.07%**<br />**-6.38%** | 2&nbsp;(0)<br />1&nbsp;(0) |
| **ProverManager** | 2,854,874&nbsp;(0) | _prove_ | 103,390&nbsp;(-1,796) | **-1.71%** | 125,448&nbsp;(-1,796) | **-1.41%** | 127,325&nbsp;(-1,796) | **-1.39%** | 140,006&nbsp;(-1,796) | **-1.27%** | 6&nbsp;(0) |
| **PublicationFeed** | 760,671&nbsp;(0) | _getPublicationHash_ | 2,936&nbsp;(0) | **0.00%** | 4,888&nbsp;(-15) | **-0.31%** | 4,936&nbsp;(0) | **0.00%** | 4,936&nbsp;(0) | **0.00%** | 42&nbsp;(-20) |
| **SignalService** | 2,913,333&nbsp;(-640,932) |  |  |  |  |  |  |  |  |  |  |
</details>
<!-- Sticky Pull Request Comment -->
author_association: NONE
user: login: LeoPatOZ
created_at: 2025-05-06T13:58:54Z
body: > Mostly a bunch of nits for clarity, but looks good to me. One question I had is: Should we make the functions in the SignalService public instead of external? I imagine contracts will also want to interact with them and not only EOAs.

Im confused doesnt external mean it just cant be called internally? external functions can still be called from other contracts
author_association: CONTRIBUTOR