// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; import "@openzeppelin/contracts/cryptography/ECDSA.sol"; import "@summa-tx/memview-sol/contracts/TypedMemView.sol"; /** * @title Message Library * @author Celo Labs Inc. * @notice Library for formatted messages used by Home and Replica. **/ library Message { using TypedMemView for bytes; using TypedMemView for bytes29; // Number of bytes in formatted message before `body` field uint256 internal constant PREFIX_LENGTH = 76; /** * @notice Returns formatted (packed) message with provided fields * @param _origin Domain of home chain * @param _sender Address of sender as bytes32 * @param _sequence Destination-specific sequence number * @param _destination Domain of destination chain * @param _recipient Address of recipient on destination chain as bytes32 * @param _body Raw bytes of message body * @return Formatted message **/ function formatMessage( uint32 _origin, bytes32 _sender, uint32 _sequence, uint32 _destination, bytes32 _recipient, bytes memory _body ) internal pure returns (bytes memory) { return abi.encodePacked( _origin, _sender, _sequence, _destination, _recipient, _body ); } /** * @notice Returns leaf of formatted message with provided fields. * @param _origin Domain of home chain * @param _sender Address of sender as bytes32 * @param _sequence Destination-specific sequence number * @param _destination Domain of destination chain * @param _recipient Address of recipient on destination chain as bytes32 * @param _body Raw bytes of message body * @return Leaf (hash) of formatted message **/ function messageHash( uint32 _origin, bytes32 _sender, uint32 _sequence, uint32 _destination, bytes32 _recipient, bytes memory _body ) internal pure returns (bytes32) { return keccak256( formatMessage( _origin, _sender, _sequence, _destination, _recipient, _body ) ); } /// @notice Returns message's origin field function origin(bytes29 _message) internal pure returns (uint32) { return uint32(_message.indexUint(0, 4)); } /// @notice Returns message's sender field function sender(bytes29 _message) internal pure returns (bytes32) { return _message.index(4, 32); } /// @notice Returns message's sequence field function sequence(bytes29 _message) internal pure returns (uint32) { return uint32(_message.indexUint(36, 4)); } /// @notice Returns message's destination field function destination(bytes29 _message) internal pure returns (uint32) { return uint32(_message.indexUint(40, 4)); } /// @notice Returns message's recipient field as bytes32 function recipient(bytes29 _message) internal pure returns (bytes32) { return _message.index(44, 32); } /// @notice Returns message's recipient field as an address function recipientAddress(bytes29 _message) internal pure returns (address) { return address(uint160(uint256(recipient(_message)))); } /// @notice Returns message's body field as bytes29 (refer to TypedMemView library for details on bytes29 type) function body(bytes29 _message) internal pure returns (bytes29) { return _message.slice(PREFIX_LENGTH, _message.len() - PREFIX_LENGTH, 0); } } /** * @title Common * @author Celo Labs Inc. * @notice Shared utilities between Home and Replica. **/ abstract contract Common { enum States {ACTIVE, FAILED} /// @notice Domain of owning contract uint32 public immutable originDomain; /// @notice Hash of `originDomain` concatenated with "OPTICS" bytes32 public immutable domainHash; /// @notice Address of bonded updater address public updater; /// @notice Current state of contract States public state; /// @notice Current root bytes32 public current; /** * @notice Event emitted when update is made on Home or unconfirmed update * root is enqueued on Replica * @param _originDomain Domain of contract's chain * @param _oldRoot Old merkle root * @param _newRoot New merkle root * @param signature Updater's signature on `_oldRoot` and `_newRoot` **/ event Update( uint32 indexed _originDomain, bytes32 indexed _oldRoot, bytes32 indexed _newRoot, bytes signature ); /** * @notice Event emitted when valid double update proof is provided to * contract * @param _oldRoot Old root shared between two conflicting updates * @param _newRoot Array containing two conflicting new roots * @param _signature Signature on `_oldRoot` and `_newRoot`[0] * @param _signature2 Signature on `_oldRoot` and `_newRoot`[1] **/ event DoubleUpdate( bytes32 _oldRoot, bytes32[2] _newRoot, bytes _signature, bytes _signature2 ); constructor( uint32 _originDomain, address _updater, bytes32 _current ) { originDomain = _originDomain; updater = _updater; current = _current; domainHash = keccak256(abi.encodePacked(_originDomain, "OPTICS")); state = States.ACTIVE; } /// @notice Called when a double update or fraudulent update is detected function fail() internal virtual; /// @notice Sets contract state to FAILED function _setFailed() internal { state = States.FAILED; } /// @notice Ensures that contract state != FAILED modifier notFailed() { require(state != States.FAILED, "failed state"); _; } /** * @notice Called internally. Checks that signature is valid (belongs to * updater). * @param _oldRoot Old merkle root * @param _newRoot New merkle root * @param _signature Signature on `_oldRoot` and `_newRoot` * @return Returns true if signature is valid and false if otherwise **/ function checkSig( bytes32 _oldRoot, bytes32 _newRoot, bytes memory _signature ) internal view returns (bool) { bytes32 _digest = keccak256(abi.encodePacked(domainHash, _oldRoot, _newRoot)); _digest = ECDSA.toEthSignedMessageHash(_digest); return ECDSA.recover(_digest, _signature) == updater; } /** * @notice Called by external agent. Checks that signatures on two sets of * roots are valid and that the new roots conflict with each other. If both * cases hold true, the contract is failed and a `DoubleUpdate` event is * emitted. * @dev When `fail()` is called on Home, updater is slashed. * @param _oldRoot Old root shared between two conflicting updates * @param _newRoot Array containing two conflicting new roots * @param _signature Signature on `_oldRoot` and `_newRoot`[0] * @param _signature2 Signature on `_oldRoot` and `_newRoot`[1] **/ function doubleUpdate( bytes32 _oldRoot, bytes32[2] calldata _newRoot, bytes calldata _signature, bytes calldata _signature2 ) external notFailed { if ( Common.checkSig(_oldRoot, _newRoot[0], _signature) && Common.checkSig(_oldRoot, _newRoot[1], _signature2) && _newRoot[0] != _newRoot[1] ) { fail(); emit DoubleUpdate(_oldRoot, _newRoot, _signature, _signature2); } } }