diff --git a/l1-contracts/contracts/state-transition/MultisigCommiter.sol b/l1-contracts/contracts/state-transition/MultisigCommiter.sol new file mode 100644 index 0000000000..c6756f05e4 --- /dev/null +++ b/l1-contracts/contracts/state-transition/MultisigCommiter.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {LibMap} from "./libraries/LibMap.sol"; +import {IZKChain} from "./chain-interfaces/IZKChain.sol"; +import {TimeNotReached, NotAZKChain} from "../common/L1ContractErrors.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; +import {IValidatorTimelock} from "./IValidatorTimelock.sol"; +import {IExecutor} from "./chain-interfaces/IExecutor.sol"; +import {ValidatorTimelock} from "./ValidatorTimelock.sol"; +import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable-v4/utils/cryptography/EIP712Upgradeable.sol"; +import {SignatureCheckerUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/utils/cryptography/SignatureCheckerUpgradeable.sol"; + + +//TODO proper errors +//TODO maybe combine with ValidatorTimelock +contract MultisigCommiter is ValidatorTimelock, EIP712Upgradeable { + /// @dev EIP-712 TypeHash for commitBatchesMultisig + bytes32 internal constant COMMIT_BATCHES_MULTISIG_TYPEHASH = + keccak256("CommitBatchesMultisig(address chainAddress, uint256 processBatchFrom, uint256 processBatchTo, bytes batchData)"); + + bytes32 internal constant COMMIT_VERIFIER_ROLE = keccak256("COMMIT_VERIFIER_ROLE"); + + mapping(address chainAddress => uint256 signingThreshold) internal signingThreshold; + + constructor(address _bridgehubAddr) ValidatorTimelock(_bridgehubAddr) {} + + //TODO __EIP712_init + + function commitBatchesSharedBridge( + address _chainAddress, + uint256 _processBatchFrom, + uint256 _processBatchTo, + bytes calldata _batchData + ) external override { + require(signingThreshold[_chainAddress] == 0, "Chain requires verifiers signatures for commit"); + _recordBatchCommitment(_chainAddress, _processBatchFrom, _processBatchTo); + _propagateToZKChain(_chainAddress); + } + + function commitBatchesMultisig( + address chainAddress, + uint256 _processBatchFrom, + uint256 _processBatchTo, + bytes calldata _batchData, + address[] calldata signers, + bytes[] calldata signatures + ) external onlyRole(chainAddress, COMMITTER_ROLE) { + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(COMMIT_BATCHES_MULTISIG_TYPEHASH, chainAddress, _processBatchFrom, _processBatchTo, _batchData))); + + _checkSignatures(chainAddress, signers, signatures, digest); + + _recordBatchCommitment(chainAddress, _processBatchFrom, _processBatchTo); + // we cannot use _propagateToZKChain here, becouse function signature is altered + IExecutor(chainAddress).commitBatchesSharedBridge(chainAddress, _processBatchFrom, _processBatchTo, _batchData); + } + + function _checkSignatures(address chainAddress, address[] calldata signers, bytes[] calldata signatures, bytes32 digest) internal view { + require(signers.length == signatures.length, "Mismatching signatures length"); + require(signers.length >= signingThreshold[chainAddress], "Not enough signers"); + + // signers must be sorted in order to cheaply validate they are not duplicated + address previousSigner = address(0); + for (uint256 i = 0; i < signers.length; i++) { + require(signers[i] > previousSigner, "Signers must be sorted"); + require(hasRole(chainAddress, COMMIT_VERIFIER_ROLE, signers[i]), "Invalid signature"); + require(SignatureCheckerUpgradeable.isValidSignatureNow(signers[i], digest, signatures[i]), "Invalid signature"); + previousSigner = signers[i]; + } + } +} \ No newline at end of file diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol index 080bab5842..83b79b9bc2 100644 --- a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -199,7 +199,14 @@ contract ValidatorTimelock is uint256 _processBatchFrom, uint256 _processBatchTo, bytes calldata // _batchData (unused in this specific implementation) - ) external onlyRole(_chainAddress, COMMITTER_ROLE) { + ) external virtual onlyRole(_chainAddress, COMMITTER_ROLE) { + _recordBatchCommitment(_chainAddress, _processBatchFrom, _processBatchTo); + _propagateToZKChain(_chainAddress); + } + + /// @dev Records the timestamp of batch commitment for the given chain address. + /// To be used from `commitBatchesSharedBridge` + function _recordBatchCommitment(address _chainAddress, uint256 _processBatchFrom, uint256 _processBatchTo) internal { unchecked { // This contract is only a temporary solution, that hopefully will be disabled until 2106 year, so... // It is safe to cast. @@ -208,7 +215,6 @@ contract ValidatorTimelock is committedBatchTimestamp[_chainAddress].set(i, timestamp); } } - _propagateToZKChain(_chainAddress); } /// @inheritdoc IValidatorTimelock